Redismq的框架,支持延时队列,能实现所有mq的功能

框架来源

在做项目业务的时候一直都有用到redis。在一些小项目背景上,引用大型的mq可能不是特别适用,并且很多mq对延时队列的支持大多不太友好。比如rabbitmq是最能自定义时间的但是需要安装插件,rocketmq的自定义时间需要花钱买阿里云的,kafka则没有对应的实现。因此本框架借鉴了rabbitmq和springboot整合的思路,rocketmq和springboot整合的思路,参考了redission的延时队列。开发了一套支持各种队列消息的功能。

基础功能

发送普通消息
发送延时消息
发送定时执行的消息
发送顺序消息

特点

  • 无侵入延时队列:无需任何配置和额外的插件即可使用延时队列
  • 兼容spring事务和seata事务:支持消息在事务提交后发送
  • 优雅的api封装: 对外暴露的api及其简单易用
  • 支持消息的负载均衡:通过redis定时轮询注册心跳
  • 支持顺序消息: 只需要把消费者和虚拟队列都设置为1
  • 支持定时消息: 可以让消息在指定时间执行
  • 支持生产者消费者回调: 消息发送结果通过回调通知用户

核心设计

负载均衡

定时给redis发送心跳注册自身,项目启动和新的客户端加入时会对所有客户端执行重平衡。

几个概念

topic等共同于rocketmq的topic
virtualQueue虚拟队列等同于rocketmq的队列,默认1个(多个队列在redis集群时有用)
queueMaxSize队列最大长度由于redis是内存数据库,因此对队列的数量进行了限制(消息的堆积能力不足其他mq)
tag等同于rocketmq的tag
group用来对消息集群分组
不同的group的队列消息,客户端都会进行隔离。
相同的服务多个实例使用相同group,每个服务的group都不能相同

案例代码

引入

      <dependency>
            <groupId>io.github.zhaohaohgroupId>
            <artifactId>redismq-spring-boot-autoconfigureartifactId>
            <version>Latest Versionversion>
        dependency>
/**
 * @Author: hzh
 * @Date: 2022/12/26 17:54
 * 生产消息的例子
 */
@RestController
@RequestMapping("producer")
public class ProducerController {
    @Autowired
    private RedisMQTemplate redisMQTemplate;

    /**
     * 发送延迟消息
     */
    @PostMapping("sendDelayMessage")
    public void sendDelayMessage() {
        redisMQTemplate.sendDelayMessage("延时消息消费", "delaytest1", Duration.ofSeconds(60));
    }


    /**
     * 发送普通消息
     */
    @PostMapping("sendMessage")
    public void sendMessage() {
        redisMQTemplate.sendMessage("普通消息消费", "test1");
    }

    /**
     * 发送顺序消息
     */
    @PostMapping("sendOrderMessage")
    public void sendOrderMessage() {
        redisMQTemplate.sendMessage("顺序消息消费", "order");
    }

    /**
     * 发送定时消费消息 带tag
     */
    @PostMapping("sendTimingMessage")
    public void sendTimingMessage() {
        LocalDateTime time = LocalDateTime.of(2022, 12, 26, 14, 20, 30);
        long l = time.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        redisMQTemplate.sendTimingMessage("定时消息消费", "time", "bussiness1", l);
    }


    /**
     * 发送定时消费消息 带tag
     */
    @PostMapping("sendMultiTagMessage")
    public void sendMultiTagMessage() {
            redisMQTemplate.sendMessage("多个标签同一topic消息消费1", "MultiTag", "bussiness1");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费2", "MultiTag", "bussiness2");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费1", "MultiTag", "bussiness1");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费2", "MultiTag", "bussiness2");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费1", "MultiTag", "bussiness1");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费2", "MultiTag", "bussiness2");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费1", "MultiTag", "bussiness1");
            redisMQTemplate.sendMessage("多个标签同一topic消息消费2", "MultiTag", "bussiness2");
    }


}

/**
 * @Author: hzh
 * @Date: 2022/12/26 17:54
 * 消费者简单案例
 */
@Component
public class SamplesConsumer {


    /**
     * delaytest1消费延时队列
     */
    @RedisListener(topic = "delaytest1", delay = true)
    public void delaytest1(String test) {
        System.out.println(test);
    }

    /**
     * 普通消息消费
     */
    @RedisListener(topic = "test1")
    public void test1(String test) {
        System.out.println(test);
    }

    /**
     * 顺序消息消费  虚拟队列,消费者线程都设置为1即可保证顺序
     */
    @RedisListener(topic = "order", virtual = 1, concurrency = 1, maxConcurrency = 1)
    public void order(Message message) {
        System.out.println(message);
    }

    @RedisListener(topic = "time",tag = "bussiness1",delay = true)
    public void time(Message message) {
        System.out.println(message);
    }


    /**
     * 多标签同topic消费,会由同一个线程池进行消费
     *
     * @param message 消息
     */
    @RedisListener(topic = "MultiTag",tag = "bussiness1")
    public void multiTag1(Message message) {
        //模拟业务消费
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println(name+message);
    }

    @RedisListener(topic = "MultiTag",tag = "bussiness2")
    public void multiTag2(Message message) {
        //模拟业务消费
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println(name+message);
    }
}

如何处理消息堆积

由于内存队列的特性,无法堆积消息(消息累积到一定数量会拒绝)。因此框架提供了发送消息的生产者前后回调和消费者前后回调。
默认消费失败加入redis的死信队列
默认生产发消息失败打印失败日志
可自定义实现对消息持久化mysql等第三方存储库

@Configuration
public class RedisMQInterceptorConfiguration {
    
    @Bean 
    public ConsumeInterceptor redisDeadQueueHandleInterceptor() {
        return new 自定义ConsumeInterceptor实现类();
    }
    
    @Bean 
    public ProducerInterceptor producerInterceptor() {
        return new 自定义ProducerInterceptor实现类();
    }
}

最后附上项目地址

喜欢的点个赞
github
gitee

缺点:目前只提供了spring-redis的api对框架的操作进行实现。

你可能感兴趣的:(java,mq,redis,java-rabbitmq,rabbitmq)