springboot redis简单消息队列实现

消息队列(MQ),核心:都是「一发一存一消费」,再直白点就是一个「转发器」。

消息队列

1、核心优点

解耦、异步、削峰限流

2、缺点

系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!

系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!

一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!

MQ应用有很多,比如ActiveMQ,RabbitMQ,Kafka等,但是也可以基于redis来实现,可以降低系统的维护成本和实现复杂度,redis中实现消息队列的几种方案。

1. 基于List的 LPUSH+BRPOP 的实现
2. PUB/SUB,订阅/发布模式
3. 基于Sorted-Set的实现
4. 基于Stream类型的实现

基于List的 LPUSH+BRPOP 的实现

使用rpushlpush操作入队列,lpoprpop操作出队列。

List支持多个生产者和消费者并发进出消息,每个消费者拿到都是不同的列表元素。

但是当队列为空时,lpop和rpop会一直空轮训,消耗资源;所以引入阻塞读blpop和brpop(b代表blocking),阻塞读在队列没有数据的时候进入休眠状态,一旦数据到来则立刻醒过来,消息延迟几乎为零。

上代码,使用redisTemplate

public class RedisQueue {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //---------取数据---------

    public void bRPopLPush( String key, int TIME_OUT, Consumer<Object> consumer) throws Exception {
        if (TIME_OUT <= 0) {  //禁止不超时阻塞
            throw new Exception("阻塞等待时长TIME_OUT 必须大于0!");
        }

        String srcKey = getKey(key);
        String dstKey = getBackupKey(key);
        while (true) {
            boolean sucess = false;
            Object obj = null;
            try {
                obj = redisTemplate.opsForList().rightPopAndLeftPush(srcKey, dstKey,
                        TIME_OUT, TimeUnit.SECONDS);
                if (obj != null) {
                    consumer.accept(obj);
                    sucess = true;
                }
            } catch (Exception ignored) {
                // 防止获取key达到超时时间抛出QueryTimeoutException异常退出
            } finally {
                if (sucess) {
                    // 处理成功才删除备份队列的key
                    redisTemplate.opsForList().remove(dstKey, 1, obj);
                }
            }
        }
    }

    public Object blockingConsume(String key, int TIME_OUT) throws Exception {
        if (TIME_OUT <= 0) {  //禁止不超时阻塞
            throw new Exception("阻塞等待时长TIME_OUT 必须大于0!");
        }

        try {
            Object obj = redisTemplate.opsForList().rightPop(key, TIME_OUT, TimeUnit.SECONDS);
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Object consume(String key){
        try {
            Object obj = redisTemplate.opsForList().rightPop(key);
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //---------存数据---------

    public Long produce(String key, Object value) {
        try {
            //执行 LPUSH 命令后,列表的长度。
            Long size = redisTemplate.opsForList().leftPush(key, value);
            return size;
        } catch (Exception e) {
            e.printStackTrace();
            return -1L;
        }
    }

    private String getKey(String key) {
        return key;
    }

    private String getBackupKey(String key) {
        return key + "_bak";
    }

}

注意

如果线程一直阻塞在那里,Redis客户端的连接就成了闲置连接,闲置过久,服务器一般会主动断开连接,减少闲置资源占用,这个时候blpop和brpop或抛出异常,所以在编写客户端消费者的时候要小心,需要注意捕获到异常还有重试

缺点:

做消费者确认ACK麻烦,不能保证消费者消费消息后是否成功处理的问题(宕机或处理异常等),通常需要维护一个Pending列表,保证消息处理确认。

不能重复消费,一旦消费就会被删除

不支持分组消费

优化点

基于List的 LPUSH+BRPOPLPUSH+LREM 的实现

为了防止消息丢失,使用bRPopLPush。在消费端取到消息的同时原子性的把该消息放入一个正在处理中的 doingKey 列表(进行备份)。业务处理完成后从正在处理中的 doingKey 列表 lrem 删除当前已经处理 ok 的消息。

正在处理中的队列存在长期不消费的消息怎么办? 可以再添加一台客户端来监控长期不消费的消息,重新将消息打回待消费的队列;这个可以使用循环队列的模式来实现:使用同一个list,从尾部出队的同时,从头部入队,如果没有异常则删除头部入队消息,如果出现异常,那么一直循环处理,当然这种处理有局限性 (慎用!很容易出问题,多线程直接爆炸)。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现延迟消息队列可以使用 Redis 的 sorted set 数据结构,其中 score 表示消息的执行时间戳,member 表示消息内容。每隔一段时间轮询一次 sorted set,将 score 小于当前时间的消息取出来执行即可。 下面是一个基于 Spring Boot 和 Redis简单实现代码: 首先引入 Redis 相关依赖,在 pom.xml 文件中添加以下内容: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 然后创建一个消息实体类 DelayMessage,用于存储消息内容和执行时间戳: ```java public class DelayMessage { private String message; private long executeTime; // getter 和 setter 方法省略 } ``` 接着创建一个 Redis 延迟消息队列的服务 DelayQueueService,其中包含添加消息和轮询消息的方法: ```java @Service public class DelayQueueService { private static final String DELAY_QUEUE_KEY = "delay_queue"; @Autowired private RedisTemplate<String, Object> redisTemplate; // 添加延迟消息 public void addDelayMessage(DelayMessage message) { redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, message, message.getExecuteTime()); } // 轮询延迟消息 public List<DelayMessage> pollDelayMessage() { long currentTime = System.currentTimeMillis(); Set<Object> messages = redisTemplate.opsForZSet().rangeByScore(DELAY_QUEUE_KEY, 0, currentTime); if (messages != null && !messages.isEmpty()) { List<DelayMessage> result = new ArrayList<>(); for (Object message : messages) { redisTemplate.opsForZSet().remove(DELAY_QUEUE_KEY, message); result.add((DelayMessage) message); } return result; } return null; } } ``` 最后在需要使用延迟消息队列的地方调用 addDelayMessage 方法添加消息,轮询消息可以单独开一个线程或者使用定时任务实现。 注意:以上代码只是一个简单的示例,实际使用中需要考虑分布式环境下的并发问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值