最新要闻
- 全球简讯:为什么感觉工资过万很普遍了?打打字就能月入过万你心动吗?央视揭秘新骗局
- 《生化危机4:重制版》第五章演示:里昂和碍事梨合作通关
- 云南小女孩骑鸵鸟上学从容淡定 挡眼睛控制方向:网友调侃是大象年检了
- 观焦点:造车新势力轿车月榜Top2 长安深蓝SL03迎开门红:1月交付6137台
- 环球快消息!越野车开进古河床随意碾压:改装牧马人无视警示牌“撒野” 专家:保护有难度
- 天天微资讯!2899元价格屠夫!XiaoMI Book 12.4 二合一评测:办公追剧不在话下
- 乳腺癌已成为全球第一大癌症:我国每年新增42万 比国外发病早
- 今日观点!投资不过山海关对东北伤害狠!老工业基地全力发展新能源车 专家称沈阳可成深圳
- 世界今热点:全球首位!以色列总统使用ChatGPT写演讲稿:开头、结尾感受下
- 全球看热讯:《角斗士2》明年上映
- 全球热点评!阿里云盘致歉:昨晚系统故障 全平台无法加载内容
- TGA年度最佳!《双人成行》销量破1000万:双人游戏天花板
- 环球今日报丨特斯拉降价到20万出头 网友忍不住要下单 宝马奔驰大众:我们不跟
- 速递!腾讯视频官宣:《三体》番外剧《三体:大史》即将上线
- 世界今日报丨大跃进了!今年小米新机都将抛弃USB 2.0
- 今日立春:二十四节气之首 万物开始复苏
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
【缓存策略及实践】前端如何配置 HTTP 缓存机制
缓存的目的
主要作用是可以加快资源获取速度,提升用户体验,减少网络传输,缓解服务端的压力。
强缓存
不需要发送请求到服务端,直接读取浏览器本地缓存,显示的 HTTP 状态码是 200 ,强缓存又分为 Disk Cache (存放在硬盘中)和 Memory Cache (存放在内存中),存放的位置是由浏览器控制的。是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header 属性共同来控制。浏览器在向服务器发送http请求的时候,服务器会将缓存规则返给http响应报文的http头和请求结果一起返回给浏览器
Expires绝对时间
(资料图)
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Expires
是HTTP/1
的产物,表示资源会在Wed, 22 Oct 2018 08:41:00 GMT
后过期,需要再次请求。并且Expires
受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-control相对时间
max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应public:响应可以被中间代理、CDN 等缓存must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证
Cache-control: max-age=30
Cache-Control
出现于HTTP/1.1
,优先级高于Expires
。该属性值表示资源会在30
秒后过期,需要再次请求。Cache-Control
可以在请求头或者响应头中设置,并且可以组合使用多种指令- 置Cache-Control的值为“public, max-age=xxx”,表示在xxx秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。
情况主要有三种
1)不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)
2)协议中有这种缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存
3)协议中存在缓存结果和缓存标识,且该结果尚未生效,强制缓存生效,直接返回该结果
强缓存缺点:
如果在xxx秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果发布新版本的时候,后台接口也同步更新了,那就gg了。有缓存的用户还在使用旧接口,而那个接口已经被后台干掉了。怎么办?
协商缓存
当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态通知浏览器从缓存中读取资源,并且响应头会设置 Last-Modified 或者 Etag 属性。
协商缓存可以通过 Last-Modified
/If-Modified-Since
和ETag
/If-None-Match
这两对 Header 来控制。
Last-Modified 与If-Modified-Since都是用来记录页面的最后修改时间。当客户端访问页面时,服务器会将页面最后修改时间通过 Last-Modified 标识由服务器发往客户端,客户端记录修改时间,再次请求本地存在的cache页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的最后修改时间戳发送回去,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则 返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传
输的数据就会大大减少,同时也减轻了服务器的负担。
Last-Modified、If-Modified-Since
Last-Modified
与If-Modified-Since
的值都是 GMT 格式的时间字符串,代表的是文件的最后修改时间。
在服务器在响应请求时,会通过
Last-Modified
告诉浏览器资源的最后修改时间。浏览器再次请求服务器的时候,请求头会包含
If-Modified-Since
字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有
if-Modified-Since
,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。如果已经修改,那么开始传输响应一个整体,服务器返回:200 OK
但是在服务器上经常会出现这种情况,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified
时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP/1.1 推出了Etag
。Etag 优先级高与Last-
Modified
。
Etag、If-None-Match
Last-Modified的缺点
1. 最小单位是秒。也就是说如果我短时间内资源发生了改变,Last-Modified 并不会发生变化; 2. 周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为文件是没有变化的是可以使用缓存的,但是 Last-Modified 记录的是上次修改时间,即使文件没有变化,但修改时间变了,所以它认为缓存失效
Etag
为了解决Last-modifed不准确的问题,后面引入了 Etag 。
Etag 一般是由文件内容 hash 生成的,也就是说它可以保证资源的唯一性,资源发生改变就会导致 Etag 发生改变。
同样地,在浏览器第一次请求资源时,服务器会返回一个 Etag 标识。当再次请求该资源时, 会通过 If-no-match 字段将 Etag 发送回服务
器,然后服务器进行比较,如果相等,则返回 304 表示未修改。
1)协商缓存生效,返回304,
2)协商缓存失效,返回200和请求结果结果
强缓存和协商缓存区别
是否需要向服务器发送请求验证本地缓存是否依旧有效。协商缓存,就是需要和服务器进行协商,最终确定是否使用本地缓存。
一定要为你的资源添加 Cache-Control 响应头
经常接触到一些网站,他们的资源文件并没有 Cache-Control
这个响应头。究其原因,在于缓存策略配置这个工作的职责不清,有时候它需要协调前端和运维。
那如果不添加 Cache-Control
这个响应头会怎么样?
是不是每次都会自动去服务器校验新鲜度,很可惜,不是。 此时会对资源进行强制缓存,而对不带有指纹信息的资源很有可能获取到过期资源。如果过期资源存在于浏览器上,还可以通过强制刷新浏览器来获取最新资源。但是如果过期资源存在于 CDN 的边缘节点上,CDN 的刷新就会复杂很多,而且有可能需要多人协作解决。
总结
通过前文,我们了解到 HTTP 缓存主要分:
- 强制缓存
- 协商缓存。
强制缓存由 Cache-Control
,Exipres
(HTTP1.0)控制。浏览器直接读本地缓存,不会再跟服务器端交互,状态码 200。
协商缓存由 Last-Modified
/ If-Modified-Since
, Etag
/If-None-Match
实现,每次请求需要让服务器判断一下资源是否更新过,从而决定浏览器是否使用缓存,如果是,则返回 304,否则重新完整响应。
缓存策略
关于 http 缓存配置的最佳实践为以下两条:
- 文件路径中带有 hash 值:一年的强缓存。因为该文件的内容发生变化时,会生成一个带有新的 hash 值的 URL。前端将会发起一个新的 URL 的请求。配置响应头
Cache-Control: public,max-age=31536000,immutable
- 文件路径中不带有 hash 值:协商缓存。大部分为
public
下文件。配置响应头Cache-Control: no-cache
与etag/last-modified
带指纹资源: 永久缓存
Cache-Control: public,max-age=31536000,immutable
资源请求最快的方式就是不向服务器发起请求,通过以上响应头可以对资源设置永久缓存。
- 静态资源带有 hash 值,即指纹
- 对资源设置一年过期时间,即 31536000,一般认为是永久缓存
- 在永久缓存期间浏览器不需要向服务器发送请求
非带指纹资源: 每次进行新鲜度校验
Cache-Control: no-cacheEtag: helloshanyue
- 由于不带有指纹,每次都需要校验资源的新鲜度。(从缓存中取到资源,可能是过期资源)
- 如果校验为最新资源,则从浏览器的缓存中加载资源
index.html
为不带有指纹资源,如果把它置于缓存中,则如何保证服务器刷新数据时,被浏览器可以获取到新鲜的资源?
因此,使用 Cache-Control: no-cache
时,客户端每次对服务器进行新鲜度校验。
事关打包,尽量减少变动
当处理永久缓存时,切记不可打包为一个大的 bundle.js
,此时一行业务代码的改变,将导致整个项目的永久缓存失效,此时需要按代码更新频率分为多个 chunk 进行打包,可细粒度控制缓存。
webpack-runtime
: 应用中的webpack
的版本比较稳定,分离出来,保证长久的永久缓存react/react-dom
:react
的版本更新频次也较低vendor
: 常用的第三方模块打包在一起,如lodash
,classnames
基本上每个页面都会引用到,但是它们的更新频率会更高一些。另外对低频次使用的第三方模块不要打进来pageA
: A 页面,当 A 页面的组件发生变更后,它的缓存将会失效pageB
: B 页面echarts
: 不常用且过大的第三方模块单独打包mathjax
: 不常用且过大的第三方模块单独打包jspdf
: 不常用且过大的第三方模块单独打包
在 webpack5
中可以使用以下配置:
{ // Automatically split vendor and commons // https://twitter.com/wSokra/status/969633336732905474 // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 splitChunks: { chunks: "all", //all: 对异步和同步的包引入,都会进行代码分割 }, // Keep the runtime chunk separated to enable long term caching // https://twitter.com/wSokra/status/969679223278505985 // https://github.com/facebook/create-react-app/issues/5358 runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}`, },}
如何开启缓存设置
前面了解缓存的方式以及区别还有前端缓存的策略,
既然知道缓存的好处,那么有哪些设置缓存的方式呢?主要有如下三种
- 配置Tomact或者ngix服务器,开启相应缓存模块
- 后端代码中动态设置
- 前端HTML页面meta标签
1.tomact或者nginx服务器
Tomcat7关于Expires的配置官方教程如下,本人阅读后还不是很了解,自行查阅测试:
http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#Expires_Filter
[tomacat中对客户端的缓存机制](http://www.360doc.com/content/17/0721/17/41344223_673116604.shtml
)
接下来以Nginx为例子,该文件为:
在 CRA 应用中,./build/static
目录均由 webpack 构建产生,资源路径将会带有 hash 值。
./build/static├── css│ ├── main.073c9b0a.css│ └── main.073c9b0a.css.map├── js│ ├── 787.cf6a8955.chunk.js│ ├── 787.cf6a8955.chunk.js.map│ ├── main.a3facdf8.js│ ├── main.a3facdf8.js.LICENSE.txt│ └── main.a3facdf8.js.map└── media └── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg
此时可通过 expires
对它们配置一年的长期缓存,它实际上是配置了 Cache-Control: max-age=31536000
的响应头。
nginx.confserver { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html index.htm; location / { # 解决单页应用服务端路由的问题 try_files $uri $uri/ /index.html; # 非带 hash 的资源,需要配置 Cache-Control: no-cache,避免浏览器默认为强缓存 expires -1; } location /static { # 带 hash 的资源,需要配置长期缓存 expires 1y; }}
2.后端代码中动态设置
第二种代码以node.js为例子,代码注释有说明:
const http = require("http");const fs = require("fs");const url = require("url");const path = require("path");const etag = require("etag");const fresh = require("fresh");const server = http.createServer(function (req, res) { let filePath, isHtml, isFresh; const pathname = url.parse(req.url, true).pathname; //根据请求路径取文件绝对路径 if (pathname === "/") { filePath = path.join(__dirname, "/index.html"); isHtml = true; } else { filePath = path.join(__dirname, "static", pathname); isHtml = false; } // 读取文件描述信息,用于计算etag及设置Last-Modified fs.stat(filePath, function (err, stat) { if (err) { res.writeHead(404, "not found"); res.end("404 Not Found
"); } else { if (isHtml) { // html文件使用协商缓存 const lastModified = stat.mtime.toUTCString(); const fileEtag = etag(stat); res.setHeader("Cache-Control", "public, max-age=0"); res.setHeader("Last-Modified", lastModified); res.setHeader("ETag", fileEtag); // 根据请求头判断缓存是否是最新的 isFresh = fresh(req.headers, { "etag": fileEtag, "last-modified": lastModified }); } else { // 其他静态资源使用强缓存 res.setHeader("Cache-Control", "public, max-age=3600"); } fs.readFile(filePath, "utf-8", function (err, fileContent) { if (err) { res.writeHead(404, "not found"); res.end("404 Not Found
"); } else { if (isHtml && isFresh) { //如果缓存是最新的 则返回304状态码 //由于其他资源使用了强缓存 所以不会出现304 res.writeHead(304, "Not Modified"); } else { res.write(fileContent, "utf-8"); } res.end(); } }); } });});server.listen(8080);
3.前端HTML页面meta标签
第三种代码如下
扩展知识
optimization.runtimeChunk 具体作用
runtimeChunk ,作用是将包含chunks映射关系的list单独从app.js里提取出来,因为每一个chunk的id基本都是基于内容hash出来的,所以你每次改动都会影响它,如果不把它提取出来的话,等于app.js每次都会改变,缓存就失效了。
在使用 CommonsChunkPlugin的时候,我们也通常把webpack runtime 的基础函数提取出来,单独作为一个chunk,毕竟code splitting想把不变的代码单独抽离出来,方便浏览器缓存,提升加载速度。
假设一个使用动态导入的情况(使用import()),在app.js
动态导入component.js
const app = () =>import("./component").then();
build之后,产生3个包。
0.01e47fe5.js
main.xxx.js
runtime.xxx.js
其中runtime
,用于管理被分出来的包。下面就是一个runtimeChunk
的截图,可以看到chunkId这些东西。
...function jsonpScriptSrc(chunkId) {/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js"/******/ }...
如果采用这种分包策略
- 当更改
app
的时候runtime
与(被分出的动态加载的代码)0.01e47fe5.js
的名称(hash)
不会改变,main的名称(hash)
会改变。 - 当更改
component.js
,main
的名称(hash)
不会改变,runtime
与 (动态加载的代码)0.01e47fe5.js
的名称(hash)会改变。
【缓存策略及实践】前端如何配置 HTTP 缓存机制
全球简讯:为什么感觉工资过万很普遍了?打打字就能月入过万你心动吗?央视揭秘新骗局
《生化危机4:重制版》第五章演示:里昂和碍事梨合作通关
云南小女孩骑鸵鸟上学从容淡定 挡眼睛控制方向:网友调侃是大象年检了
观焦点:造车新势力轿车月榜Top2 长安深蓝SL03迎开门红:1月交付6137台
环球快消息!越野车开进古河床随意碾压:改装牧马人无视警示牌“撒野” 专家:保护有难度
天天微资讯!2899元价格屠夫!XiaoMI Book 12.4 二合一评测:办公追剧不在话下
微头条丨C盘扩容:不要轻易转换动态磁盘 Dynamic Disk
乳腺癌已成为全球第一大癌症:我国每年新增42万 比国外发病早
今日观点!投资不过山海关对东北伤害狠!老工业基地全力发展新能源车 专家称沈阳可成深圳
世界今热点:全球首位!以色列总统使用ChatGPT写演讲稿:开头、结尾感受下
全球看热讯:《角斗士2》明年上映
全球热点评!阿里云盘致歉:昨晚系统故障 全平台无法加载内容
TGA年度最佳!《双人成行》销量破1000万:双人游戏天花板
环球今日报丨特斯拉降价到20万出头 网友忍不住要下单 宝马奔驰大众:我们不跟
速递!腾讯视频官宣:《三体》番外剧《三体:大史》即将上线
2023年1月随笔
世界今日报丨大跃进了!今年小米新机都将抛弃USB 2.0
今日立春:二十四节气之首 万物开始复苏
8个你可能不知道答案的常见JavaScript面试问题
世界热资讯!荣耀北斗卫星通信专利获批通过 荣耀Magic5系列将首发?
B站《三体》动画“晚节不保”:即将跌破4分
全球实时:再也不怕手一抖跳广告了!规范App乱跳转新标准出台
热门:坚挺四年的苹果:栽了
关注:你以为你真的会玩《俄罗斯方块》?看完这些大神 我大悟了
UI通过元素定位实现特定区域截图
全球热推荐:2022浙江高考数学导数压轴解析
每日速讯:春节开特斯拉出行的国内车主真不少!自驾万里的数以百计
微头条丨开年如何选购生产力整机!锐龙9 7950X vs i9-13900K对比测试:谁是更好的创作工具?
【全球聚看点】客人泡茶放近50根藏红花吓坏主人 真大补药:喝完身体并没有不适
四川公司回应招聘“下班到点跑的绕道”:本职工作完成不用加班
世界今亮点!MySQL数据类型补充
当前资讯!Python中的关键字的用法
每日热闻!在 FreeBSD 12 上安装 Gitea
女子身高185求职当老师被拒 用人单位:常弯腰工作很累
环球焦点!599元 戴尔上架新款透明机械键盘:定制轴体 全键热插拔
AMD Zen4笔记本登顶世界第一!31%优势碾压12代酷睿
环球最资讯丨ES6 简介(一)
【环球热闻】一汽车电梯故障 200多万的法拉利秒变“大事故车”
NVIDIA AD106、AD107小核心首次现身:“减肥”多达30%
全球今亮点!《狂飙》能“逆风翻盘” 一半功劳都是热搜的
全球微速讯:宠托师职业受青睐!上门喂宠物 几天收入数千元
环球微速讯:不用羡慕代驾小哥了!绿源新品TCR开售:整车超轻能跑120km
100%纯果蔬汁:味全每日C果汁5.5元/瓶抄底
私家车定速巡航失灵!时速120狂飙半小时:万幸平安无事
全球快报:《三体》主演于和伟:我本身就是科幻迷!
环球快看点丨1月新能源汽车销量榜:比亚迪“能打”两个特斯拉
全球快讯:iPhone 14 Plus出货跌到0台:苹果拒绝认输
一文搞懂工作流审批(Java+activiti)快速开发+自定义工作流
天天热推荐:HEU_KMS_Activator_v27.0.2全能系统数字许可激活工具
快看:2999元 联想扬天V14/V15笔记本上架:Zen2架构锐龙5 7520U
国产科幻FPS大作!《边境》官宣2月6日开启新测试
环球焦点!网友花2499元就买到了努比亚Z50:系统零广告 性价比无敌
每日热门:AMD终于要解决锐龙7000装机贵的麻烦了 B650主板降价
每日速递:《三体》电视剧惊现360全家桶产品:竟遭周鸿祎挑刺
天天热讯:大神教你显卡和CPU怎么搭配才合适
Python借助企业微信群机器人推送消息和文件
【天天聚看点】【验证码逆向专栏】某验“初代”滑块验证码逆向分析
快资讯丨阿里二面: BigKey、HotKey 问题严重,该如何 预防和解决
Pandas练习
2023年安卓机皇!聊聊三星S23系列与前代有哪些不同
当前观点:高颜值+顶级做工!铭瑄RTX 4080 iCraft OC16G瑷珈显卡评测:三风扇稳压71℃
全球讯息:奥迪E-Tron撞车 电池包撞飞后起火!官方:不清楚是安全功能还是隐患
天天观热点:投屏480p、禁HDMI被吐槽割韭菜!爱奇艺利润将暴增 外资力挺
全球微速讯:《森林之子》PC配置需求公布 1080Ti显卡就能爽玩
看点:微信对话生成器,生成微信聊天记录,聊天记录生成器
环球快看:跳表java实现(可直接运行)
热消息:[概率论与数理统计]笔记:5.5 单正态总体的参数假设检验
依赖注入(DI注入)
当前动态:Wine 8.1版本正式发布:首次默认启用“Windows 10”前缀
为博眼球太奇葩 四川男子用扳手代替方向盘开车拍视频:结果被扣4分
今日热闻!苹果刚发布的2299元新品HomePod 2仅支持老掉牙Wi-Fi 4:原因不服不行
“聪明的”ChatGPT 是否拥有生命?
天天热议:速度是根本!威刚UE800 U盘评测:真正跑满1GB/s
世界新动态:【算法训练营day38】动态规划理论基础 LeetCode509. 斐波那契数 LeetCode70. 爬楼梯 LeetCode746. 使用最小花
报道:韩国刷新世界最低生育率纪录:无人店铺数量持续增长 人工智能需求强
苹果营收4年来首降 库克:裁员是最后手段
低于20万会买吗?特斯拉新款Model 3外形曝光:续航、动力大增
天天热讯:今晚油价或迎年内第二次上调:预计每升上涨0.17元
播报:奔驰销售吐槽:向每位进店客户推荐买新能源 直到客户崩溃或打我
Webpack解析与讲解
全球焦点!微软回应Xbox 360商店关闭:只是搞错了
全球最新:每逢佳节胖三斤 专家提醒:节后运动“甩膘”要注意三点
天天快资讯丨el表达式注入漏洞
环球快报:13倍浓缩:日本隅田川胶囊咖啡1.16元/杯史低
充会员才解封?爱奇艺回应一号三用被封:技术故障 跳转错误页面
《卧龙》天柱山介绍公开:红晶小姐姐美如画!
天天热点!对Intel穷追猛打!AMD Zen4c 128核心上半年杀来
突然暴雷!世界第一辆量产太阳能汽车 黄了
讯息:操作系统的体系结构
天天热头条丨2023年新势力首月销量成绩单:理想最显眼 零跑暴跌
全球新资讯:蔚来大降价超10万?总裁回应:没有 展车最多2.4万优惠
【全球报资讯】比尔·盖茨盛赞ChatGPT:称其“不亚于互联网诞生”
环球视点!女子拍抖音私闯已关闭自然保护区 或处5000元以下罚款
一个手机号搞定!微信正式支持注册小号:生活、工作能分开吗?
世界语言的分布是什么?世界语言难度排行
三星手机怎么截屏图片?三星手机如何防盗?
韩国游戏公司有哪些?韩国游戏公司排名
穿越火线什么时候出的?穿越火线怎么安装?
饱和石灰水是什么意思?饱和石灰水变浑浊的原因是什么?