服务自动降级nginx+lua
接上一篇:https://www.cnblogs.com/bogiang/p/15102643.html
本篇讲的是自动降级及实操
1.自动降级
自动降级,使服务治理变得更加自动化,减少人力开支
根据高并发程度(qps),自动选择降级方式。
比如:
假设我们的微服务+mysql系统, 每秒最多每秒100个并发,
每秒100个并发的时候,直接从微服务+mysql获取数据。
每秒200个的时候,后面的100个自动降级为等待1秒后再从微服务+mysql获取数据。
每秒500个的时候,后面的300个自动降级为用nginx从redis获取数据。
每秒1000个的时候,后面的500个自动降级为用nginx从静态文件获取数据。
每秒超过1000个的时候, 1000个往后的不予返回数据。
上面举例详解:
数据实时性和质量: (微服务+mysql) > redis缓存 > 静态文件
所以,我们优先顺序为:(微服务+mysql) > redis缓存 > 静态文件
性能损耗:静态文件 > redis缓存 > (微服务+mysql)
所以,我们负载量分配为:静态文件 > redis缓存 > (微服务+mysql)
1.1为什么自动降级
- 自动降级,使得服务治理变得自动化,减少人力开支。
- 相比人工降级,自动降级反应更加及时,能够微秒级别的做出反应。
1.2自动降级场景
高并发场景下,为了防止服务器奔溃,我们采用自动降级
1.3原理
1.3.1漏桶原理
漏桶原理详解:
水龙头: 水龙头代表外界访问,水龙头的水流量时而大,时而小,很大的时候就代表高并发。也就是说水流量代表请求量
出水水滴:出水水滴大小代表服务器处理的请求量,如上图可见,并不是你来多少水,我就立马处理多少水,而是把水先放到漏桶里,漏孔的出水速度(处理速度),不会因为水龙头调整阈值而增加。也就是说, 无论外界请求是多少,我服务器始终安装自定的出水速度在处理,有效地防止高并发场景下服务器处理过多的请求而被跑死。
桶:桶代表暂存的请求量,把请求先不处理,先放到桶里面,排水孔那里一个个处理。相当于是一个请求缓冲。
如上所示,不管外界放水多少进来,出水总是稳定的,也就是说,我们系统每秒处理的请求量,是一个比较稳定的值,不会因为访问量大,系统一下子处理过多请求而导致系统崩溃或宕机。
漏桶原理用(nginx的限流模块)可以实现, 但是官方的模块,仅仅能实现限流降级,不能进行二次开发。
- 并不能解决返回内容降级。就是上面说的,不能更加并发量在mysql,redis,静态文件中自由切换降级方式。
- 不能添加额外的需求,而用nginx+lua的话,可以把自己额外的想法和需求用lua代码去实现。也就是支持二次开发的意思。
1.3.2 具有多个漏孔的漏桶原理(nginx+lua实现)
为了解决nginx自带限流模块漏桶的不完美,我们要实现的是有n个漏孔的漏桶:
一个漏孔是请求微服务+mysql,
1个漏孔是从nosql缓存中获得数据,
1个漏孔是从静态文件中获得数据,
大大增加漏桶的容量。
上面这样的漏桶, 不但出水量增加, 而且桶的容量也大大增加。
实现这样的漏桶, 我们采用下面这样的设计:
1.4 设计分析
我们把上面那张图稍加改造,就生成了我们下图中的右图。
如上图所示,
改造前:
左边是普通的nginx漏桶原理, 保证对微服务+mysql是平缓的,我们不能把桶的容量设得过大。超出桶容量的请求将被拒绝。由于桶很小, 很多请求会被拒绝掉,用户体验非常不好。服务器抗并发能力也并不高。
改造后:
右边是nginx+lua实现的多级缓存降级,可以在多种降级方案(mysql,nosql,静态文件)中自由切换,配备多个出水口,这样桶的容量和每秒出水量都增加了不少。
1.5 实现
到git下载自动降级的lua-resty-limit-traffic
模块
git clone https://github.com/openresty/lua-resty-limit-traffic
或 百度云
链接:https://pan.baidu.com/s/1z7f5CKE7C6m8eu_OAB7Kmg
提取码:wwai
自动降级代码:
nginx代码:
user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
lua_shared_dict my_lua_cache 128m;
lua_package_path "/www/wwwroot/swoole/lua/5.1/lua-resty-limit-traffic-master/lib/?.lua;/www/wwwroot/swoole/lua/5.1/lua-resty-redis/lib/?.lua;;/www/wwwroot/swoole/lua/5.1/lua-resty-redis-cluster/lib/resty/?.lua;;";
lua_package_cpath "/www/wwwroot/swoole/lua/5.1/lua-resty-redis-cluster/lib/libredis_slot.so;;";
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 7999;
server_name localhost;
#获取广告推荐数据 goods_list_advert_from_data
location /goods_list_advert {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua_file /www/wwwroot/lua/goods_list_advert.lua;
}
#自动降级
location /auto_demotion {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua_file /www/wwwroot/lua/auto_demotion.lua;
}
location /goods_list_advert_from_data {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua '
ngx.say("从服务器mysql获取数据")
';
}
}
}
lua文件:auto_demotion.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by bogiang.
--- DateTime: 2021/8/5 16:18
---
local redis = require("resty.redis");
-- 创建一个redis对象实例。在失败,返回nil和描述错误的字符串的情况下
local redis_instance = redis:new();
--设置后续操作的超时(以毫秒为单位)保护,包括connect方法
redis_instance:set_timeout(1000)
--建立连接
local ip = '127.0.0.1'
local port = 6379
--尝试连接到redis服务器正在侦听的远程主机和端口
local ok,err = redis_instance:connect(ip,port)
if not ok then
ngx.say("connect redis error : ",err)
end
---直接去req.md 复制一下代码做修改
--加载nginx-lua 限流模块
--local limit_req = require ('resty.limit.req')
local limit_req = require ('resty.limit.req')
-- 这里设置rate=10个请求/每秒,漏桶桶容量设置为100个请求
-- 因为模块中控制粒度为毫秒级别,所以可以做到毫秒级别的平滑处理
local lim, err = limit_req.new("my_lua_cache", 10, 100)
if not lim then
ngx.log(ngx.ERR,"failed to instantiate a resty.limit.req object: ", err)
return ngx.exit(500)
end
-- the following call must be per-request.
-- here we use the remote (IP) address as the limiting key
-- 通过ip来做key
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
ngx.say('超过的请求 ..... 溢出来了!!')
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
--去抄goods_list_advert.lua的代码
-- 0-20 的微服务+mysql 20-10/10 0-1
-- 21-50 的redis获取 1-4
-- 51-100 静态文件 4-xxx
if (delay>0 and delay<=1) then
ngx.sleep(delay)
ngx.exec('/goods_list_advert_from_data')
return
elseif (delay>1 and delay<=4) then
local resp, err = redis_instance:get("nihao")
ngx.say(resp)
return
elseif (delay>4) then
ngx.header.content_type="application/x-javascript;charset=utf-8"
local file = "/tmp/goods_list_advert.json"
local f =io.open(file)
local content = f:read("*all")
f:close()
ngx.print(content)
return
end
ngx.exec('/goods_list_advert_from_data')
报错提示:
解决:https://blog.csdn.net/u010711495/article/details/111053164
因为没有开启共享内存区域导致的,所以在nginx中要开起来lua_shared_dict sdata 10m;
这里的sdata为共享内存的名字
测试结果: