PHP消息队列学习
在我们平常网站设计时,会遇到“给用户群发短信”,“商城订单系统大批量订单处理”,“商城秒杀活动”等需求,这些功能,都有一个共同的特点:就是在面对高迸发的同时,必须要保证系统处理数据的有效性。那么如何处理这些数据,“消息队列”就是很好的选择。
接下来我们主要了解以下知识:
1.队列是什么东西?它能做哪些事情?
2.队列的应用场景有哪些?
3.如何使用队列对业务进行解耦?
4.如何使用redis队列来缓解系统压力?
一、认识消息队列
1.1 消息队列概念
从本质上来说消息队列就是一个队列结构的中间件,就是说消息放入(即入队)这个中间件之后可以不马上处理,而等待另外一个程序进行处理读取(出队)这些消息数据,并且按照顺序逐次处理。也就是说你的遇到一个并发特别大而且耗时特别长,还不需要马上进程处理返回结果的功能时,可以使用消息队列解决此类问题。
1.2 核心结构
由一个业务系统进行入队,把消息逐次插入到消息队列中,插入成功之后直接返回成功结果,然后另一个消息处理系统,这个系统会把消息系统中的记录逐次读取出来并进行处理,完成出队流程。
1.3 应用场景
数据冗余:比如商城的订单系统,后续需要严格可靠的进行数据转换和记录,消息队列可以把这些订单数据持久化存储在队列中,如果队列中有订单数据,则后续处理程序进行获取,后续处理完之后将队列中与该条订单对应的元素删除,这样就能保证每条订单都能被有效处理,以下是一些消息队列常见的使用场景。
系统解耦:软件开发尽量做到“高内聚,低耦合”,使用消息队列之后,可以做到入队系统和出队系统相互分开,互不影响,假如入队系统出现故障崩溃,出队系统不受影响,依然能够正常运作。
流量削峰:例如商城秒杀和抢购,可以配合缓存来使用消息队列,能够有效顶住瞬间的高访问量,防止服务器因承受不住压力崩溃。
异步通信:消息发送之后,如果消息入队成功之后可以直接返回发送成功。
扩展性:商城订单队列,不仅可以处理订单,还可以给其他业务使用,比如下单成功之后,必然为其生成一条快递派送订单。
排序保证:有些场景需要按照产品的顺序进行处理,比如单进单出从而保证数据按照一定的顺序处理,可以使用消息队列。
1.4 常见队列的实现优缺点
队列介质:
1、数据库,比如mysql(可靠性高,容易实现,数据量大时速度慢)
2、缓存,例如redis(速度快,单个消息报包过大时效率低)
3、消息系统,例如rabbitMq(专业性强,可靠,学习成本高)
消息处理触发机制:
1、死循环方式读取:易实现,故障时无法及时恢复。(比较适合做秒杀,比较集中,运维集中维护)
2、定时任务:压力均分,有处理上限;可选择用户访问量低时段减小服务器压力;目前比较流行的处理触发机制。(唯一缺点是间隔和数据需要注意,不要等上一个任务还没有完成下一个任务又开始了)
3.守护进程:类似于php-fpm和php-cg,需要shell基础
二、解耦案例:队列处理“订单系统”和“配送系统”
对于订单流程,我们可以设计两个系统,即“订单系统”和“配送系统”。在网购中我们都知道,当我们下单之后,在我的订单中可以看到我的货物正在配送中,这时就要参与进来一个“配送系统”。如果在架构时把“订单系统”和“配送系统”设计在一起,就会出现一些问题。对于订单系统来说,因为系统压力较大,但是“配送系统”却没有必要即时做出反应;第二个,我们不希望在订单系统出现故障时,配送系统也无法运行,这个时候两个系统就会相互影响正常运转。所以我们要把两个系统的耦合度降到最低。这个可以用“对列表”来实现两者之间的沟通。
2.1 架构设计
1、首先订单系统接收到用户的订单,然后进行订单的处理。
2、然后会把这些订单信息写到对列表中,这个对列表是沟通订单系统和订单系统的关键。
3、由配送系统定时执行的一个程序来读取队列进行处理。
4、配送系统处理之后,会把已处理的记录进行标记处理
2.2 程序流程
三、流量消峰案例:Redis 的 list 类型实现秒杀/抢购活动
redis 基于内存,它的速度非常快,redis对数据库有一个非常好的补充作用,因为它是可持久化的,redis会周期性把数据写到硬盘中,因此它不需要担心断电问题,redis提供了5种不同的数据类型(字符串,双向表链,哈希,集合,有序集合)。
一般情况下,做秒杀案例,抢购,瞬间高并发,需要排队的案例中,redis是一个很好的选择。
3.1 redis数据类型中的list类型
redis 的list 是一个双向链表,可以从头部或者尾部追加数据。
* LPUSH/LPUSHX :将值插入到(/存在的)列表头部
* RPUSH/RPUSHX: 将值插入到(/存在的)列表尾部
* LPOP : 移除并获取列表的第一个元素
* RPOP: 移除并获取列表的最后一个元素
* LTRIM: 保留指定区间内的元素
* LLEN: 获取列表长度
* LSET: 通过索引设置列表元素的值
* LINDEX: 通过索引获取列表中的元素
* LRANGE: 获取列表指定范围内的元素
3.2 架构设计
1、首先记录哪一个用户参与了秒杀同时记录他的时间。
2、将用户的id存到redis列表中,让它排队。如果规定只有前10个用户可以参与活动,如果列表中个数已经够了就不会让它继续插入数据。这样redis的列表长度就为10个。
3、最后慢慢的将redis中的数据写入到数据库中,以减小数据的压力。
3.3 代码初级设计
1、当用户开始参与秒杀时,将秒杀程序的请求写入Redis(uid, time)中。
2、假如只有10个人可以秒杀成功,检查Redis已经存放数据的长度,达到上限后不再插入,说明秒杀已完成。
3、最后循环处理存入Redis中的10条数据,然后慢慢取数据存入到数据库中。
在秒杀这一块对数据库压力特别大,如果我们直接在用户发起秒杀请求时,每次都查询数据库是否已经达到秒杀人数上限的话,会造成数据库压力巨大。现在通过Redis的一个队列list,然后把秒杀请求放入到Redis里面,最后将秒杀成功的数据通过入库程序写入到数据库,这样的话会极大缓解mysql的压力。
本文参考来自:https://www.cnblogs.com/dump/p/8243868.html