一个比较麻烦的限流实现

主要环节

1、前端接受用户请求,发送给后端建立任务;
2、后端接受请求后,db创建任务记录(初始状态),将记录发送到mq;
2、消费者拿到任务后,将任务置为running,然后向外部平台发送请求,并向mq的延时队列(5分钟)发送消息;
3、外部平台收到请求后,(正常)会在2分钟内向我方的接收接口提交结果,也可能(5分钟)超时无响应;
4、我方接收接口收到响应后,完成任务记录的后续工作,成功--输出结果,失败--置为失败;
5、若外部平台未能在时限内返回结果,延时队列负责将任务置为失败

实现细节:

1、使用消息队列是为了避免使用定时任务,定时任务间隔不好把控,多实例部署时,多个定时任务进程还会导致任务的重复消费;
2、由于外部平台的并发限制为n,如果消费者实例数为x,每个实例并发为y,则x * y = n;

x个web实例,@RabbitListener的并发为y,每个并发线程有不同的consumerTag

3、消费线程有唯一的consumerTag,可利用consumerTag建立redisson锁,获取锁才进行任务消费(db保存consumerTag字段),向外部平台发送请求,收到外部平台的异步反馈后才能解锁;

获取分布式锁lock=consumerTag(随机化tryLockAsync方法的threadId,防止可重入锁),使用await阻塞获取锁的过程,得到锁以后发起对外部平台的请求并将consumerTag存入任务表的字段中,然后执行下一条消息,就会在获取锁的时候阻塞。

4、根据1-2-3的步骤,在上一个消息得到反馈或超时之前,消费线程会阻塞等待获取锁,保证了x * y = n(任务提交和结果接收是异步的,发送和接收可能在不同实例,所以使用分布式锁),;

有一个专门的接口接收外部平台的任务结果,根据返回结果,释放对应的分布式锁,并更新任务状态

5、redisson是可重入锁,每次调用tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId)获取锁时必须传入随机化的threadId,否则提交任务后的线程在消费下一条消息时直接因为可重入锁而无需等待,限流目的就无法实现。
6、如果超时未能收到外部平台的反馈,可以通过设置leaseTime自动释放锁,如果不设置leaseTime,只要客户端不失去连接, 锁会由看门狗自动续期持有;
7、延时队列的主要工作是将任务置为失败,是必不可少的,也可以由它来实现锁的释放。

已经发送到外部平台的任务,会把任务id发送到一个延时队列中,过期时更新任务状态

如何实现 x * y = n

@RabbitListener(
    queuesToDeclare = @Queue("${ ... }"),
    ackMode = "MANUAL",
    autoStartup = "${ ... }",
    containerFactory = "beanName"
)
@RabbitHandler


# 配置containerFactory
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConcurrentConsumers(3);         // 最小并发线程数 (默认是单个线程)
factory.setMaxConcurrentConsumers(10);     // 最大并发线程数

优点是简单,负载均衡算法为完全公平

# Java semaphore的分布式版本
RSemaphore semaphore = redisson.getSemaphore("external_api_limit");
semaphore.trySetPermits(n); // 初始化只允许 n 个请求

boolean acquired = semaphore.tryAcquire(5, TimeUnit.SECONDS); # 5秒
if (acquired) {
    try {
        // 调用外部平台
    } finally {
        semaphore.release(); // 请求完成后释放名额
    }
} else {
    // 没获取到许可,说明已经达到并发上限,等下次
}

增加RSemaphore后, 可以性能强的节点上适当增加并发线程数,增加灵活性。

其他主体

1、rabbitmq
2、springboot的rabbitmq客户端
3、redisson
4、延时队列

posted @ 2024-06-07 17:34  又是火星人  阅读(30)  评论(0)    收藏  举报