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


Kafka 中的时间轮的实现是 TimingWheel 类 , 位于 kafka.utils.timer 包中 。 基于Kafka的时间轮同样可以得到O(1)时间复杂度 , 性能上还是不错的 。
基于Kafka的时间轮的实现方式 , 在实现方式上有点复杂 , 需要依赖kafka , 但是他的稳定性和性能都要更高一些 , 而且适合用在分布式场景中 。
六、RocketMQ延迟消息相比于Kafka来说 , RocketMQ中有一个强大的功能 , 那就是支持延迟消息 。


延迟消息 , 当消息写入到Broker后 , 不会立刻被消费者消费 , 需要等待指定的时长后才可被消费处理的消息 , 称为延时消息 。
有了延迟消息 , 我们就可以在订单创建好之后 , 发送一个延迟消息 , 比如20分钟取消订单 , 那就发一个延迟20分钟的延迟消息 , 然后在20分钟之后 , 消息就会被消费者消费 , 消费者在接收到消息之后 , 去关单就行了 。
但是 , RocketMQ的延迟消息并不是支持任意时长的延迟的 , 它只支持:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h这几个时长 。 (商业版支持任意时长)
可以看到 , 有了RocketMQ延迟消息之后 , 我们处理上就简单很多 , 只需要发消息 , 和接收消息就行了 , 系统之间完全解耦了 。 但是因为延迟消息的时长受到了限制 , 所以并不是很灵活 。
如果我们的业务上 , 关单时长刚好和RocketMQ延迟消息支持的时长匹配的话 , 那么是可以基于RocketMQ延迟消息来实现的 。 否则 , 这种方式并不是最佳的 。
七、RabbitMQ死信队列延迟消息不仅在RocketMQ中支持 , 其实在RabbitMQ中也是可以实现的 , 只不过其底层是基于死信队列实现的 。
当RabbitMQ中的一条正常的消息 , 因为过了存活时间(TTL过期)、队列长度超限、被消费者拒绝等原因无法被消费时 , 就会变成Dead Message , 即死信 。

当一个消息变成死信之后 , 他就能被重新发送到死信队列中(其实是交换机-exchange) 。
那么基于这样的机制 , 就可以实现延迟消息了 。 那就是我们给一个消息设定TTL , 然但是并不消费这个消息 , 等他过期 , 过期后就会进入到死信队列 , 然后我们再监听死信队列的消息消费就行了 。
而且 , RabbitMQ中的这个TTL是可以设置任意时长的 , 这就解决了RocketMQ的不灵活的问题 。
但是 , 死信队列的实现方式存在一个问题 , 那就是可能造成队头阻塞 , 因为队列是先进先出的 , 而且每次只会判断队头的消息是否过期 , 那么 , 如果队头的消息时间很长 , 一直都不过期 , 那么就会阻塞整个队列 , 这时候即使排在他后面的消息过期了 , 那么也会被一直阻塞 。
基于RabbitMQ的死信队列 , 可以实现延迟消息 , 非常灵活的实现定时关单 , 并且借助RabbitMQ的集群扩展性 , 可以实现高可用 , 以及处理大并发量 。 他的缺点第一是可能存在消息阻塞的问题 , 还有就是方案比较复杂 , 不仅要依赖RabbitMQ , 而且还需要声明很多队列(exchange)出来 , 增加系统的复杂度
八、RabbitMQ插件其实 , 基于RabbitMQ的话 , 可以不用死信队列也能实现延迟消息 , 那就是基于rabbitmq_delayed_message_exchange插件 , 这种方案能够解决通过死信队列实现延迟消息出现的消息阻塞问题 。 但是该插件从RabbitMQ的3.6.12开始支持的 , 所以对版本有要求 。
这个插件是官方出的 , 可以放心使用 , 安装并启用这个插件之后 , 就可以创建x-delayed-message类型的队列了 。
前面我们提到的基于私信队列的方式 , 是消息先会投递到一个正常队列 , 在TTL过期后进入死信队列 。 但是基于插件的这种方式 , 消息并不会立即进入队列 , 而是先把他们保存在一个基于Erlang开发的Mnesia数据库中 , 然后通过一个定时器去查询需要被投递的消息 , 再把他们投递到x-delayed-message队列中 。