使用redisMQ-spring-boot-starter实现消息队列和延时队列

本文介绍了RedisMQ-spring-boot-starter,一个基于Redis的轻量级消息队列中间件,支持开箱即用、消息确认、延时队列、发布与订阅模式以及虚拟空间隔离,还提供了web控制台进行监控。文章详细讲解了如何配置和使用这个库,包括依赖管理、配置Redis和创建消费者和生产者示例。
摘要由CSDN通过智能技术生成

简介

redisMQ-spring-boot-starter是一个轻量级的、基于Redis实现的消息队列中间件,它有如下优点:

  • 开箱即用,你几乎不用添加额外的配置
  • 支持消息队列、延时队列,并提供精细化配置参数
  • 支持发布与订阅模式
  • 提供消息确认机制
  • 支持虚拟空间,不同虚拟空间的数据互相隔离
  • 支持web控制台,实时查看各个队列的消费情况

开始使用

引用依赖

springboot3.0以下版本:

<dependency>
    <groupId>io.github.lengmianshi</groupId>
    <artifactId>redisMQ-spring-boot-starter</artifactId>
    <version>1.0.7</version>
</dependency>

<!-- 以下配置可以改为你自己的版本 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

注:spring-boot-starter-data-redis依赖于spring-data-redis,如果发生依赖冲突,要确保spring-data-redis的版本不低于2.1.0.RELEASE,可在你的pom.xml中锁定版本:

<dependencyManagement>
    <dependencies>
          <dependency>
              <groupId>org.springframework.data</groupId>
              <artifactId>spring-data-redis</artifactId>
              <version>2.1.2.RELEASE</version>
          </dependency>
    </dependencies>
</dependencyManagement>

springboot3.0:

<dependency>
    <groupId>io.github.lengmianshi</groupId>
    <artifactId>redisMQ-spring-boot-starter</artifactId>
    <version>2.0.7</version>
</dependency>
        
<!-- 以下配置可以改为你自己的版本 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

配置redis

一般引入redis的项目都会事先配置,如果你的项目没配置过,则可在application.yml中加上如下配置:

springboot3.0以下版本:

spring:
  redis:
    host: test.redis.com  #改成你的
    password: vC403V2KMc0Kghz #改成你的
    port: 6379 #改成你的
    jedis:
      pool:
        max-active: 100
        max-idle: 10
        min-idle: 10
    timeout: 2000
    # 覆盖重复注册的bean
  main:
    allow-bean-definition-overriding: true
springboot3.0:
```yaml
spring:
  data:
    redis:
      host: test.redis.com #改成你的
      password: vC403V2KMc0Kghz #改成你的
      port: 6379 #改成你的
      jedis:
        pool:
          max-active: 100
          max-idle: 10
          min-idle: 10
      timeout: 2000
  # 覆盖重复注册的bean
  main:
    allow-bean-definition-overriding: true

消息队列

生产者发送消息

@Autowired
private RedisQueueTemplate redisQueueTemplate;

/**
 * 1次只发送一条消息
 */
@Test
public void test1() {
    JSONObject message = new JSONObject();
    message.put("bondId", "17f62f1dfb5afb12e8d67cd651c1df53");
    message.put("year", 2022);
    redisQueueTemplate.sendMessage("test_queue", message);
}

/**
 * 批量发送消息
 */
@Test
public void test2() {
    List messageList = new ArrayList<>();
    for (int i = 0; i < 5000; i++) {
        JSONObject mess = new JSONObject();
        mess.put("index", i);
        messageList.add(mess);
    }

    redisQueueTemplate.sendMessageAll("test_queue", messageList);
}

注:示例中每条MQ消息都用JSONObject包装,这只是我的个人习惯,你也可以使用实体类

消费者消费消息

消费方法的参数只能有1个,并且类型要与生产者发送消费的类型保存一致:

@Component
public class QueueConsumer {
    //使用默认参数
    @RedisQueueListener(queue = "test_queue")
    public void test(JSONObject message){
        System.out.println(message);
    }

    //指定单个实例下使用5个消费线程
    @RedisQueueListener(queue = "test_queue2", consumers = 5)
    public void test2(JSONObject message){
        System.out.println(message);
    }

    //单个实例5个线程,手动确认
    @RedisQueueListener(queue = "test_queue3", consumers = 5, autoAck = false)
    public void test3(JSONObject message){
        System.out.println(message);
    }

}

@RedisQueueListener注解支持的所有参数:

package com.leng.project.redisqueue.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisQueueListener {
    /**
     * 队列名
     *
     * @return
     */
    String queue() default "";

    /**
     * 消费者线程数
     *
     * @return
     */
    int consumers() default 1;

    /**
     * 是否自动确认
     *
     * @return
     */
    boolean autoAck() default true;

    /**
     * 一次从队列中取多少数据
     *
     * @return
     */
    int prefetch() default 50;

    /**
     * 获取消息的频率,单位秒
     * @return
     */
    long frequency() default 2;
}

其中:

  • consumers:单个实例下启动多少个消费线程,默认为1
  • autoAck:是否自动确认消息,默认为true。自动确认与手动确认的区别:
    • 自动确认:消费线程从队列中取出消息,如果消费失败,则该条消息丟失
    • 手动确认:消费线程从队列中取出消息,并将消息写入待确认队列中;如果消费失败,则一段时间后(15分钟)会重新入队,消费端要做幂等性处理
  • prefetch:一个消费线程一次性从队列中取出多少条消息,因为涉及锁的竞争,不宜过小,默认为50
  • frequency:单个消费线程每隔多少秒获取一次消息,默认为2,最小值为1。有人可能会奇怪,消息不是应该即时消费吗?不是越快越好吗?实际上,有些业务场景对消息的实时性要求很低,几天、几个月、甚至一年才执行一次,这时我们完全可以把frequency调大,以减轻redis的压力

延时队列

延时队列的常用场景如用户下单,xx分钟后没有支付则自动关闭订单;已支持的订单,xxx天后自动确认收货等。

生产者发送消息

@Autowired
private RedisQueueTemplate redisQueueTemplate;

/**
 * 1次只发送1条消息
 */
public void test1(){
    JSONObject message = new JSONObject();
    message.put("bondId", "17f62f1dfb5afb12e8d67cd651c1df53");
    message.put("year", 2022);
    //延时5秒
    redisQueueTemplate.sendDelayMessage("test_delay_queue", message, 5, TimeUnit.SECONDS);
}

/**
 * 批量发送,每条消息的延时时长一样
 */
public void test2(){
    List messageList = new ArrayList<>();
    for (int i = 0; i < 5000; i++) {
        JSONObject mess = new JSONObject();
        mess.put("index", i);
        messageList.add(mess);
    }
    //延时5秒
    redisQueueTemplate.sendDelayMessageAll(queue, messageList, 5, TimeUnit.SECONDS);
}

/**
 * 批量发送,每条消息的延时时长各不相同
 */
public void test3(){
    List messageList=new ArrayList<>();
    for(int i=0; i< 5000; i++){
        JSONObject mess=new JSONObject();
        mess.put("index",i);
        
        //每条消息可以使用不同的延时时长,这里为了简便,统一写成5了
        DelayMessageParam param=new DelayMessageParam(mess,5,TimeUnit.SECONDS);
        messageList.add(param);
    }

    redisQueueTemplate.sendDelayMessageAll(queue,messageList);
}

注:示例中每条MQ消息都用JSONObject包装,这只是我的个人习惯,你也可以使用实体类

消费者消费消息

@RedisDelayQueueListener注解的参数与@RedisQueueListener完全相同;消费方法的参数只能有1个,并且类型要与生产者发送消费的类型保存一致:

@Component
public class DelayQueueConsumer {
  /**
   * 使用默认参数
   * @param message
   */
  @RedisDelayQueueListener(queue = "test_delay_queue")
    public void test(JSONObject message){
        System.out.println(message);
    }

  /**
   * 单个实例5个消费线程
   * @param message
   */
  @RedisDelayQueueListener(queue = "test_delay_queue2", consumers = 5)
    public void test2(JSONObject message){
        System.out.println(message);
    }

  /**
   * 单个实例5个消费线程,手动确认
   * @param message
   */
  @RedisDelayQueueListener(queue = "test_delay_queue3", consumers = 5, autoAck = false)
    public void test3(JSONObject message){
        System.out.println(message);
    }

}

发布与订阅模式

发布者向某个channel发布消息,所有订阅了该channel的消费端均能接受消息

消费端

使用@RedisSubscribeListener修饰某个方法,即可定义一个消费端,其中channel表示要订阅的频道:

import com.alibaba.fastjson.JSONObject;
import com.leng.project.redisqueue.annotation.RedisSubscribeListener;
import org.springframework.stereotype.Component;

@Component
public class ChannelConsumer {

    //带参数
    @RedisSubscribeListener(channel = "test_channel")
    public void test(JSONObject message) {
        System.out.println("test_channel:" + message);        
    }
    
    //不带参数
    @RedisSubscribeListener(channel = "test_channel_02")
    public void test() {
      System.out.println("test_channel_02");
    }
    
}

channel还可以使用通配符:

import com.alibaba.fastjson.JSONObject;
import com.leng.project.redisqueue.annotation.RedisSubscribeListener;
import com.leng.project.redisqueue.utils.SubscribeUtils;
import org.springframework.stereotype.Component;

@Component
public class ChannelConsumer {
    
    @RedisSubscribeListener(channel = "test_channel*")
    public void test2() {
        System.out.println("test_channel*:" + SubscribeUtils.currentChannel());
    }
}

使用通配符后,如果想确切地知道是哪个channel产生的消息,可以使用SubscribeUtils.currentChannel()

生产者

@Autowired
private RedisQueueTemplate redisQueueTemplate;

public String testSendChannelMessage(){
    JSONObject message = new JSONObject();
    message.put("bondId", "17f62f1dfb5afb12e8d67cd651c1df53");
    message.put("year", 2022);
    //带参数
    redisQueueTemplate.sendChannelMessage("test_channel", message);
    //不带参数
    redisQueueTemplate.sendChannelMessage("test_channel_02");
    return "OK";
}

虚拟空间

参考了RabbitMQ的设计。虚拟空间很有必要,例如,开发环境和测试环境的数据如果没有隔离,在调试时被测试环境的消费端干扰。
配置虚拟空间:

queue:
  virtual-host: /dev  #默认为 /

Web管理平台

浏览器访问:http://ip:port/queue.html,默认的账号密码为admin/admin

配置账号:

queue:
  console:
    #是否启用web控制台
    enable: true
    username: admin #登录用户名
    password: 123456 #密码

登录成功后的界面,可查看所有虚拟空间的队列及消费情况:
image.png

注:如果你的系统使用了权限控制框架,如shiro、spring-security等,则需要对如下3个资源放行:

  • /queue.html
  • /queue/**
  • /static/**

ps:
项目地址:https://github.com/lengmianshi/redisMQ-spring-boot-starter,欢迎提bug

  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shiro-redis-spring-boot-starter是一个用于集成Apache Shiro和Redis的Spring Boot Starter项目。Apache Shiro是一个强大而灵活的Java安全框架,用于身份验证、授权和会话管理等安全功能。而Redis是一个高性能的内存数据库,其具有快速的数据存取能力和持久化支持。 shiro-redis-spring-boot-starter提供了一种简化和快速集成Shiro和Redis的方式,使得在Spring Boot应用中实现安全功能变得更加容易。通过使用该Starter,我们可以方便地将Shiro的会话管理功能存储到Redis中,从而支持分布式环境下的会话共享和管理。 使用shiro-redis-spring-boot-starter可以带来以下好处: 1. 分布式环境的会话共享:通过将Shiro的会话数据存储到Redis中,不同的应用节点可以共享同一个会话,从而实现分布式环境下的会话管理和跨节点的身份验证和授权。 2. 高可用性和性能:Redis作为一个高性能的内存数据库,具有出色的数据读写能力和持久化支持,可以提供可靠的会话存储和高性能的数据访问能力。 3. 简化配置和集成:shiro-redis-spring-boot-starter提供了封装好的配置和集成方式,减少了我们自己实现集成的复杂性和工作量。 总结来说,shiro-redis-spring-boot-starter为我们提供了一种简化和快速集成Shiro和Redis的方式,使得在Spring Boot应用中实现安全功能变得更加容易和高效。通过它,我们可以实现分布式环境下的会话共享和管理,提供高可用性和性能的数据存取能力,同时简化了配置和集成的复杂性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值