一、目的
并发问题处理:单位时间内请求次数过多,访问量较大时,报错提示用户。往往需要进行限流(每一秒限制请求几次)
注意: 这边说的访问量较大,是指有修改数据库数据; 正常的纯查询, 无需做限流; 当然,要做也是可以的
二、基本思路
使用 redis 缓存加 scheduler 定时器进行实现;
以每一小时为处理的时间单位,以每一秒为 job 的启动间隔;
在一小时内,为每一秒都分配一定的令牌数量(即限流的次数);
在每一秒内,请求了需限流的接口,则将令牌数量减 1 ;在这一秒内,一旦在请求时发现令牌数量为 0 时,则直接报错提示
三、具体实现
1、使用定时器分配令牌数量:
@Override
protected void execute() throws Throwable {
logger.info("begin to run FreeProductTokenJob");
// 1 个小时,每间隔 1 秒钟,发放指定数量的Token
int maxTime = 60 * 60 * 1;
for (int i = 0; i < maxTime; i++) {
sleep(1000);
redisAccess.stringOps().set(RedisKeyConstant.FREE_PRODUCT_TOKEN_KEY, String.valueOf(tokenCount));
}
redisAccess.keyOps().delete(RedisKeyConstant.FREE_PRODUCT_TOKEN_KEY);
logger.info("End to run FreeProductTokenJob");
}
private void sleep(int millisecond) {
try {
Thread.sleep(millisecond);
} catch (InterruptedException e) {
logger.error("Sleep error", e);
}
}
2、controller、service 消费令牌:
@Inject
private FreeProductTokenService freeProductTokenService;
@Override
public FreeProductGetTokenResponse getToken() {
FreeProductGetTokenResponse response = new FreeProductGetTokenResponse();
response.setCanRunInto(freeProductTokenService.getToken());
return super.setSuccessBaseResponse(response, "");
}
@Service
public class FreeProductTokenService {
@Inject
private RedisAccess redisAccess;
public boolean getToken() {
if (!redisAccess.keyOps().exists(RedisKeyConstant.FREE_PRODUCT_TOKEN_KEY)) {
return true;
}
String count = redisAccess.stringOps().get(RedisKeyConstant.FREE_PRODUCT_TOKEN_KEY);
if (StringUtils.hasText(count) && Integer.parseInt(count) > 0) {
// 调用一次,令牌数量减 1
redisAccess.stringOps().increment(RedisKeyConstant.FREE_PRODUCT_TOKEN_KEY, -1);
return true;
}
return false;
}
}
3、在需要限流的接口处调用:
if (!productFavoriteWrapper.getToken()) {
setErrorResponse(response, 180007, "当前购买人数过多,请稍后尝试");
return response;
}
@Inject
private FreeProductServiceAPI freeProductServiceAPI;
public boolean getToken() {
FreeProductGetTokenResponse response = freeProductServiceAPI.getToken();
if (null == response || 200 != response.getCode()) {
return false;
}
return response.isCanRunInto();
}