数据库|领导:谁再用 Redis 实现过期订单关闭,立马滚蛋!!( 四 )


基于RabbitMQ插件的方式可以实现延迟消息 , 并且不存在消息阻塞的问题 , 但是因为是基于插件的 , 而这个插件支持的最大延长时间是(2^32)-1 毫秒 , 大约49天 , 超过这个时间就会被立即消费 。 但是他基于RabbitMQ实现 , 所以在可用性、性能方便都很不错
九、Redis过期监听很多用过Redis的人都知道 , Redis有一个过期监听的功能 ,
在 redis.conf 中 , 加入一条配置notify-keyspace-events Ex开启过期监听 , 然后再代码中实现一个KeyExpirationEventMessageListener , 就可以监听key的过期消息了 。
这样就可以在接收到过期消息的时候 , 进行订单的关单操作 。
这个方案不建议大家使用 , 是因为Redis官网上明确的说过 , Redis并不保证Key在过期的时候就能被立即删除 , 更不保证这个消息能被立即发出 。 所以 , 消息延迟是必然存在的 , 随着数据量越大延迟越长 , 延迟个几分钟都是常事儿 。
而且 , 在Redis 5.0之前 , 这个消息是通过PUB/SUB模式发出的 , 他不会做持久化 , 至于你有没有接到 , 有没有消费成功 , 他不管 。 也就是说 , 如果发消息的时候 , 你的客户端挂了 , 之后再恢复的话 , 这个消息你就彻底丢失了 。 (在Redis 5.0之后 , 因为引入了Stream , 是可以用来做延迟消息队列的 。 )
十、Redis的zset虽然基于Redis过期监听的方案并不完美 , 但是并不是Redis实现关单功能就不完美了 , 还有其他的方案 。

我们可以借助Redis中的有序集合——zset来实现这个功能 。
zset是一个有序集合 , 每一个元素(member)都关联了一个 score , 可以通过 score 排序来取集合中的值 。
我们将订单超时时间的时间戳(下单时间+超时时长)与订单号分别设置为 score 和 member 。 这样redis会对zset按照score延时时间进行排序 。 然后我们再开启redis扫描任务 , 获取”当前时间 > score”的延时任务 , 扫描到之后取出订单号 , 然后查询到订单进行关单操作即可 。
使用redis zset来实现订单关闭的功能的优点是可以借助redis的持久化、高可用机制 。 避免数据丢失 。 但是这个方案也有缺点 , 那就是在高并发场景中 , 有可能有多个消费者同时获取到同一个订单号 , 一般采用加分布式锁解决 , 但是这样做也会降低吞吐型 。
但是 , 在大多数业务场景下 , 如果幂等性做得好的 , 多个消费者取到同一个订单号也无妨 。
十一、Redisson上面这种方案看上去还不错 , 但是需要我们自己基于zset这种数据结构编写代码 , 那么有没有什么更加友好的方式?
有的 , 那就是基于Redisson 。
Redisson是一个在Redis的基础上实现的框架 , 它不仅提供了一系列的分布式的Java常用对象 , 还提供了许多分布式服务 。

Redission中定义了分布式延迟队列RDelayedQueue , 这是一种基于我们前面介绍过的zset结构实现的延时队列 , 它允许以指定的延迟时长将元素放到目标队列中 。
其实就是在zset的基础上增加了一个基于内存的延迟队列 。 当我们要添加一个数据到延迟队列的时候 , redission会把数据+超时时间放到zset中 , 并且起一个延时任务 , 当任务到期的时候 , 再去zset中把数据取出来 , 返回给客户端使用 。
大致思路就是这样的 , 感兴趣的大家可以看一看RDelayedQueue的具体实现 。
基于Redisson的实现方式 , 是可以解决基于zset方案中的并发重复问题的 , 而且还能实现方式也比较简单 , 稳定性、性能都比较高 。
总结我们介绍了11种实现订单定时关闭的方案 , 其中不同的方案各自都有优缺点 , 也各自适用于不同的场景中 。 那我们尝试着总结一下: