可过期信号量并发控制
需求背景
供应商提供的接口,单个账号存在并发限制,超出并发数就会报错,并且支持并发数较低,经常会因为高频调用而导致报错。
而供应商暂时不支持提高单个账号的并发数,给出的解决方案是提供多个账号。账号为次数充值账号,需要尽量做到次数均衡使用。
问题分析
我们需要保证接口不会因为并发数上限而报错的前提下,充分的使用多个账号的并发数,同时保证并发控制的效率:
-
请求排队:需要根据总并发数,控制超出并发时,所有请求进入同一队列等待,先进先出
-
集群支持:所有服务集群共享多个账号,需要支持集群的并发控制
-
账号分配:请求进入处理后,取用有空闲并发的账号进行处理
-
轮询处理:通过轮询的机制,均衡分配各账号的请求数
为保证并发的控制支持集群,我们需要使用到中间件进行协助处理,Redis、Mq或者数据库。
数据库效率太低,直接不考虑
对比Redis和Mq,Redis本身支持轻量级的mq,我们只需要的支持到排队、类似令牌桶的处理量控制,Redis均能满足,且有工具包能使用,满足我们的需求。
解决方案
我们可以通过redisson
框架提供的 可过期信号量 RPermitExpirableSemaphore
实现我们的功能
RPermitExpirableSemaphore
提供的功能:
// 创建信号量池, 设置总信号量数,建议在应用功能启动时初始化
RPermitExpirableSemaphore totalSemaphore = redissonClient.getPermitExpirableSemaphore(TOTAL_SEMAPHORE_KEY);
totalSemaphore.trySetPermits(30);
// acquire方法获取信号量,信号量已满则进入队列等待。信号量持有时间超出 TIMEOUT_SECOND 会自动释放
// 用于控制总并发,排队
RPermitExpirableSemaphore totalSemaphore = redissonClient.getPermitExpirableSemaphore(TOTAL_SEMAPHORE_KEY);
String totalPermitId = totalSemaphore.acquire(TIMEOUT_SECOND, TimeUnit.SECONDS);
// tryAcquire尝试获取信号量,信号量已满则返回null,不等待。信号量持有时间超出 TIMEOUT_SECOND 会自动释放
// 用于分配账号
RPermitExpirableSemaphore accountSemaphore = redissonClient.getPermitExpirableSemaphore(ACCOUNT_SEMAPHORE_KEY_SUFFIX + account.getAppKey());
String accountPermitId = accountSemaphore.tryAcquire(0, TIMEOUT_SECOND, TimeUnit.SECONDS);
// 处理完毕后主动释信号量
RPermitExpirableSemaphore totalSemaphore = redissonClient.getPermitExpirableSemaphore(TOTAL_SEMAPHORE_KEY);
totalSemaphore.release(totalPermitId);