【面试题】Redis实现延时队列

背景

        事情是这样的,前几天up主在跟一个面试官聊天,聊到消息队列kafka,然后提到,怎样实现消息的延迟发送。当时内心状态是这样的。

哈哈哈,怎么可能,武功再高也怕菜刀,当时就上演了一出乱拳打死老师傅的戏码。

不扯了,言归正传,当时的思路是这样的,延迟发送么,把kafka换掉不就行了,rocketMQ都帮你实现好了,哈哈哈。是不是既简单又粗暴。但是这样说出来又显得UP主无能,这不行,才高八斗,玉树临风,风靡万千少女的我怎么可能吃瘪,不可能,绝对不可能

当时就想到,不就延时么,加个时间就可以了么,搞一张数据表,数据丢进去,加个时间,轮询,发送,结束!NO NO NO

细细想来就会发现问题,消息少了还行,多了就不行了,还会卡性能,灵光一现,Redis是个好东西啊,基于内存,读写性能强大到爆炸,就它了。

        Redis的物种数据结构中有一种是ZSET数据结构,非常适合现在的这种场景,在这种数据结构中每个元素都有score和value,我们可以将消息产的时间作为score,这样在Redis中的消息就是根据时间排序的有序集合,每次在里面取出时间戳最小的消息消费即可,消费结束删除,到这里基本思路就出来了,直接上代码,开搞!

引入基础包

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

获取延时消息(代码仅供延时,提供思路) 

 /**
     * 获取延时队列中已到期的消息
     */
    public List<String> getExpiredMessages() {
        //获取到开始时间
        long minScore = 0;
        //获取到结束时间
        long maxScore = System.currentTimeMillis();
        //获取到指定范围区间的数据列表
        Set<Object> messages = Collections.singleton(redis.zRangeByInx("KEY", minScore, maxScore));
        if (messages == null || messages.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> result = new ArrayList<>();
        for (Object message : messages) {
            String delayMessage = JSONObject.parseObject(JSON.toJSONString(message), DelayMessage.class);
            result.add(delayMessage);
        }
        return result;
    }

这样基本功能就有了,开启一个定时任务,直接在池子里取数据就行了,大概实现就是,当消息产生的时候,就将时间戳作为score放到ZSET集合里面,消费消息在里面按照时间先后顺序取出,消费完删除,看到这里是不是就觉得可以了啊,完美!NO NO NO

        上面只是实现了最基础的功能,仔细观察你会发现,当数据量多的时候,消息的消费就会变慢,甚至会非常慢,因为上面的设计思路是串行执行的,如果生产者生成了一条消息,二消费者在半小时后才收到

        所以要解决上面的问题,就需要解决消息消费慢的问题,更好的支持业务,那就需要引入线程池,并发消费,十个人干活总比一个人干活要快吧(不一定),废话不多说,上代码

 List<String> messages =getExpiredMessages();
        List<List<String>> partition = Lists.partition(messages, 10);
        List<CompletableFuture<List<SectionOutputDTO>>> completableFutureList = partition.stream().map(
                message -> CompletableFuture.runAsync(() ->
                               sendMessage(message),
                        taskExecutor)).collect(Collectors.toList());
        CompletableFuture<Void> allOf = CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]));
       allOf.thenRun(()->{
           redis.rovome(messages);
       })

        这样并发处理消息,而且引入单独的线程池,不会影响主系统流程,而且每次消费完都会清除已经消费的消息,防止了重复消费,按照这种思路搞起码不会出什么大问题。

重点

1.redis的ZSET数据结构特点

2.消息消费尽可能实时,延迟发送也要准确

3.redis的读写性能

4.并发控制

5.消息重复消费问题

思考

最后问下小伙伴,如果说不用redis还有其他实现方式吗,评论区打出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值