Redis实现消息队列的方式

一、消息队列是什么
 

消息队列的构成:

  1. 消息队列:存储和管理消息,也被称为消息代理
  2. 生产者:发送消息到消息队列
  3. 消费者:从消息队列获取消息并处理消息

redis提供三种不同方式实现消息队列:

  1. list结构:基于list结构模拟消息队列
  2. PubSub:基于的点对点消息队列
  3. Stream:比较完善的消息队列模型(推荐)

 

二、List模拟消息队列

 
redis的list结构是一个双向链表,很容易模拟出队列效果

队列是入口和出口不在一边,因此可以用LPUSH结合RPOP、或者RPUSH结合LPOP实现

但是,当队列没有消息时pop就会返回null,并不会jvm堵塞队列那样堵塞并等待消息,因此这里应该使用BRPOP或者BLPOP来实现堵塞队列。

 

缺点:

无法避免消息丢失。从消息队列取到消息,还没来得及处理就挂掉了,这个消息就消失了。

只支持单消费者。一个人拿走就从队列里面弹出了。

三、PubSub的消息队列


PubSub(发布订阅)是redis2.0版本引入的消息传递模型,消费者可以订阅一个或多个channel(频道),生产者向对应channel发送消息后,所有订阅者都能收到相关消息。

PubSub消息队列支持多生产、多消费

缺点:

不支持数据持久化(刚刚的list本质是做存储的我们拿来当队列所以可以持久化)

无法避免消息丢失。

消息堆积有上限,超出时数据丢失。(缓存空间是有上限的)

四、Stream的消息队列(重点)


Stream是redis5.0引入的一种新数据类型,可以实现一个功能非常完善的消息队列。

1、单消费模式

  特点: 

  1. 消息可回溯。不消失永久保存在队列里。
  2. 一个消息可以被多个消费者读取。读完不消失的,可以多个读
  3. 可以堵塞读取
  4. 有消息漏读的风险


2、消费者组
消费者组(Consumer Group):将多个消费者划分到一个组,监听同一个队列。

消费者监听消息的基本思路:

 stream类型消息队列的消费者组特点:

  1. 消息可回溯
  2. 可以多消费者争抢消息,加快消费速度
  3. 可以阻塞读取
  4. 没有消息漏镀的风险
  5. 有消息确认机制,保证消息至少被消费一次

五、redis三种消息队列对比 

 

六、优化秒杀实战


1、创建消息队列
创建一个stream类型的消息队列,名为stream.orders

2、修改下单脚本
修改之前秒杀下单lua脚本,认定有抢购资格后,直接向steam.orders中添加消息,内容包含voucher、userId、orderId

-- 优惠券id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]
-- 订单id
local orderId = ARGV[3]
 
-- 库存key
local stockKey = "seckill:stock:"..voucherId
-- 订单key
local orderKey = "seckill:order:"..voucherId
 
-- 判断库存是否充足
if(tonumber(redis.call('get', stockKey)) <= 0) then
    return 1
end
 
-- 判断用户是否已经下过单
if(redis.call('sismember', orderKey, userId) == 1) then
    return 2
end
 
-- 扣减库存
redis.call('incrby', stockKey, -1)
 
-- 将 userId 存入当前优惠券的 set 集合
redis.call('sadd', orderKey, userId)
 
-- 将订单信息存入到消息队列中 xadd stream.orders * k1 v1 k2 v2
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

3、接收消息处理
项目启动时,开启一个线程任务,尝试获取stream.orders中的消息,完成下单

   

    /***
     * 创建线程池
     */
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
 
    /***
     * 容器启动时,便开始创建独立线程,从队列中读取数据,创建订单
     */
    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }
 
    private class VoucherOrderHandler implements Runnable {
 
        @Override
        public void run() {
            while(true){
                try {
                    // 获取消息队列中的订单信息 xreadgroup group g1 c1 count 1 block 2000 streams s1 0
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2000)),
                            StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
                    );
                    // 判断订单信息是否为空
                    if(list == null || list.isEmpty()){
                        // 如果为 null,说明没有消息,继续下一次循环
                        continue;
                    }
                    // 解析消息
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    // 创建订单
                    createVoucherOrder(voucherOrder);
                    // 确认消息 xack s1 g1 id
                    stringRedisTemplate.opsForStream().acknowledge("stream.orders", "g1", record.getId());
                } catch (Exception e) {
                    log.error("处理订单异常!", e);
                    handlePendingList();
                }
            }
 
        }
 
        private void handlePendingList() {
            while(true){
                try {
                    // 获取 pending-list 中的订单信息 xreadgroup group g1 c1 count 1 block 2000 streams s1 0
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                            StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
                    );
                    // 判断订单信息是否为空
                    if(list == null || list.isEmpty()){
                        break;
                    }
                    // 解析消息
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    // 创建订单
                    createVoucherOrder(voucherOrder);
                    // 确认消息 xack s1 g1 id
                    stringRedisTemplate.opsForStream().acknowledge("stream.orders", "g1", record.getId());
                } catch (Exception e) {
                    log.error("处理订单异常!", e);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                }
            }
 
        }
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot与Redis结合实现消息队列的方法如下: 1. 首先,确保你的Spring Boot项目中已经引入了Redis的依赖。 2. 创建一个消息发布者类,用于发布消息到Redis消息队列中。可以使用RedisTemplate来实现消息的发布。以下是一个示例代码: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component public class MessagePublisher { @Autowired private RedisTemplate<String, Object> redisTemplate; public void publish(String channel, Object message) { redisTemplate.convertAndSend(channel, message); } } ``` 3. 创建一个消息订阅者类,用于监听Redis消息队列并处理接收到的消息。可以使用@RedisListener注解来实现消息的订阅。以下是一个示例代码: ```java import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.stereotype.Component; @Component public class MessageSubscriber implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { String channel = new String(message.getChannel()); String body = new String(message.getBody()); // 处理接收到的消息 System.out.println("Received message: " + body + " from channel: " + channel); } } ``` 4. 在需要发布消息的地方,通过调用消息发布者类的publish方法来发布消息。以下是一个示例代码: ```java @Autowired private MessagePublisher messagePublisher; public void sendMessage(String channel, Object message) { messagePublisher.publish(channel, message); } ``` 5. 在需要订阅消息的地方,通过在消息订阅者类的方法上添加@RedisListener注解来监听指定的频道。以下是一个示例代码: ```java @RedisListener(channels = "myChannel") public void handleMessage(String message) { // 处理接收到的消息 System.out.println("Received message: " + message); } ``` 通过以上步骤,你就可以使用Spring Boot与Redis结合实现消息队列了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值