令牌桶算法
这里使用Redis实现令牌桶算法,令牌桶算法具体细节可参考其他博客,这里不赘述,大致就是在 一个时间段 内,存在一定数量的令牌,我们需要拿到令牌才可以继续操作。
所以实现思路大致就是:
Redis 中记录上次拿取令牌的时间,以及令牌数,每个手机号对应一个桶
每次拿令牌时,校验令牌是否足够。
/**
* @author YukeSeko
*/
@Component
public class RedisTokenBucket {
@Resource
private RedisTemplate<String,String> redisTemplate;
/**
* 过期时间,400秒后过期
*/
private final long EXPIRE_TIME = 400;
/**
* 令牌桶算法,一分钟以内,每个手机号只能发送一次
* @param phoneNum
* @return
*/
public boolean tryAcquire(String phoneNum) {
// 每个手机号码一分钟内只能发送一条短信
int permitsPerMinute = 1;
// 令牌桶容量
int maxPermits = 1;
// 获取当前时间戳
long now = System.currentTimeMillis();
String key = RedisConstant.SMS_BUCKET_PREFIX + phoneNum;
// 计算令牌桶内令牌数
int tokens = Integer.parseInt(redisTemplate.opsForValue().get(key + "_tokens") == null ? "0" : redisTemplate.opsForValue().get(key + "_tokens"));
// 计算令牌桶上次填充的时间戳
long lastRefillTime = Long.parseLong(redisTemplate.opsForValue().get(key + "_last_refill_time") == null ? "0" : redisTemplate.opsForValue().get(key + "_last_refill_time"));
// 计算当前时间与上次填充时间的时间差
long timeSinceLast = now - lastRefillTime;
// 计算需要填充的令牌数
int refill = (int) (timeSinceLast / 1000 * permitsPerMinute / 60);
// 更新令牌桶内令牌数
tokens = Math.min(refill + tokens, maxPermits);
// 更新上次填充时间戳
redisTemplate.opsForValue().set(key + "_last_refill_time", String.valueOf(now),EXPIRE_TIME, TimeUnit.SECONDS);
// 如果令牌数大于等于1,则获取令牌
if (tokens >= 1) {
tokens--;
redisTemplate.opsForValue().set(key + "_tokens", String.valueOf(tokens),EXPIRE_TIME, TimeUnit.SECONDS);
// 如果获取到令牌,则返回true
return true;
}
// 如果没有获取到令牌,则返回false
return false;
}
}