令牌桶算法( Token Bucket
)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 1/QPS
时间间隔(如果 QPS=100
,则间隔是 10ms
)往桶里加入 Token
(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个 Token
,如果没有 Token
可拿了就阻塞或者拒绝服务.
@Autowired private JedisClientService jedisClient; public boolean acquire(String key, Integer permits, long currMillSecond) { try { //针对新用户创建令牌桶 if (!jedisClient.exists(key)) { jedisClient.hset(key, "last_mill_second", String.valueOf(currMillSecond)); jedisClient.hset(key, "curr_permits", "0"); jedisClient.hset(key, "max_permits", "50"); jedisClient.hset(key, "rate", "400"); return true; } //获取令牌桶信息,上一个令牌时间,当前可用令牌数,最大令牌数,令牌消耗速率 List<String> limitInfo = jedisClient.hmget(key, "last_mill_second", "curr_permits", "max_permits", "rate"); long lastMillSecond = Long.parseLong(limitInfo.get(0)); Integer currPermits = Integer.valueOf(limitInfo.get(1)); Integer maxPermits = Integer.valueOf(limitInfo.get(2)); Double rate = Double.valueOf(limitInfo.get(3)); //向桶里面添加令牌 Double reversePermitsDouble = ((currMillSecond - lastMillSecond) / 1000) * rate; Integer reversePermits = reversePermitsDouble.intValue(); Integer expectCurrPermits = reversePermits + currPermits; Integer localCurrPermits = Math.min(expectCurrPermits, maxPermits); //添加令牌之后更新时间 if (reversePermits > 0) { jedisClient.hset(key, "last_mill_second", String.valueOf(currMillSecond)); } //判断桶里面剩余的令牌数目 if (localCurrPermits - permits >= 0) { jedisClient.hset(key, "curr_permits", String.valueOf(localCurrPermits - permits)); return true; } else { jedisClient.hset(key, "curr_permits", String.valueOf(localCurrPermits)); return false; } } catch (Exception e) { e.printStackTrace(); return false; } }
参考文章: https://blog.csdn.net/tianyaleixiaowu/article/details/74942405