We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
根据近来使用 Nginx 缓存的实践,做一个总结,以及阐述了一般的缓存系统实现的关键和问题。
Nginx 缓存是比较传统的单机本地缓存(需要说明的是,根据 KEY 计算缓存路径的方式是一样的,也就是说多个 Nginx 进程是可以共享相同缓存资源的),比较简单,容易理解。但是,通过 Nginx 缓存的使用与理解,能够窥探到业界通用的缓存系统的设计与实现方法。Nginx 会对上游返回的 Response 进行缓存,存放在特定目录下(磁盘),Nginx 会启动一个专门的 Worker 进程对缓存进行管理,周期性的删除过期缓存等。当一个请求进来,首先会判断是否需要使用缓存(proxy_cache_bypass),如果需要使用缓存,则直接将请求代理到上游;否则根据 proxy_cache_key 计算出 KEY(32 位 Hash 值),根据这个值从对应的目录(可设置多级目录)下获取相同名称的资源。需要注意的是,磁盘上缓存的是整个响应,包括响应头和响应体,而且,此时,Nginx 不会再对请求头和响应头进行判断,只是直接读进内存,返回响应到客户端,这点也很重要。实例:
proxy_cache_bypass
proxy_cache_key
Path: /tmp/c/5a/3a1796923cc2b162a5e7f89dc4cf95ac
/tmp/c/5a/3a1796923cc2b162a5e7f89dc4cf95ac
▒q-\▒▒▒▒▒▒▒▒yq-\▒▒go▒ KEY: httphttpbin.org/cache/60 HTTP/1.1 200 OK Connection: close Server: gunicorn/19.9.0 Date: Thu, 03 Jan 2019 02:20:41 GMT Content-Type: application/json Content-Length: 219 Cache-Control: public, max-age=60 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Via: 1.1 vegur { "args": {}, "headers": { "Accept": "*/*", "Connection": "close", "Host": "httpbin.org", "User-Agent": "curl/7.52.1" }, "origin": "103.126.92.86", "url": "http://httpbin.org/cache/60" }
缓存 KEY 的计算是缓存应用中非常关键的部分,一般的自然想到的就是根据请求 URL 来计算,即: $scheme$proxy_host$uri$is_args$args。
$scheme$proxy_host$uri$is_args$args
但是,这会导致几个问题:
$scheme$proxy_host$uri
http_accept_encoding
cookie
user_agent
根据标准的 HTTP 协议,服务端(源站)应该在响应头设置 Vary 字段,来显示指定这些影响缓存的头部字段,缓存系统需要支持这种协商机制,对不同的客户端请求返回不同的资源。但是,Vary 这个字段使用的比较少,甚至很多程序员都不清楚这个字段,而且,很多缓存系统也是不支持这个字段。
Vary
因此,KEY 的计算需要非常谨慎,最好的办法是根据具体的 Host 选择合适的字段来计算,即 proxy_cache_key 可配置化。一般为了简单快速实现,通用的方式主要有:
总之,通用实现的场景下,最重要的是,宁可多缓存几份或;者直接回源,也不能出现请求响应的资源不匹配的情况;
proxy_pass
有些请求响应(大多数动态资源)是不能缓存的,那么 Nginx 怎么会知道哪些响应应该被缓存呢?根据 HTTP 协议, 响应头 Cache-Control 和 Expires 控制缓存失效时间,也即,Nginx 只会缓存这种显式指定了缓存失效时间的响应。
Cache-Control
Expires
当然,在代理层是可以做很多事情的,比如,可以使用 add_header 来添加 Cache-Control 头,使得源站返回的响应能够被缓存,或者设置 proxy_no_cache 来强制不缓存某些响应。
add_header
proxy_no_cache
proxy_cache_purge
缓存系统必不可少的一个功能就是需要支持外部直接刷新的接口,因为很多场景下,需要将被缓存的资源提前失效,这个时候一般是通过 API 接口直接刷新的,也即请求对应的 PURGE 方法。缓存刷新模块看似简单,但是,在大流量的场景下,可能还要支持批量刷新功能,比如大型网站的更新迭代,往往有大量的资源需要更新,CDN 厂商经常会遇到这类需求。要保证缓存能够被快速,准确和稳定的刷新,还是挺有挑战的。一般缓存失效,磁盘上对应的文件并不会立即直接被删除掉,因为读写磁盘代价较大,而且缓存的资源实在太多了,一般先会在内存标记,然后待缓存管理进程(线程)周期性的轮询删除,配置命令 proxy_cache_path 属性 inactive 就是指定删除周期的。
PURGE
proxy_cache_path
inactive
Nginx 中可以使用 proxy_cache_purge 命令来支持 PURGE 请求刷新对应缓存资源。
map $request_method $purge_method { PURGE 1; default 0; } server { ... location / { proxy_pass http://backend; proxy_cache cache_zone; proxy_cache_key $uri; proxy_cache_purge $purge_method; } }
但是,这个命令只有商业版才会有,大部分开发者用的应该都是开源版。
This functionality is available as part of our commercial subscription.
在整个架构中,缓存系统往往是比较容易引起问题的模块,除了自身的问题外,因为缓存的使用还有一个与上下游协商的过程,也可能出现外部服务不规范导致的问题,一般比较多的问题已就是缓存未及时刷新,导致客户端使用了旧资源,以及 404 等错误状态未启用缓存(或过滤)导致缓存穿透,缓存刷新,过期不合理,导致缓存雪崩等。一般的使用场景直接使用 Nginx 自带的缓存就够了,但是,对于比较复杂的场景,要自建缓存系统才行,在 CDN 中缓存系统尤为重要。
以下,是我在项目中的使用实例:
proxy_cache_path /tmp levels=1:2 keys_zone=mcache:5m max_size=5g inactive=60m use_temp_path=off; ... location / { proxy_cache_key $scheme$proxy_host$uri$is_args$args$http_accept_encoding; # compatible with clients that do not support gzip. proxy_no_cache $cookie_nocache $arg_nocache$arg_comment; proxy_no_cache $http_pragma $http_authorization; proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment; proxy_cache_bypass $http_pragma $http_authorization; proxy_cache_bypass $http_cache_control; proxy_cache mcahe; proxy_pass http://test.com; proxy_set_header Host "myhost.com"; add_header X-Cache $upstream_cache_status; }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Nginx 缓存实践与实现讨论
根据近来使用 Nginx 缓存的实践,做一个总结,以及阐述了一般的缓存系统实现的关键和问题。
Nginx 缓存是比较传统的单机本地缓存(需要说明的是,根据 KEY 计算缓存路径的方式是一样的,也就是说多个 Nginx 进程是可以共享相同缓存资源的),比较简单,容易理解。但是,通过 Nginx 缓存的使用与理解,能够窥探到业界通用的缓存系统的设计与实现方法。Nginx 会对上游返回的 Response 进行缓存,存放在特定目录下(磁盘),Nginx 会启动一个专门的 Worker 进程对缓存进行管理,周期性的删除过期缓存等。当一个请求进来,首先会判断是否需要使用缓存(
proxy_cache_bypass
),如果需要使用缓存,则直接将请求代理到上游;否则根据proxy_cache_key
计算出 KEY(32 位 Hash 值),根据这个值从对应的目录(可设置多级目录)下获取相同名称的资源。需要注意的是,磁盘上缓存的是整个响应,包括响应头和响应体,而且,此时,Nginx 不会再对请求头和响应头进行判断,只是直接读进内存,返回响应到客户端,这点也很重要。实例:Path:
/tmp/c/5a/3a1796923cc2b162a5e7f89dc4cf95ac
缓存 KEY 的计算是缓存应用中非常关键的部分,一般的自然想到的就是根据请求 URL 来计算,即:
$scheme$proxy_host$uri$is_args$args
。但是,这会导致几个问题:
$scheme$proxy_host$uri
就够了;http_accept_encoding
,cookie
,user_agent
等等)的不同,返回不同的资源,因此计算的时候需要加入这些头部字段,否则会返回错误的资源,可能会导致比较严重的问题;根据标准的 HTTP 协议,服务端(源站)应该在响应头设置
Vary
字段,来显示指定这些影响缓存的头部字段,缓存系统需要支持这种协商机制,对不同的客户端请求返回不同的资源。但是,Vary
这个字段使用的比较少,甚至很多程序员都不清楚这个字段,而且,很多缓存系统也是不支持这个字段。因此,KEY 的计算需要非常谨慎,最好的办法是根据具体的 Host 选择合适的字段来计算,即
proxy_cache_key
可配置化。一般为了简单快速实现,通用的方式主要有:http_accept_encoding
一定要加入 KEY,否则会导致将压缩的资源缓存,响应给不接受压缩资源的客户端,出现乱码的情况。虽然现在大部分浏览器都是支持解压的。总之,通用实现的场景下,最重要的是,宁可多缓存几份或;者直接回源,也不能出现请求响应的资源不匹配的情况;
有些请求响应(大多数动态资源)是不能缓存的,那么 Nginx 怎么会知道哪些响应应该被缓存呢?根据 HTTP 协议, 响应头
Cache-Control
和Expires
控制缓存失效时间,也即,Nginx 只会缓存这种显式指定了缓存失效时间的响应。当然,在代理层是可以做很多事情的,比如,可以使用
add_header
来添加Cache-Control
头,使得源站返回的响应能够被缓存,或者设置proxy_no_cache
来强制不缓存某些响应。缓存系统必不可少的一个功能就是需要支持外部直接刷新的接口,因为很多场景下,需要将被缓存的资源提前失效,这个时候一般是通过 API 接口直接刷新的,也即请求对应的
PURGE
方法。缓存刷新模块看似简单,但是,在大流量的场景下,可能还要支持批量刷新功能,比如大型网站的更新迭代,往往有大量的资源需要更新,CDN 厂商经常会遇到这类需求。要保证缓存能够被快速,准确和稳定的刷新,还是挺有挑战的。一般缓存失效,磁盘上对应的文件并不会立即直接被删除掉,因为读写磁盘代价较大,而且缓存的资源实在太多了,一般先会在内存标记,然后待缓存管理进程(线程)周期性的轮询删除,配置命令proxy_cache_path
属性inactive
就是指定删除周期的。Nginx 中可以使用
proxy_cache_purge
命令来支持PURGE
请求刷新对应缓存资源。但是,这个命令只有商业版才会有,大部分开发者用的应该都是开源版。
小结
在整个架构中,缓存系统往往是比较容易引起问题的模块,除了自身的问题外,因为缓存的使用还有一个与上下游协商的过程,也可能出现外部服务不规范导致的问题,一般比较多的问题已就是缓存未及时刷新,导致客户端使用了旧资源,以及 404 等错误状态未启用缓存(或过滤)导致缓存穿透,缓存刷新,过期不合理,导致缓存雪崩等。一般的使用场景直接使用 Nginx 自带的缓存就够了,但是,对于比较复杂的场景,要自建缓存系统才行,在 CDN 中缓存系统尤为重要。
以下,是我在项目中的使用实例:
The text was updated successfully, but these errors were encountered: