java 限流器实现

一、目的

   并发问题处理:单位时间内请求次数过多,访问量较大时,报错提示用户。往往需要进行限流(每一秒限制请求几次)

注意: 这边说的访问量较大,是指有修改数据库数据; 正常的纯查询, 无需做限流; 当然,要做也是可以的

二、基本思路

   使用 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();

    }
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
下面给出一个简单的限流实现,使用了令牌桶算法: ``` import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class RateLimiter { private final int capacity; // 令牌桶的容量 private final int rate; // 令牌桶的生成速率,即每秒生成多少个令牌 private AtomicInteger tokens; // 当前令牌桶中令牌的数量 private long lastRefillTime; // 上次令牌桶中令牌生成的时间 public RateLimiter(int capacity, int rate) { this.capacity = capacity; this.rate = rate; this.tokens = new AtomicInteger(capacity); this.lastRefillTime = System.nanoTime(); } // 尝试获取令牌,如果获取成功则返回 true,否则返回 false public boolean tryAcquire() { refill(); return tokens.getAndUpdate(n -> n > 0 ? n - 1 : n) > 0; } // 等待直到获取到令牌 public void acquire() throws InterruptedException { while (!tryAcquire()) { TimeUnit.MILLISECONDS.sleep(100); } } // 生成令牌 private void refill() { long now = System.nanoTime(); long elapsedNanos = now - lastRefillTime; int newTokens = (int) (elapsedNanos * rate / 1_000_000_000); if (newTokens > 0) { tokens.updateAndGet(n -> Math.min(n + newTokens, capacity)); lastRefillTime = now; } } } ``` 使用示例: ``` RateLimiter limiter = new RateLimiter(10, 1); // 令牌桶容量为 10,每秒生成 1 个令牌 for (int i = 0; i < 20; i++) { if (limiter.tryAcquire()) { System.out.println("Task " + i + " is executed"); } else { System.out.println("Task " + i + " is rejected"); } TimeUnit.MILLISECONDS.sleep(100); } ``` 输出: ``` Task 0 is executed Task 1 is rejected Task 2 is rejected Task 3 is rejected Task 4 is rejected Task 5 is rejected Task 6 is rejected Task 7 is rejected Task 8 is rejected Task 9 is rejected Task 10 is executed Task 11 is rejected Task 12 is rejected Task 13 is rejected Task 14 is rejected Task 15 is rejected Task 16 is rejected Task 17 is rejected Task 18 is rejected Task 19 is rejected ``` 上面的代码中,令牌桶的容量为 10,每秒生成 1 个令牌。在执行任务时,先使用 `tryAcquire` 尝试获取令牌,如果获取成功则执行任务,否则等待一段时间后重试。如果不想等待,可以使用 `acquire`,它会一直阻塞直到获取到令牌。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值