<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          漲薪5K必學(xué)高并發(fā)核心編程,限流原理與實(shí)戰(zhàn),分布式計(jì)數(shù)器限流

          共 6849字,需瀏覽 14分鐘

           ·

          2022-03-02 11:29


          分布式計(jì)數(shù)器限流

          分布式計(jì)算器限流是使用Redis存儲(chǔ)限流關(guān)鍵字key的統(tǒng)計(jì)計(jì)數(shù)。

          這里介紹兩種限流的實(shí)現(xiàn)方案:Nginx Lua分布式計(jì)數(shù)器限流和RedisLua分布式計(jì)數(shù)器限流。

          實(shí)戰(zhàn):Nginx Lua分布式計(jì)數(shù)器限流

          本小節(jié)以對(duì)用戶IP計(jì)數(shù)器限流為例實(shí)現(xiàn)單IP在一定時(shí)間周期(如10秒)內(nèi)只能訪問一定次數(shù)(如10次)的限流功能。由于使用到Redis存儲(chǔ)分布式訪問計(jì)數(shù),通過Nginx Lua編程完成全部功能,因此這里將這種類型的限流稱為Nginx Lua分布式計(jì)數(shù)器限流。

          本小節(jié)的Nginx Lua分布式計(jì)數(shù)器限流案例架構(gòu)如圖9-3所示。

          圖9-3 Nginx Lua分布式計(jì)數(shù)器限流架構(gòu)

          首先介紹限流計(jì)數(shù)器腳本RedisKeyRateLimiter.lua,該腳本負(fù)責(zé)完成訪問計(jì)數(shù)和限流的結(jié)果判斷,其中涉及Redis的存儲(chǔ)訪問,具體的代碼如下:

          local redisExecutor = require("luaScript.redis.RedisOperator");
          --一個(gè)統(tǒng)一的模塊對(duì)象
          local _Module = {}
          _Module.__index = _Module
          --方法:創(chuàng)建一個(gè)新的實(shí)例
          function _Module.new(self, key)
          local object = { red = nil } setmetatable(object, self)
          --創(chuàng)建自定義的redis操作對(duì)象
          local red = redisExecutor:new();
          red:open();
          object.red = red;
          object.key = "count_rate_limit:" .. key;
          return object
          end
          --方法:判斷是否能通過流量控制
          --返回值為true表示通過流量控制,返回值為false表示被限制
          function _Module.acquire(self)
          local redis = self.red;
          local current = redis:getValue(self.key);
          --判斷是否大于限制次數(shù)
          local limited = current and current ~= ngx.null and tonumber(current) > 10; --限流的次數(shù)
          --被限流
          if limited then
          redis:incrValue(self.key);
          return false;
          end
          if not current or current == ngx.null then
          redis:setValue(self.key, 1);
          redis:expire(self.key, 10); --限流的時(shí)間范圍
          else
          redis:incrValue(self.key);
          end
          return true;
          end
          --方法:取得訪問次數(shù),供演示使用
          function _Module.getCount(self)
          local current = self.red:getValue(self.key);
          if current and current ~= ngx.null then
          return tonumber(current);
          end
          return 0;
          end
          --方法:歸還redis連接
          function _Module.close(self)
          self.red:close();
          end
          return _Module
          以上代碼位于練習(xí)工程LuaDemoProject的
          src/luaScript/module/ratelimit/文件夾下,文件名稱為
          RedisKeyRateLimiter.lua。
          然后介紹access_auth_nginx限流腳本,該腳本使用前面定義的
          RedisKeyRateLimiter.lua通用訪問計(jì)算器腳本,完成針對(duì)同一個(gè)IP的限流操
          作,具體的代碼如下:
          ---此腳本的環(huán)境:nginx內(nèi)部
          ---啟動(dòng)調(diào)試
          --local mobdebug = require("luaScript.initial.mobdebug");
          --mobdebug.start();
          --導(dǎo)入自定義的計(jì)數(shù)器模塊
          local RedisKeyRateLimiter = require("luaScript.module.ratelimit.RedisKeyRateLimiter");
          定義出錯(cuò)的
          輸出對(duì)象--定義出錯(cuò)的JSON輸出對(duì)象
          local errorOut = { resp_code = -1, resp_msg = "限流出錯(cuò)", datas = {} };
          --取得用戶的ip
          local shortKey = ngx.var.remote_addr;
          --沒有限流關(guān)鍵字段,提示錯(cuò)誤
          if not shortKey or shortKey == ngx.null then
          errorOut.resp_msg = "shortKey不能為空"
          ngx.say(cjson.encode(errorOut));
          return ;
          end
          --拼接計(jì)數(shù)的redis key
          local key = "ip:" .. shortKey;
          local limiter = RedisKeyRateLimiter:new(key);
          local passed = limiter:acquire();
          --如果通過流量控制
          if passed then
          ngx.var.count = limiter:getCount();
          --注意,在這里直接輸出會(huì)導(dǎo)致content階段的指令被跳過
          --ngx.say( "目前的訪問總數(shù):",limiter:getCount(),"
          ");

          end
          --回收redis連接
          limiter:close();
          --如果沒有流量控制,就終止nginx的處理流程
          if not passed then
          errorOut.resp_msg = "抱歉,被限流了";
          ngx.say(cjson.encode(errorOut));
          ngx.exit(ngx.HTTP_UNAUTHORIZED);
          end
          return ;

          以上代碼位于練習(xí)工程LuaDemoProject的
          src/luaScript/module/ratelimit/文件夾下,文件名稱為access_auth_nginx.lua。access_auth_nginx.lua在拼接計(jì)數(shù)器的key時(shí)使用了Nginx的內(nèi)置變量$remote_addr獲取客戶端的IP地址,最終在Redis存儲(chǔ)訪問計(jì)數(shù)的key的格式如下:

          count_rate_limit:ip:192.168.233.1

          這里的192.168.233.1為筆者本地的測(cè)試IP,存儲(chǔ)在Redis中針對(duì)此IP的限流計(jì)數(shù)結(jié)果如圖9-4所示。

          圖9-4 存儲(chǔ)在Redis中針對(duì)此IP的限流計(jì)數(shù)結(jié)果

          在Nginx的access請(qǐng)求處理階段,使用access_auth_nginx.lua腳本進(jìn)行請(qǐng)求限流的配置代碼如下:

          location = /access/demo/nginx/lua {
          set $count 0;
          access_by_lua_file luaScript/module/ratelimit/access_auth_nginx.lua;
          content_by_lua_block {
          ngx.say( "目前的訪問總數(shù):",ngx.var.count,"
          "
          );
          ngx.say("hello world!");
          }
          }

          以上配置位于練習(xí)工程LuaDemoProject的src/conf/nginxratelimit.conf文件中,在使之生效之前,需要在openresty-start.sh腳本中換上該配置文件,然后重啟Nginx。

          接下來,開始限流自驗(yàn)證。

          上面的代碼中,由于RedisKeyRateLimiter所設(shè)置的限流規(guī)則為單IP在10秒內(nèi)限制訪問10次,所以,在驗(yàn)證的時(shí)候,在瀏覽器中刷新10次之后就會(huì)被限流。在瀏覽器中輸入如下測(cè)試地址:

          http://nginx.server/access/demo/nginx/lua?seckillGoodId=1

          10秒內(nèi)連續(xù)刷新,第6次的輸出如圖9-5所示。

          圖9-5 自驗(yàn)證時(shí)第6次刷新的輸出

          10秒之內(nèi)連續(xù)刷新,發(fā)現(xiàn)第10次之后請(qǐng)求被限流了,說明Lua限流腳本工作是正常的,被限流后的輸出如圖9-6所示。

          圖9-6 自驗(yàn)證時(shí)刷新10次之后的輸出

          以上代碼有兩點(diǎn)缺陷:

          (1)數(shù)據(jù)一致性問題:計(jì)數(shù)器的讀取和自增由兩次Redis遠(yuǎn)程操作完成,如果存在多個(gè)網(wǎng)關(guān)同時(shí)進(jìn)行限流,就可能會(huì)出現(xiàn)數(shù)據(jù)一致性問題。

          (2)性能問題:同一次限流操作需要多次訪問Redis,存在多次網(wǎng)絡(luò)傳輸,大大降低了限流的性能。

          實(shí)戰(zhàn):Redis Lua分布式計(jì)數(shù)器限流

          大家知道,Redis允許將Lua腳本加載到Redis服務(wù)器中執(zhí)行,可以調(diào)用大部分Redis命令,并且Redis保證了腳本的原子性。由于既使用Redis存儲(chǔ)分布式訪問計(jì)數(shù),又通過Redis執(zhí)行限流計(jì)數(shù)器的Lua腳本,因此這里將這種類型的限流稱為RedisLua分布式計(jì)數(shù)器限流。

          本小節(jié)的Redis Lua分布式計(jì)數(shù)器限流案例的架構(gòu)如圖9-7所示。

          圖9-7 Redis Lua分布式計(jì)數(shù)器限流架構(gòu)

          首先來看限流的計(jì)數(shù)器腳本redis_rate_limiter.lua,該腳本負(fù)責(zé)完成訪問計(jì)數(shù)和限流結(jié)果的判斷,其中會(huì)涉及Redis計(jì)數(shù)的存儲(chǔ)訪問。需要注意的是,該腳本將在Redis中加載和執(zhí)行。

          計(jì)數(shù)器腳本redis_rate_limiter.lua的代碼如下:

          ---此腳本的環(huán)境:redis內(nèi)部,不是運(yùn)行在Nginx內(nèi)部
          --返回0表示被限流,返回其他表示統(tǒng)計(jì)的次數(shù)
          local cacheKey = KEYS[1]
          local data = redis.call("incr", cacheKey)
          local count=tonumber(data)
          --首次訪問,設(shè)置過期時(shí)間
          if count == 1 then
          redis.call("expire", cacheKey, 10) --設(shè)置超時(shí)時(shí)間10秒
          end
          if count > 10 then --設(shè)置超過的限制為10人
          表示需要限流 return 0; --0表示需要限流
          end
          --redis.debug(redis.call("get", cacheKey))
          return count;

          以上代碼位于練習(xí)工程LuaDemoProject的
          src/luaScript/module/ratelimit/文件夾下,文件名為redis_rate_limiter.lua。在調(diào)用該腳本之前,首先要將其加載到Redis,并且獲取其加載之后的sha1編碼,以供Nginx上的限流腳本access_auth_evalsha.lua使用。

          將redis_rate_limiter.lua加載到Redis的Linux Shell命令如下:

          [root@localhost ~]#cd /work/develop/LuaDemoProject/src/luaScript/module/ratelimit/
          [root@localhost ratelimit]#/usr/local/redis/bin/redis-cli script load "$(cat redis_rate_limiter.lua)"
          "2c95b6bc3be1aa662cfee3bdbd6f00e8115ac657"

          然后來看access_auth_evalsha.lua限流腳本,該腳本使用Redis的evalsha操作指令,遠(yuǎn)程訪問加載在Redis上的redis_rate_limiter.lua訪問計(jì)算器腳本,完成針對(duì)同一個(gè)IP的限流操作。

          access_auth_evalsha.lua限流腳本的代碼如下:

          ---此腳本的環(huán)境:nginx內(nèi)部
          local RedisKeyRateLimiter = require("luaScript.module.ratelimit.RedisKeyRateLimiter");
          --定義出錯(cuò)的JSON輸出對(duì)象
          local errorOut = { resp_code = -1, resp_msg = "限流出錯(cuò)", datas = {} };
          --讀取get參數(shù)
          local args = ngx.req.get_uri_args()
          --取得用戶的ip
          local shortKey = ngx.var.remote_addr;
          --沒有限流關(guān)鍵字段,提示錯(cuò)誤
          if not shortKey or shortKey == ngx.null then
          errorOut.resp_msg = "shortKey不能為空"
          ngx.say(cjson.encode(errorOut));
          return ;
          end
          --拼接計(jì)數(shù)的redis key
          local key = "count_rate_limit:ip:" .. shortKey;
          local limiter = RedisKeyRateLimiter:new(key);
          local passed = limiter:acquire();
          --如果通過流量控制
          if passed then
          ngx.var.count = limiter:getCount();
          --注意,在這里直接輸出會(huì)導(dǎo)致content階段的指令被跳過
          --ngx.say( "目前的訪問總數(shù):",limiter:getCount(),"
          ");

          end
          --回收redis連接
          limiter:close();
          如果沒有流量控制
          就終止
          的處理流程--如果沒有流量控制,就終止Nginx的處理流程
          if not passed then
          errorOut.resp_msg = "抱歉,被限流了";
          ngx.say(cjson.encode(errorOut));
          ngx.exit(ngx.HTTP_UNAUTHORIZED);
          end
          return ;

          以上代碼位于練習(xí)工程LuaDemoProject的
          src/luaScript/module/ratelimit/文件夾下,文件名為access_auth_evalsha.lua。在Nginx的access請(qǐng)求處理階段,使用access_auth_evalsha.lua腳本進(jìn)行請(qǐng)求限流的配置如下:

           location = /access/demo/evalsha/lua {
          set $count 0;
          access_by_lua_file luaScript/module/ratelimit/access_auth_evalsha.lua;
          content_by_lua_block {
          ngx.say( "目前的訪問總數(shù):",ngx.var.count,"
          "
          );
          ngx.say("hello world!");
          }
          }

          以上配置位于練習(xí)工程LuaDemoProject的
          src/conf/nginx-ratelimit.conf文件中,在使之生效之前需要在openresty-start.sh腳本中換上該配置文件,然后重啟Nginx。

          接下來開始限流自驗(yàn)證。在瀏覽器中訪問以下地址:

          http://nginx.server/access/demo/evalsha/lua

          10秒之內(nèi)連續(xù)刷新,發(fā)現(xiàn)第10次之后請(qǐng)求被限流了,說明Redis內(nèi)部的Lua限流腳本工作是正常的,被限流后的輸出如圖9-8所示。

          圖9-8 自驗(yàn)證時(shí)刷新10次之后的輸出

          通過將Lua腳本加載到Redis執(zhí)行有以下優(yōu)勢(shì):

          (1)減少網(wǎng)絡(luò)開銷:不使用Lua的代碼需要向Redis發(fā)送多次請(qǐng)求,而腳本只需一次即可,減少網(wǎng)絡(luò)傳輸。

          (2)原子操作:Redis將整個(gè)腳本作為一個(gè)原子執(zhí)行,無須擔(dān)心并發(fā),也就無須事務(wù)。

          (3)復(fù)用:只要Redis不重啟,腳本加載之后會(huì)一直緩存在Redis中,其他客戶端可以通過sha1編碼執(zhí)行。

          本文給大家講解的內(nèi)容是高并發(fā)核心編程,限流原理與實(shí)戰(zhàn),分布式計(jì)數(shù)器限流

          1. 下篇文章給大家講解的是高并發(fā)核心編程,限流原理與實(shí)戰(zhàn),Nginx漏桶限流詳解;

          2. 覺得文章不錯(cuò)的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;

          3. 感謝大家的支持!


          本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號(hào)里找我,我等你哦。

          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产日韩精品无码去免费专区国产 | 黄色一级片播放视频 | 成人中文字幕在线视频 | 人人舔人人操人人干 | 黄色片黄色片一级片不卡片 |