只有光頭才能變強。java
文本已收錄至個人GitHub倉庫,歡迎Star:github.com/ZhongFuChen…git
以前在學習的時候也接觸不到高併發/大流量這種東西,因此限流固然是沒接觸過的了。在看公司項目的時候,發現有用到限流(RateLimiter),順帶了解一波。github
爲啥要限流,相信就不用我多說了。redis
在代碼世界上,限流有兩種比較常見的算法:算法
好比,如今我有一個桶子,綠色那塊是我能裝水的容量,若是超過我能裝下的容量,再往桶子裏邊倒水,就會溢出來(限流):segmentfault
咱們目前能夠知道的是:服務器
OK,如今咱們在桶子裏挖個洞,讓水能夠從洞子裏邊流出來:網絡
桶子的洞口的大小是固定的,因此水從洞口流出來的速率也是固定的。併發
因此總結下來算法所需的參數就兩個:分佈式
漏桶算法有兩種實現:
通過上面的分析咱們就知道:
漏桶算法能夠平滑網絡上的突發流量(由於漏水的速率是固定的)
如今我有另一個桶子,這個桶子不用來裝水,用來裝令牌:
令牌會必定的速率扔進桶子裏邊,好比我1秒扔10個令牌進桶子:
桶子能裝令牌的個數有上限的,好比個人桶子最多隻能裝1000個令牌。
每一個請求進來,就會去桶子拿一個令牌
令牌桶算法支持網絡上的突發流量
**漏桶和令牌桶的區別:**從上面的例子估計你們也能看出來了,漏桶只能以固定的速率去處理請求,而令牌桶能夠以桶子最大的令牌數去處理請求
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;
}
複製代碼
解釋:
參考來源:
更多資料參考:
樂於輸出乾貨的Java技術公衆號:Java3y。公衆號內有200多篇原創技術文章、海量視頻資源、精美腦圖,關注便可獲取!
以爲個人文章寫得不錯,點贊!