nginx+lua+redis实现降级

前言

商城或web站点的用户访问量出乎意料地增加了很多,超出了系统的负载能力, 系统有些扛不住,继而导致注
册,下单,支付什么的全部在绕圈卡住,继而导致公司业务损失了不少用户和订单。。


一、引子

面对一大波访问量出乎意料地涌入,超出了系统正常的负载范围,我们可以采用降级来应对,何谓降级?就是将不重要的服务和功能采用屏蔽,或降低实时性,或延迟处理,等等方式,最终目的是保证核心服务可用。

二、什么是降级, 为什么降级,降级的场景?

降级的最终目的是保证核心服务的高可用。过程就是丢卒保帅,有些服务是无法降级的,比如支付。
当我们的服务器压力剧增为了保证核心功能的可用性 ,而选择性的降低一些功能的可用性,或者直接关闭该功能。
这就是典型的丢车保帅了。 就比如贴吧类型的网站,当服务器吃不消的时候,可以选择把发帖功能关闭,注册功能关闭,改密码,改头像这些都关了,为了确保登录和浏览帖子这种核心的功能。
降级的原理:就是降低次要功能的可用性实用性,增加核心功能的高可用性。

降级的实现原理是多样多样的。
利用一个降级开关,以这个开关为判断依据,切换数据的获取方式,比如当mysql负载高的时候,可以从mysql切换到redis,比如从redis切换到静态文件,比如从错误频发的新版本切换到老版本等等。 这个开关是根据现状来配置的,比如当新版本错误频发的时候,我们可以配置这个开关为从老版本获取数据。

三、降级的种类

  1. 根据降级的开关位置:分为服务代码降级和开关前置降级
    代码降级就是利用代码控制,这种方式比前置降级要low并不推荐
    前置降级是把降级开关放到http请求链路层的上游,降低链路层消耗。比如提升到nginx,甚至可以提升到前
    端,当提升到前端,后端访问压力接近于0

拓展:【如何提升到前端】
可以通过一个从服务器获取的js脚本进行控制。

  1. 根据读写:分为读降级和写降级。

读降级,比如,读取动态数据,降级为读取静态数据。 写降级,
比如,写入mysql,降级为写入消息队列, 等高峰期过后,在从队列写入mys

  1. 根据降级的性质:分为返回内容降级,限流降级,限速降级。

返回内容降级,比如,返回实时数据,降级为返回兜底数据 限流降级,
比如,1000个请求,我只接受500个。 这么做也是无奈之下选择,要不然系统崩了 谁都访问不了 限速降级,
比如,对于那些访问过于频繁的ip进行限速

拓展:【限流限速】
nginx自带限流限速,比如ngx_http_limit_req_module和ngx_http_limit_conn_module 。但是这类模块只是提供了在nginx配置文件中进行简单的参数配置。 如果想更加灵活和功能更多,可以编写自己的lua代码进行控制,而不是用现成的模块进行配置文件修 改。
4.根据降级的维护特点:分为手动降级和自动降级
手动降级,是人为看到系统负载异常后,手动调整降级
自动降级,是系统监测到异常后,自动降级,自动降级虽然更加智能,但有时候自动脚本可能会干一些超乎预 料的事情。

四、 业务分析

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

五、 设计分析

第一种:从数据
第二种:从微服务
第三种:从缓存
第四种:从文件
第五种:从前端返回
第六种:直接返回空
前5种获取广告推荐数据的方式,从上而下,对系统带来的性能的损耗逐渐降低,最底下的第5种,几乎对系统没有损耗,但是数据实时性也是自上而下逐渐降低的,从需求上讲,我们更喜欢实时从数据库读取,但为了降低性能损耗,在系统负载重的时候, 我们可能不得不降级为从文件或者从缓存中读取。这个降级是通过一个开关可以控制的。

六、 降级的线路图

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

这个降级开关是个什么东西,结构如何呢?
实际上是一条条的redis数据, 每条数据就是一个开关。

这条开关数据的结构是这么设计的:

key:url链接中的请求地址。
value:记录这个请求从哪里读取数据,也就是配置从哪里查询数据。

七、实现降级

1. 配置中心

配置中心是一个后台,这个配置中心大家根据各自需求自己去实现,这里我们采用手工操作redis的方式,实际上是
通过后台操作的redis。

2. nginx+lua+redis实现降级

nginx配置文件/etc/nginx/nginx.conf

user nobody;
worker_processes  1;
events {
lua降级代码:/etc/nginx/lua/goods_list_advert.lua
 worker_connections  1024;
}
http {
 lua_package_path "/usr/share/lua/5.1/lua-resty-redis/lib/?.lua;;/usr/share/lua/5.1/lua-
resty-redis-cluster/lib/resty‘7/?.lua;;";
 lua_package_cpath "/usr/share/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    80;
   server_name  127.0.0.1;
   server_name  192.168.232.100;
    #获取广告推荐数据
 location /goods_list_advert {
   default_type 'application/x-javascript;charset=utf-8';
     content_by_lua_file /etc/nginx/lua/goods_list_advert.lua;
   }
    #从服务层+mysql获取数据
   location /goods_list_advert_from_data {
     default_type 'application/x-javascript;charset=utf-8';
     content_by_lua '
       ngx.say("从服务层+mysql获取数据")
      ';
   }
 }
}

lua降级代码:/etc/nginx/lua/goods_list_advert.lua


--获取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--------------------

local redis = require("resty.redis");
--local redis = require "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)
    return close_redis(redis_instance);
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 = "/etc/nginx/html/goods_list_advert.json"
    local f = io.open(file, "rb")
    local content = f:read("*all")
    f:close()
    ngx.print(content)

--当开关是要停掉数据获取时--------------------
elseif "SHUT_DOWN" == switch then

    ngx.say('no data')
end

八、验证降级

验证1 设置为从服务和数据库读取

[root@101 redis-5.0.8]# redis-cli
127.0.0.1:6379> set level_goods_list_advert FROM_DATA

发送请求,发现广告推荐请求获取到了微服务提供的数据

用postman:http://127.0.0.1:6379/get_goods_List

返回数据:[{“name”:[“bingwoo”]}]

验证2 设置为从缓存读取

127.0.0.1:6379> set level_goods_list_advert FROM_CACHE

发送请求,发现广告推荐请求获取到了微服务提供的数据

用postman:http://127.0.0.1:6379/get_goods_List

返回数据:redis_data

验证3 设置为从静态文件读取

127.0.0.1:6379> set level_goods_list_advert FROM_STATIC

发送请求,发现广告推荐请求获取到了微服务提供的数据

用postman:http://127.0.0.1:6379/get_goods_List

返回数据:
[
{
“goods_id”:“1”,
“goods_name”:“测试1”,
},
{
“goods_id”:“2”,
“goods_name”:“测试2”,
}
]
验证4 设置为从缓存读取

127.0.0.1:6379> set level_goods_list_advert FROM_CACHE

发送请求,发现广告推荐请求获取到了微服务提供的数据

用postman:http://127.0.0.1:6379/get_goods_List

返回数据:null

九、自动降级

原理:

采用nginx+lua+redis对错误返回进行统计,当统计到的错误次数达到一定数值时,lua会判断这个数值, 然后会在/etc/nginx/lua/goods_list_advert.lua中添加如下代码:

--判断错误的响应,并进行计数, 后续便可以参考这个数值进行降级
if tonumber(ngx.var.status) == 200 then
  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("error_count_goods_list_advert")
  --得到的数据为空处理
  if error_count == ngx.null then
    error_count = 0
  end
  error_count = error_count + 1
  --统计错误次数到error_count_goods_list_advert
  local resp,err = redis_instance:set("error_count_goods_list_advert",error_count)
  if not resp then
    ngx.say("set msg error : ",err)
    return close_redis(redis_instance)
  end
end

当我们有意关掉一些服务让状态码不为200时,
可以看到redis中error_count_goods_list_advert的值在加1:

127.0.0.1:6379> get error_count_goods_list_advert “1”

接下来,你就可以在lua中添加判断, 比如设置一个阈值为100, 当error_count_goods_list_advert的值达到100时(也就是错误返回达到100时),你就可以降级为请求另外一个版本(老版本程序)

你可能感兴趣的:(降级,lua,redis,nginx)