通过FastCGI Cache实现服务降级

运维 系统运维
关于FastCGI Cache,以前很多朋友已经做过分享,说点与众不同的:虽然使用了缓存,但出于实时性考虑,正常情况下缓存都是被穿透的,只有在出现异常情况的时候才查询。本文总结了通过FastCGI Cache实现服务降级的方法。

在自然界中,很多生物面临生死考验的时候,往往会做出惊人的反应,其中最为大家熟知的当属壁虎,危难关头,与其坐以待毙,不如断尾求生,通过自残来换取活下去的希望。对于互联网项目而言,同样存在着很多生死考验,比如:访问量激增;数据库宕机等等,此时如果没有合理的降级方案,那么结局必然是死路一条。

 

任何问题一旦脱离了实际情况,便失去了讨论的意义。在继续之前,不妨先介绍一下案例的背景情况:一个PHP网站,以读为主,原本躲在CDN后面,运行很稳定,后来新增了很多强调实时性的需求,便去掉了CDN,进而导致系统稳定性受到影响。因为历史包袱重,所以完全废弃以前的架构显得并不现实,解决方案***能够尽可能透明,不能对原有架构造成冲击,最终我选择了通过FastCGI Cache实现服务降级的方案。

关于FastCGI Cache,以前很多朋友已经做过分享,比如:超群莿鸟栖草堂,概念性的东西我就不再赘述了,说点与众不同的:虽然使用了缓存,但出于实时性考虑,正常情况下缓存都是被穿透的,只有在出现异常情况的时候才查询,架构图如下:

Degradation

Degradation

实现的关键点在于通过error_page处理异常,并且完成服务降级:

  1. limit_conn_zone $server_name zone=perserver:1m; 
  2. error_page 500 502 503 504 = @degradation; 
  3. fastcgi_cache_path /tmp 
  4.        levels=1:2 
  5.        keys_zone=degradation:100m 
  6.                    inactive=10d 
  7.                    max_size=10g
  8. upstream php { 
  9.     server 127.0.0.1:9000; 
  10.     server 127.0.0.1:9001; 
  11. server { 
  12.     listen 80; 
  13.     limit_conn perserver 1000; 
  14.     server_name *.xip.io; 
  15.     root /usr/local/www; 
  16.     index index.html index.htm index.php; 
  17.     location / { 
  18.         try_files $uri $uri/ /index.php$is_args$args; 
  19.     } 
  20.     location ~ \.php$ { 
  21.         set $cache_key $request_method://$host$request_uri; 
  22.         set $cache_bypass "1"; 
  23.         if ($arg_degradation = "on") { 
  24.             set $cache_bypass "0"; 
  25.         } 
  26.         try_files $uri =404
  27.         include fastcgi.conf; 
  28.         fastcgi_pass php; 
  29.         fastcgi_intercept_errors on; 
  30.         fastcgi_next_upstream error timeout; 
  31.         fastcgi_cache degradation; 
  32.         fastcgi_cache_lock on; 
  33.         fastcgi_cache_lock_timeout 1s; 
  34.         fastcgi_cache_valid 200 301 302 10h; 
  35.         fastcgi_cache_min_uses 10; 
  36.         fastcgi_cache_use_stale error 
  37.                                 timeout 
  38.                                 invalid_header 
  39.                                 updating 
  40.                                 http_500 
  41.                                 http_503; 
  42.         fastcgi_cache_key $cache_key; 
  43.         fastcgi_cache_bypass $cache_bypass; 
  44.         add_header X-Cache-Status $upstream_cache_status; 
  45.         add_header X-Response-Time $upstream_response_time; 
  46.     } 
  47.     location @degradation { 
  48.         rewrite . $request_uri?degradation=on last; 
  49.     } 

插播一个小技巧:设置域名时用到了xip.io,有了它就不用设置hosts了,方便调试。

代码里用到的都是Nginx缺省包含的功能,我们可以看作是一个通用版,不过对照我们架构图中的目标就会发现:它没有实现全局激活缓存的功能。如何实现呢?最简单的方法就是通过单位时间内出错次数的多少来判断系统健康以否,设置相应的阈值,一旦超过限制就全局激活缓存,通过Lua我们可以实现一个定制版:

  1. lua_shared_dict fault 1m; 
  2.  
  3. limit_conn_zone $server_name zone=perserver:1m; 
  4.  
  5. error_page 500 502 503 504 = @degradation; 
  6.  
  7. fastcgi_cache_path /tmp 
  8.                    levels=1:2 
  9.                    keys_zone=degradation:100m 
  10.                    inactive=10d 
  11.                    max_size=10g
  12.  
  13. upstream php { 
  14.     server 127.0.0.1:9000; 
  15.     server 127.0.0.1:9001; 
  16. init_by_lua ' 
  17.     get_fault_key = function(timestamp) 
  18.         if not timestamp then 
  19.             timestamp = ngx.time() 
  20.         end 
  21.         return os.date("fault:minute:%M", timestamp) 
  22.     end 
  23.     get_fault_num = function(timestamp) 
  24.         local fault = ngx.shared.fault 
  25.         local key = get_fault_key(timestamp) 
  26.         return tonumber(fault:get(key)) or 0 
  27.     end 
  28.     incr_fault_num = function(timestamp) 
  29.         local fault = ngx.shared.fault 
  30.         local key = get_fault_key(timestamp) 
  31.         if not fault:incr(key, 1) then 
  32.             fault:set(key, 1, 600) 
  33.         end 
  34.     end 
  35. '; 
  36. server { 
  37.     listen 80; 
  38.     limit_conn perserver 1000; 
  39.     server_name *.xip.io; 
  40.     root /usr/local/www; 
  41.     index index.html index.htm index.php; 
  42.     location / { 
  43.         rewrite_by_lua ' 
  44.             if ngx.var.arg_degradation then 
  45.                 return ngx.exit(ngx.OK) 
  46.             end 
  47.  
  48.             local ok = true 
  49.  
  50.             for i = 0, 1 do 
  51.                 local num = get_fault_num(ngx.time() - i * 60) 
  52.                 if num > 1000 then 
  53.                     ok = false 
  54.                     break 
  55.                 end 
  56.             end 
  57.            if not ok then 
  58.                 local query = "degradation=on" 
  59.                 if ngx.var.args then 
  60.                     ngxngx.var.args = ngx.var.args .. "&" .. query 
  61.                 else 
  62.                     ngx.var.args = query 
  63.                 end 
  64.             end 
  65.         '; 
  66.         try_files $uri $uri/ /index.php$is_args$args; 
  67.     } 
  68.     location ~ \.php$ { 
  69.         set $cache_key $request_method://$host$request_uri; 
  70.  
  71.         set $cache_bypass "1"; 
  72.         if ($arg_degradation = "on") { 
  73.             set $cache_bypass "0"; 
  74.         } 
  75.         try_files $uri =404
  76.         include fastcgi.conf; 
  77.         fastcgi_pass php; 
  78.         fastcgi_intercept_errors on; 
  79.         fastcgi_next_upstream error timeout; 
  80.         fastcgi_cache degradation; 
  81.         fastcgi_cache_lock on; 
  82.         fastcgi_cache_lock_timeout 1s; 
  83.         fastcgi_cache_valid 200 301 302 10h; 
  84.         fastcgi_cache_min_uses 10; 
  85.         fastcgi_cache_use_stale error 
  86.                                 timeout 
  87.                                 invalid_header 
  88.                                 updating 
  89.                                 http_500 
  90.                                 http_503; 
  91.         fastcgi_cache_key $cache_key; 
  92.         fastcgi_cache_bypass $cache_bypass; 
  93.         add_header X-Cache-Status $upstream_cache_status; 
  94.         add_header X-Response-Time $upstream_response_time; 
  95.     } 
  96.     location @degradation { 
  97.         content_by_lua ' 
  98.             if ngx.var.arg_degradation then 
  99.                 return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) 
  100.             end 
  101.             local res = ngx.location.capture( 
  102.                 ngx.var.request_uri, {args = "degradation=on"
  103.             ) 
  104.             ngx.status = res.status 
  105.             for name, value in pairs(res.header) do 
  106.                 ngx.header[name] = value 
  107.             end 
  108.             ngx.print(res.body) 
  109.             incr_fault_num() 
  110.         '; 
  111.     } 

说明:实际上真实案例中缓存键名的获取逻辑有点复杂,鉴于篇幅所限一切从简。

当系统正常时,运行于动态模式,数据通过PHP-FPM渲染;当系统异常时,全局缓存被激活,运行于静态模式,数据通过缓存渲染。通过测试发现,系统在从正常切换到异常时,因为舍弃了PHP-FPM,所以RPS从一千跃升到一万。这让我想起儿时看圣斗士的情景:每当不死鸟一辉被敌人击倒后,他总能重新站起来,并爆发出更大的能量。

此外需要说明的是:在发生故障的时候,如果出现大量缓存过期的情况,那么由于涉及到缓存的重建,所以依然会和PHP-FPM发生交互行为,这可能会影响性能,此时没有特别好的解决办法,如果Nginx版本够的话,可以考虑激活fastcgi_cache_revalidate,如此一来,PHP-FPM一旦判断系统处于异常情况,那么可以直接返回304实现缓存续期。

通过FastCGI Cache实现服务降级,这是一个***的方案么?非也!它甚至有些丑陋,比如说多台服务器时,会导致大量冗余的缓存,此外磁盘IO也需要注意。虽然这不是一个***的方案,但是它简单,正符合我解决棘手问题时的惯用打法:先用一个土鳖一点的方案缓解问题,再用一个***的方案解决问题。稍后我会考虑使用Memcached,加上一致性哈希来替换FastCGI Cache,实现一个相对***的服务降级方案。

责任编辑:黄丹 来源: 火丁笔记
相关推荐

2013-04-25 10:01:35

Nginx

2018-08-01 14:20:11

微服务架构人工智能

2021-03-16 08:31:59

微服务Sentinel雪崩效应

2017-11-16 15:45:25

服务降级熔断

2017-07-03 09:50:07

Spring Clou微服务架构

2016-12-19 11:33:26

2023-02-27 13:41:04

apt-get软件包

2023-05-05 18:38:33

多级缓存Caffeine开发

2024-01-30 18:10:25

2023-06-20 08:10:00

2023-08-03 08:52:10

缓存数据逐出数据

2012-09-25 11:47:48

FacebookCache服务器

2010-01-15 22:29:11

2014-11-04 10:34:27

JavaCache

2022-10-26 15:22:31

React组件User组件

2013-03-26 10:48:22

创业降级论

2012-07-16 09:45:44

降级论傻蛋

2015-06-10 16:05:26

NginxFastcgi

2010-03-29 16:58:41

Nginx FastC

2011-01-14 16:04:01

Linux集群系统
点赞
收藏

51CTO技术栈公众号