1. 程式人生 > >【高併發】億級流量場景下如何實現分散式限流?看完我徹底懂了!!(文末有福利)

【高併發】億級流量場景下如何實現分散式限流?看完我徹底懂了!!(文末有福利)

## 寫在前面 > 在網際網路應用中,高併發系統會面臨一個重大的挑戰,那就是大量流高併發訪問,比如:天貓的雙十一、京東618、秒殺、搶購促銷等,這些都是典型的大流量高併發場景。關於秒殺,小夥伴們可以參見我的另一篇文章《[【高併發】高併發秒殺系統架構解密,不是所有的秒殺都是秒殺!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247484357&idx=1&sn=23e6e38143704db0fa4588186b534e13&chksm=cee51c08f992951e5b883c55b788588f9cbc822e41694b5b4a334ea5d2dc0ae62a5d64e39dc2&token=1388808518&lang=zh_CN#rd)》 > > 關於【冰河技術】微信公眾號,解鎖更多【高併發】專題文章。 > > 注意:由於原文篇幅比較長,所以被拆分為:理論、演算法、實戰(HTTP介面實戰+分散式限流實戰)三大部分。 > >理論篇:《[【高併發】如何實現億級流量下的分散式限流?這些理論你必須掌握!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247485706&idx=1&sn=c7d71c0c6b9b15c3b330766f1083e29c&chksm=cee516c7f9929fd170ce636a63fc3764d5ef62eb7ef67a7601664d3d56b0d04c8759e666bc87&token=378924601&lang=zh_CN#rd)》 > >演算法篇:《[【高併發】如何實現億級流量下的分散式限流?這些演算法你必須掌握!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247485719&idx=1&sn=8659791a07a55ae4b646679846d0264f&chksm=cee516daf9929fcc33961276715980832d0b2d875cf563121052592141f132dfa31bbd669ebf&token=378924601&lang=zh_CN#rd)》 > >專案原始碼已提交到github:https://github.com/sunshinelyz/mykit-ratelimiter 本文是在《[【高併發】億級流量場景下如何為HTTP介面限流?看完我懂了!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247485730&idx=1&sn=dd48c84389aae92f0126f1c815f71d20&chksm=cee516eff9929ff932ae3d53928e227492afff5149b34351e2060e0b4d7d6124063cacb52799&token=430998188&lang=zh_CN#rd)》一文的基礎上進行實現,有關專案的搭建可參見《[【高併發】億級流量場景下如何為HTTP介面限流?看完我懂了!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247485730&idx=1&sn=dd48c84389aae92f0126f1c815f71d20&chksm=cee516eff9929ff932ae3d53928e227492afff5149b34351e2060e0b4d7d6124063cacb52799&token=430998188&lang=zh_CN#rd)》一文的內容。小夥伴們可以關注【冰河技術】微信公眾號來閱讀上述文章。 前面介紹的限流方案有一個缺陷就是:它不是全域性的,不是分散式的,無法很好的應對分散式場景下的大流量衝擊。那麼,接下來,我們就介紹下如何實現億級流量下的分散式限流。 分散式限流的關鍵就是需要將限流服務做成全域性的,統一的。可以採用Redis+Lua技術實現,通過這種技術可以實現高併發和高效能的限流。 Lua是一種輕量小巧的指令碼程式語言,用標準的C語言編寫的開源指令碼,其設計的目的是為了嵌入到應用程式中,為應用程式提供靈活的擴充套件和定製功能。 ## Redis+Lua指令碼實現分散式限流思路 我們可以使用Redia+Lua指令碼的方式來對我們的分散式系統進行統一的全侷限流,Redis+Lua實現的Lua指令碼: ```lua local key = KEYS[1] --限流KEY(一秒一個) local limit = tonumber(ARGV[1]) --限流大小 local current = tonumber(redis.call('get', key) or "0") if current + 1 > limit then --如果超出限流大小 return 0 else --請求數+1,並設定2秒過期 redis.call("INCRBY", key, "1") redis.call("expire", key "2") return 1 end ``` 我們可以按照如下的思路來理解上述Lua指令碼程式碼。 (1)在Lua指令碼中,有兩個全域性變數,用來接收Redis應用端傳遞的鍵和其他引數,分別為:KEYS、ARGV; (2)在應用端傳遞KEYS時是一個數組列表,在Lua指令碼中通過索引下標方式獲取陣列內的值。 (3)在應用端傳遞ARGV時引數比較靈活,可以是一個或多個獨立的引數,但對應到Lua指令碼中統一用ARGV這個陣列接收,獲取方式也是通過陣列下標獲取。 (4)以上操作是在一個Lua指令碼中,又因為我當前使用的是Redis 5.0版本(Redis 6.0支援多執行緒),執行的請求是單執行緒的,因此,Redis+Lua的處理方式是執行緒安全的,並且具有原子性。 這裡,需要注意一個知識點,那就是原子性操作:如果一個操作時不可分割的,是多執行緒安全的,我們就稱為原子性操作。 接下來,我們可以使用如下Java程式碼來判斷是否需要限流。 ```java //List設定Lua的KEYS[1] String key = "ip:" + System.currentTimeMillis() / 10