在設計服務的時候,咱們會遇到不一樣的限速限頻需求。根據不一樣的需求場景,咱們會有對應的解決方案。java
使用Guava的RateLimiter工具類,基於令牌桶算法實現流量限制。算法
通常地RateLimiter提供的是單機的限流方案。若須要實現服務總體的QPS流量控制,能夠基於Redis實現令牌桶算法。也能夠將總體QPS拆分紅單個機器的訪問QPS,再進行相應的控制(這種方式QPS控制並非很準確,尤爲是在服務QPS限制比較低的狀況下)。數據庫
private final RateLimiter rateLimiter = RateLimiter.create(100); // 100QPS
// 堵塞限制QPS
void foo1() {
rateLimiter.acquire(); // 在這裏有可能發生堵塞
// 實際執行的邏輯塊
}
// 不堵塞限制QPS
void foo2() {
if (rateLimiter.tryAcquire()) { // 這裏不會發生任何堵塞行爲
// QPS以內容許執行的代碼路徑
} else {
// 超過指定QPS時執行的代碼路徑
}
}
複製代碼
通常不推薦在同步業務請求中使用堵塞版本的RateLimter進行限速,更多的使用場景是在消費類場景中使用(如Kafka消費、定時任務之類)。併發
高頻次場景app
如1天內接口請求次數;分佈式
低頻次場景工具
如1個小時內用戶只能發5條feed動態;ui
高頻次場景(Redis Counter)spa
在高頻次場景咱們通常都不會太關注具體的限頻次數,因此在計數上不會要求特別準確。線程
咱們能夠經過Redis的Counter對頻次進行統計。如1天內的接口請求次數咱們能夠相似這樣設計20190403_appkey,在20190403當天全部的接口訪問,都會對這個key進行加1操做,這樣就能夠大概統計到當天的全部請求次數,並進行相應的限次邏輯。
private final FastDateFormat fastDateFormat = FastDateFormat.getInstance("MMddHH");
@Resource
private RedisClient rc;
public long incrCount(String appkey, long time) {
String key = key(appkey, time);
return rc.incr(key);
}
private String key(String appkey, long time) {
return appkey + "_" + fastDateFormat.format(time);
}
複製代碼
低頻次場景
在低頻次場景,由於次數限制比較少,因此要求計數準確。
咱們可使用Redis SortedSet去維護feed集合,member爲對應的feed動態ID,score爲對應的feed動態發佈時間。在用戶發佈feed動態的時候,先計算有效的元素個數,最後進行相應的限次邏輯。
這裏是任意的時間間隔,而不是固定的時間間隔。倘若用戶在0點59分發布了5條feed動態,用戶在1點0分的時候仍不能夠發佈,須要等到1點59分的時候才能夠解除限制。
@Resource
private RedisClient rc;
public void add(User user, FeedItem feedItem) {
rc.zadd(key(user), feedItem.getCreateTime(), feedItem.getFeedId());
double min = 0;
double max = (double) System.currentTimeMillis()
- TimeUnit.HOURS.toMillis(1);
rc.zremrangeByScore(key(user), min, max);
}
public List<String> get(User user, long curTime) {
double max = curTime;
double min = (double) curTime
- TimeUnit.HOURS.toMillis(1);
Set<byte[]> value = rc.zrevrangeByScore(key(user), max, min);
return value.stream().map(RedisUtils::toString).collect(Collectors.toList());
}
private String key(User user) {
return String.valueOf(user.getUid());
}
複製代碼
若是咱們的需求變得更復雜,須要根據動態類型限定用戶只能發佈N條feed動態。
咱們能夠經過Redis的Hash結構來維護用戶最近發佈的N條feed動態,其中field爲動態類型,value爲feed動態列表。在用戶發佈feed動態的時候,須要獲取對應類型的發佈feed集合,過濾掉過時的feed集合後判斷用戶是否容許發佈。若容許發佈,再把新的feed加入到對應的集合中。(注:過時的feed集合已通過濾,所以數據不會愈來愈多)
注意事項
注意併發,能夠經過分佈式鎖解決併發訪問的問題。這裏通常是低頻操做,出現併發的可能性較低;
使用上比Sorted Set複雜,不過能夠減小空間佔用;
以上是咱們常見的限速限頻需求,本文簡單介紹一些基本的思路。在實際應用場景中或許有更巧妙的解決方案,能夠一塊兒溝通交流。