服务降级 nginx+lua
服务降级是什么
服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。过程就是丢卒保帅,有些服务是无法降级的,比如支付。
为什么要服务降级
当我们的服务器压力剧增为了保证核心功能的可用性 ,而选择性的降低一些功能的可用性,或者直接关闭该功能。这就是典型的丢车保帅了。 就比如贴吧类型的网站,当服务器吃不消的时候,可以选择把发帖功能关闭,注册功能关闭,改密码,改头像这些都关了,为了确保登录和浏览帖子这种核心的功能
降级的原理:就是降低次要功能的可用性实用性,增加核心功能的高可用性。
怎么降级
降级实现过程原理:利用一个降级开关,以这个开关为判断依据,切换数据的获取方式,比如当mysql负载高的时候,可以从mysql切换到redis,比如从redis切换到静态文件,比如从错误频发的新版本切换到老版本等等。 这个开关是根据现状来配置的,比如当新版本错误频发的时候,我们可以配置这个开关为从老版本获取数据。
1.降级的种类
1.1 根据降级的开关位置:分为服务代码降级、前置降级
代码降级就是利用代码控制,这种方式比前置降级效果差,并不推荐
前置降级是把降级开关放到了http请求链路层的上游,降低链路层消耗。比如提升到nginx,甚至可以提升到前端。当提升到前端,后端访问压力接近于0
可以通过一个服务器获取js脚本进行控制
3.2根据读写:分为读降级、写降级
读降级:比如,读取动态数据改为读取缓存、静态数据
写降级:比如,写入MySQL,降级为写入消息队列,等高峰期过后,再从队列中写入MySQL
3.2根据降级的性质:分为返回内容降级、限流降级、限速降级
返回内容降级:比如,返回实时数据,降级为返回兜底数据
限流降级:比如,1000个请求,降级为接受500个请求
限速降级:比如,将访问频繁的ip进行限速
限流降级、限速降级可以使用nginx的模块
ngx_http_limit_req_module和ngx_http_limit_conn_module
3.2根据降级的维护特点:分为手动降级、自动降级
手动降级:人为看到系统负载异常后,手动调整降级
自动降级:是系统监测到异常后,自动降级,自动降级虽然更加智能,但有时候自动脚本可能会干一些超乎预料的事情
实操
广告推荐模块业务如下:

大家知道,广告推荐模块的特点:
1 是要经过对数据模型进行大量分析,并结合用户刚刚的浏览记录,计算出用户喜欢喜欢什么商品,然后给他打什么广告,运算量相当大。
2 是广告推荐模块不是商城的核心模块,没有了这个模块, 买家照样可以完成商品的购买。
总结上面两点, 我们可以在商城负载过高时,对广告推荐模块进行降级,让它只从缓存或静态文件中读取数据,或者干脆nginx不返回任何数据给它。
设计分析:

需要降级的接口有很多,一般是和产品以及市场一起确定的,依据功能的重要程度, 不重要的功能就可以为它配置降级,我们这里挑选广告推荐进行实战
广告推荐模块分析:

降级的线路图

详解:
1 降级配置中心, 用于统一管理所有的微服务的降级开关, 该中心是单独的服务器,和微服务服务器集群是分开的
2 每个微服务都有一个降级开关。
开关前置:

这个降级开关是个什么东西,结构如何呢?
实际上是一条条的redis数据, 每条数据就是一个开关。
这条开关数据的结构是这么设计的:
key:url链接中的请求地址。
value:记录这个请求从哪里读取数据,也就是配置从哪里查询数据
实现降级

nginx+lua+redis实现降级
lua-redis扩展下载:
链接:https://pan.baidu.com/s/1RXzcTpd7bd9Dfr_uqgjRFw
提取码:qqai
nginx配置文件 /nginx.conf
user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
lua_package_path "/www/wwwroot/swoole/lua/5.1/lua-resty-redis/lib/?.lua;;/www/wwwroot/swoole/lua/5.1/lua-resty-redis-cluster/lib/resty‘7/?.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 /goods_list_advert_from_data {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua '
ngx.say("从服务器mysql获取数据")
';
}
# location /lua_redis {
# content_by_lua_file /etc/nginx/lua/lua_redis.lua;
# }
# #这次的操作,主要是加入了下面这几行代码
# location /lua_redis_sku_num {
# #将要执行的lua操作的redis库存代码封装在/etc/nginx/lua/lua_redis.lua中
#
# content_by_lua_file /etc/nginx/lua/lua_redis_sku_num.lua;
# }
}
}
lua 降级代码 /www/wwwroot/lua/goods_list_advert.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by bogiang.
--- DateTime: 2021/8/4 16:38
---
local redis = require("resty.redis");
--获取get 或post 参数--
local request_method = ngx.var.request_method
local args = nil
local param = nil
--获取参数值
if "GET" == request_method then
args=ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args=ngx.req.get_post_args()
end
sku_id=args["sku_id"]
--关闭redis的函数--------------------
local function close_redis(redis_instance)
if not redis_instance then
return
end
local ok,err = redis_instance:close();
if not ok then
ngx.say("close redis error : ",err);
end
end
-- 创建一个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
--从redis里面读取开关--------------------
local key = "level_goods_list_advert"
local switch, err = redis_instance:get(key)
if not switch then
ngx.say("get msg error : ", err)
return close_redis(redis_instance)
end
--得到的开关为空处理--------------------
if switch==ngx.null then
switch="FROM_DATA" --比如默认值
end
if "FROM_DATA" == switch then
ngx.exec('/goods_list_advert_from_data')
elseif "FROM_CACHE" == switch then
local resp, err = redis_instance:get("nihao")
ngx.say(resp)
elseif "FROM_STATIC" == switch 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)
elseif "SHUT_DOWN" == switch then
ngx.say('no data')
elseif "NIMABI" == switch then
ngx.say('error')
ngx.var.status="400"
end
--判断错误的响应,并进行计数, 后续便可以参考这个数值进行降级
if tonumber(ngx.var.status) == 200 then
local count_key="error_count_goods_list_advert"
ngx.say(ngx.var.status)
ngx.log(ngx.ERR,"upstream reponse status is " .. ngx.var.status .. ",please notice it")
local error_count,err = redis_instance:get(count_key)
if error_count == ngx.null then
error_count=0
end
error_count = error_count+1
local resp,err = redis_instance:set(count_key,error_count)
if not resp then
ngx.say("set msg error : ",err)
return close_redis(redis_instance)
end
end
效果



浙公网安备 33010602011771号