假设我们的微服务+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) ---直接去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) -- 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) ngx.log(ngx.ERR, "failed to limit req: ", err) return ngx.exit(500) --去抄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 ngx.exec('/goods_list_advert_from_data')

    报错提示:

    解决:https://blog.csdn.net/u010711495/article/details/111053164
    因为没有开启共享内存区域导致的,所以在nginx中要开起来lua_shared_dict sdata 10m; 这里的sdata为共享内存的名字

    测试结果: