1. 整体结构
2. 基本概念
mqMsg 是需要处理的异步任务的【基本单元】,与【Topic】关联在一起。
Topic 是一组相同类型的mqMsg集合,提供给消费者订阅的。
3. 消息结构
mqMsg :延迟队列里的基本单元属性如下:
因为有不同的业务,所以内容是不同的,所以基本单元利用【泛型】来封装 msg<?>可以接收任何DTO。
4. 消息存储
- Redis提供了【有序集合zset】,利用zset封装延迟消息。
- stringRedisTemplate.opsForZSet().add(topic, mqMsg, score)
5. 代码实现思想-生产者
- 业务方 对mqMsg进行消息结构赋值,如时间、重试次数等。
- 之后存入 Redis Zet 数据结构即可,key为topic,score为当前时间戳 + 延迟时间。
6. 代码实现思想-消费者(模板)
每个业务topic 提供给 消费者订阅,消费者的基本逻辑 都是一样的,所以我们抽出一个模板方法。
各个业务继承这个模板方法,并再各个业务类上加上自定义注解用来表示 topic。
【公共部分】逻辑
-
消息获取转实体
-
执行处理消息(各个子类具体实现)
-
根据具体处理消息【结果】进行【重试】or【成功】or【失败】or【异常处理】,具体的处理各个子类具体实现
-
其中重试主要思想为根据消息结果该业务方投递前设置的属性值超时时间 延迟时间 次数重新投递
6. 代码实现思想-消费者(工厂)
工厂主要用于管理消费者队列处理器的【注册】和【创建】以及提供【根据topic获取处理器】的功能
-
维护了一个 ConcurrentHashMap容器用于存储【topic<=>处理器】的映射保证【并发】访问的效率和安全性
-
其中处理器 使用的是一个 父类抽象类以及泛型<?> 表示可以【接收任何类型】的子类,增加了代码的灵活性和扩展性
7. 代码实现思想-注册
ApplicationRunner.run被调用时,所有bean已经被实例化,依赖注入已经完成。
所以我们可以在run方法中安全地访问Spring上下文中的bean。
我们在run方法对工厂进行注册的工作。
- 使用applicationContext.getBeansOfType动态地发现和访问容器中注册的bean,
其中传入我们的父类抽象类,这时候返回的是map,键是bean的名字,值是bean的实例。
-
对这个Map进行遍历,获取bean注解的topic对工厂进行注册。
-
注册完后工厂就维护一套【topic = 消费者bean】
8. 启动消费者
- 使用ScheduledThreadPoolExecutor定时任务和周期性任务的线程池
- 间隔3秒执行消费逻辑
- 先扫描 Redis keys 中 topic:* 延迟队列相关的key
- 使用并行进行处理 各个 topic队列,具体处理思路利用rangeByScore获取0到当前时间戳的60的元素,并开始并行消费
- 具体消费逻辑是 我们从消费队列拿出基本消息体 可以关联到topic从而从工厂获取对应的 消费者,后对topic队列进行移除,在使用对应消费者进行处理消息。