只有光頭才能變強。文本已收錄至個人GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3yjava
以前在學習的時候也接觸不到高併發/大流量這種東西,因此限流固然是沒接觸過的了。在看公司項目的時候,發現有用到限流(RateLimiter),順帶了解一波。git
爲啥要限流,相信就不用我多說了。github
在代碼世界上,限流有兩種比較常見的算法:redis
好比,如今我有一個桶子,綠色那塊是我能裝水的容量,若是超過我能裝下的容量,再往桶子裏邊倒水,就會溢出來(限流):算法
咱們目前能夠知道的是:segmentfault
OK,如今咱們在桶子裏挖個洞,讓水能夠從洞子裏邊流出來:服務器
桶子的洞口的大小是固定的,因此水從洞口流出來的速率也是固定的。網絡
因此總結下來算法所需的參數就兩個:併發
漏桶算法有兩種實現:分佈式
通過上面的分析咱們就知道:
漏桶算法能夠 平滑網絡上的突發流量(由於漏水的速率是固定的)
如今我有另一個桶子,這個桶子不用來裝水,用來裝令牌:
令牌會必定的速率扔進桶子裏邊,好比我1秒扔10個令牌進桶子:
桶子能裝令牌的個數有上限的,好比個人桶子最多隻能裝1000個令牌。
每一個請求進來,就會去桶子拿一個令牌
好比這秒我有1001個請求,我就去桶子裏邊拿1001個令牌,此時可能會出現兩種狀況:
令牌桶算法支持 網絡上的突發流量
漏桶和令牌桶的區別:從上面的例子估計你們也能看出來了,漏桶只能以固定的速率去處理請求,而令牌桶能夠以桶子最大的令牌數去處理請求
RateLimiter是Guava的一個限流組件,我這邊的系統就有用到這個限流組件,使用起來十分方便。
引入pom依賴:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>20.0</version> </dependency>
RateLimiter它是基於令牌桶算法的,API很是簡單,看如下的Demo:
public static void main(String[] args) { //線程池 ExecutorService exec = Executors.newCachedThreadPool(); //速率是每秒只有3個許可 final RateLimiter rateLimiter = RateLimiter.create(3.0); for (int i = 0; i < 100; i++) { final int no = i; Runnable runnable = new Runnable() { @Override public void run() { try { //獲取許可 rateLimiter.acquire(); System.out.println("Accessing: " + no + ",time:" + new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(new Date())); } catch (Exception e) { e.printStackTrace(); } } }; //執行線程 exec.execute(runnable); } //退出線程池 exec.shutdown(); }
咱們能夠從結果看出,每秒只能執行三個:
RateLimiter是一個單機的限流組件,若是是分佈式應用的話,該怎麼作?
可使用Redis+Lua的方式來實現,大體的lua腳本代碼以下:
local key = "rate.limit:" .. 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,並設置1秒過時 redis.call("INCRBY", key,"1") redis.call("expire", key,"1") return current + 1 end
Java代碼以下:
public static boolean accquire() throws IOException, URISyntaxException { Jedis jedis = new Jedis("127.0.0.1"); File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua"); String luaScript = FileUtils.readFileToString(luaFile); String key = "ip:" + System.currentTimeMillis()/1000; // 當前秒 String limit = "5"; // 最大限制 List<String> keys = new ArrayList<String>(); keys.add(key); List<String> args = new ArrayList<String>(); args.add(limit); Long result = (Long)(jedis.eval(luaScript, keys, args)); // 執行lua腳本,傳入參數 return result == 1; }
解釋:
執行lua腳本(lua腳本判斷當前key是否超過了最大限制limit)
參考來源:
更多資料參考:
樂於輸出 乾貨的Java技術公衆號: Java3y。公衆號內 有200多篇原創技術文章、海量視頻資源、精美腦圖, 關注便可獲取!
以爲個人文章寫得不錯,點贊!