PHP 转 Go 系列 | ThinkPHP 与 Gin 框架之 Redis 延时消息队列技术实践
我们在某宝或某多多上抢购商品时,如果只是下了订单但没有进行实际的支付,那在订单页面会有一个支付倒计时,要是过了这个时间点那么订单便会自动取消。在这样的业务场景中,一般情况下就会使用到延时队列。
通常在客户下单之后,就会将订单数据推送到延时队列中并且会对该消息设置一个延时时长,比如设置五分钟、十分钟、或十五分钟等,具体的时长应该还是要结合当前的业务进行衡量,然后消费端会在指定时间到达后就对该消息进行支付支付状态判断,如果已经支付则不予处理,要还是未支付,则会取消该订单,并且释放商品库存。
我们这次分享的内容,主要是基于 Redis 延时队列的实现方式,当然除了 Redis 还可以用其他的技术,比如 RabbitMQ、Kafka、RocketMQ 等专业的消息队列。但是我用 Redis 的原因是,它的应用场景比较广泛,我们平时接触也比较多,而且相对于专业的消息队列它没有过多复杂的配置,学起来容易上手,出了问题解决起来也快,学东西的路径都是由易到难嘛。
另外,如果你对上面提到的专业消息队列使用很熟练,也可以将 Redis 更换成它们,这里只是存储介质的不同,技术的实现逻辑上没有太大区别,重要的是设计思想,大家各取所需吧。
好了,我先介绍一下这次延时队列的实现逻辑。主要分为三个部分,一是:消息的发送,如果设置了延时时间则会将消息存储到 Redis 的延时队列中,反之会直接将消息推送到 Redis 的就绪队列中等待消费。二是:将到期的消息从 Redis 延时队列中取出,并且推送到 Redis 的就绪队列中等待消费。三是:消费端会从 Redis 的就绪队列中按顺序读取出消息,并且执行对应的业务处理逻辑,如果处理失败则会将该消息,再次推送到 Redis 的延时队列中进行下一次的重试。
这里说到的延时队列是利用 Redis 有序集合来实现的,它每间隔一秒钟就会被轮询一次,如果有到期的消息,则就会将该消息推送到 Redis 就绪队列,并且从该集合中移除过期的消息,至此就可以等待着消费端进行消费了。接下来我们就从实际的代码出发,来看一下如何实现基于 Redis 的延时队列。
话不多说,开整!我们先来看一下整体的项目目录结构,内容主要分为 PHP 和 Go 两部分。
ThinkPHP
使用 composer 创建基于 ThinkPHP 框架的 php_delay 项目。
这个就是延时队列实现的核心类,定义了就绪、延时、失败三个消息队列。send()
方法用于发送消息,其中可以指定 $delay
参数设置延时时间单位是秒。wait()
方法用于消费端监听消息,从下面的代码可以看出这里还利用多进程,父进程的作用是每间隔一秒钟,就从 Redis 有序集合中读取到期的消息,并将该消息推送到 Redis 就绪队列,子进程则阻塞监听就绪队列的消息,并且将接收到的消息回调到用户自定义的业务函数中。
这个是消费端脚本,主要是实现在接收到消息之后,进行具体的业务逻辑处理。
这个是通过 API 接口将消息,推送到延时队列中。
我们来实际测试一下,先执行 php think consumer
启动消费者,然后再执行 php think run
启动服务,最后使用 Postman 工具进行调用。
Gin
通过 go mod 初始化 go_delay 项目。
这里和上面 PHP 中的实现逻辑都差不多,有一点值得注意的是在 Go 中是利用协程来异步从 Redis 有序集合中轮询到期的消息,而 PHP 是利用的多进程。
使用 go extend.InitQueue()
启动了一个消费者。从这里可以看出在 Go 中不需要单独启动一个消费者脚本进程,只需启动一个异步的协程即可监听消息,因此在 Go 中实现 Redis 延时队列相较于 PHP 要方便很多。
这个是通过 API 接口将消息,推送到延时队列中。
我们直接执行 go run main.go
启动服务,然后使用 Postman 工具进行调用。
结语
看到这里我相信大家已经对基于 Redis 延时队列的实现方式,有所了解了。从上面的例子中可以看出来,这次延时队列用到的核心数据结构是 Redis 的列表和有序集合。有序集合主要用于存放设置了延时时长的消息,而列表存放的是就绪的消息,即等着被消费者消费的消息。
从 PHP 和 Go 两者语言的区别来看,在 PHP 中需要单独启动消费者脚本,还有在轮询有序集合中到期的消息,也需要在额外的进程中进行,不然就会阻塞消息的消费逻辑。而在 Go 中只需要异步开启一个协程就可以等待消息的到来,轮询到期的消息也再另外开启一个协程便可以完成对应的操作,单从这一点就可以看出 Go 的优势比 PHP 的要大。
此外,在 Go 语言中还可以利用通道 Channel 来替代 Redis,同样也可以实现延时队列,不过 Channel 不能持久化到磁盘,一旦服务挂了消息就丢失了,所以还是老老实实用 Redis 的好。再好的技术知识,也需要亲自来实践才能吸收,所以建议大家手动实践一下,如果有想要获取完整案例代码的朋友,可以在公众号内回复「8392」即可,本次分享的内容就到这里结束了,希望对大家能有所帮助。
感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。
文章转载自:Yxh_blogs
评论