17,消息队列:秒杀时如何处理每秒上万次的下单请求?
消息队列看作暂时存储数据的一个容器,认为它是一个平衡低速系统和高速系统处理任务时间差的工具。
秒杀场景下的削峰填谷
将秒杀请求暂存在消息队列中,然后业务服务器会响应用户 「秒杀结果正在计算中」,释放了系统资源之后再处理其它用户的请求。
在后台启动若干个队列处理程序,消费消息队列中的消息,再执行校验库存、下单等逻辑。因为只有有限个队列处理线程在执行,所以落入后端数据库上的并发请求是有限的 。而请求是可以在消息队列中被短暂地堆积,当库存被消耗完之后,消息队列中堆积的请求就可以被丢弃了 。
通过异步处理简化秒杀请求中的业务流程
其实在大量的写请求“攻击”你的电商系统的时候,消息队列除了发挥主要的削峰填谷的作用之外,还可以实现异步处理来简化秒杀请求中的业务流程,提升系统的性能。
你想,在刚才提到的秒杀场景下,我们在处理购买请求时需要 500ms。这时你分析了一下整个的购买流程,发现这里面会有主要的业务逻辑,也会有次要的业务逻辑:比如说,主要的流程是生成订单、扣减库存;次要的流程可能是我们在下单购买成功之后会给用户发放优惠券,会增加用户的积分。
解耦实现秒杀系统模块之间松耦合
除了异步处理和削峰填谷以外,消息队列在秒杀系统中起到的另一个作用是解耦合。
比如数据团队对你说,在秒杀活动之后想要统计活动的数据,借此来分析活动商品的受欢迎程度、购买者人群的特点以及用户对于秒杀互动的满意程度等等指标。而我们需要将大量的数据发送给数据团队,那么要怎么做呢?
总结
1、削峰填谷是消息队列的主要作用
2、分离业务异步处理更能增加大系统处理能力
3、解耦可以提升系统的鲁棒性
18,消息投递:如何保证消息仅仅被消费一次?
消息为什么会丢失
如果要保证消息只被消费一次,首先就要保证消息不会丢失。那么消息从被写入到消息队列到被消费者消费完成,这个链路上会有哪些地方存在丢失消息的可能呢?其实主要存在三个场景:
消息从生产者写入到消息队列的过程;
消息在消息队列中的存储场景;
消息被消费者消费的过程。
1、在消息生产的过程中丢失消息
针对这种情况,我建议你采用的方案是消息重传。也就是当你发现发送超时后就将消息重新发一次,但也不能无限制地重传消息。一般来说,如果不是消息队列发生故障或者是到消息队列的网络断开了,重试 2~3 次就可以了。
不过这种方案可能会造成消息的重复,从而在消费的时候重复消费同样的消息。
2、在消息队列中丢失消息
拿 Kafka 举例,消息在 Kafka 中是存储在本地磁盘上的,而为了减少消息存储时对磁盘的随机 I/O,我们一般会将消息先写入到操作系统的 Page Cache 中,然后再找合适的时机刷新到磁盘上。
比如 Kafka 可以配置当达到某一时间间隔或者累积一定的消息数量的时候再刷盘,也就是所说的异步刷盘。
不过如果发生机器掉电或者机器异常重启,Page Cache 中还没有来得及刷盘的消息就会丢失了。那么怎么解决呢?
你可能会把刷盘的间隔设置很短或者设置累积一条消息就就刷盘,但这样频繁刷盘会对性能有比较大的影响,而且从经验来看,出现机器宕机或者掉电的几率也不高,所以我不建议你这样做。
如果系统对消息丢失的容忍度很低,你可以考虑以集群方式部署 Kafka 服务,通过部署多个副本备份数据保证消息尽量不丢失。
如果你需要确保消息一条都不能丢失,那么建议不要开启消息队列的同步刷盘,而是用集群的方式来解决,可以配置当所有 ISR Follower 都接收到消息才返回成功。
如果对消息的丢失有一定的容忍度,那么建议不部署集群,即使以集群方式部署,也建议配置只发送给一个 Follower 就可以返回成功了。
我们的业务系统一般对于消息的丢失有一定的容忍度,比如说以上面的红包系统为例,如果红包消息丢失了,我们只要后续给没有发送红包的用户补发红包就好了。
3、在消费的过程中存在消息丢失的可能
所以,在这里你需要注意的是,一定要等到消息接收和处理完成后才能更新消费进度(保守的策略),但是这也会造成消息重复的问题。
如何保证消息只被消费一次
从上面的分析中你能发现,为了避免消息丢失我们需要付出两方面的代价:一方面是性能的损耗,一方面可能造成消息重复消费。
性能的损耗我们还可以接受,因为一般业务系统只有在写请求时才会有发送消息队列的操作,而一般系统的写请求的量级并不高,但是消息一旦被重复消费就会造成业务逻辑处理的错误。那么我们要如何避免消息的重复呢?
2、在生产、消费过程中增加消息幂等性的保证
消息在生产和消费的过程中都可能会产生重复,所以你要做的是在生产过程和消费过程中增加消息幂等性的保证,这样就可以认为从“最终结果上来看”消息实际上是只被消费了一次的。
在消息生产过程中,在 Kafka0.11 版本和 Pulsar 中都支持“producer idempotency”的特性,翻译过来就是生产过程的幂等性,这种特性保证消息虽然可能在生产端产生重复,但是最终在消息队列存储时只会存储一份。
而在消费端,幂等性的保证会稍微复杂一些,你可以从通用层和业务层两个层面来考虑。
在通用层面,你可以在消息被生产的时候使用发号器给它生成一个全局唯一的消息 ID,消息被处理之后把这个 ID 存储在数据库中,在处理下一条消息之前先从数据库里面查询这个全局 ID 是否被消费过,如果被消费过就放弃消费。
你可以看到,无论是生产端的幂等性保证方式还是消费端通用的幂等性保证方式,它们的共同特点都是为每一个消息生成一个唯一的 ID,然后在使用这个消息的时候,先比对这个 ID 是否已经存在,如果存在则认为消息已经被使用过。
总结
消息的丢失可以通过生产端的重试、消息队列配置集群模式,以及消费端合理处理消费进度三个方式来解决。
为了解决消息的丢失通常会造成性能上的问题以及消息的重复问题。
通过保证消息处理的幂等性可以解决消息的重复问题。
19,消息队列:如何降低消息队列系统中消息的延迟?
什么是消息延迟?
消息队列在消费过程中大量堆积就是消息延迟,也就是消费的频率跟不上生产。比方说,生产者向队列中一共生产了1000条消息,某一个消费者消费进度是900条,那么这个消费者的消费延迟就是100条消息。
如何监控消息延迟
使用消息队列提供的工具,通过监控消息的堆积来完成;
通过生成监控消息的方式来监控消息的延迟情况。
减少消息延迟的正确姿势
想要减少消息的处理延迟,需要在消费端和消息队列两个层面来完成。在消费端,我们的目标是提升消费者的消息处理能力,你能做的是:
优化消费代码提升性能;
增加消费者的数量(这个方式比较简单)。
再来说说消息队列本身在读取性能优化方面做了哪些事情。
我曾经也做过一个消息中间件,在最初设计中间件的时候,我主要从两方面考虑读取性能问题:
消息的存储;
零拷贝技术。
20,面试现场第 2 期:当问到项目经历时,面试官究竟想要了解什么?(略)
期中测试丨10 道高并发系统设计题目自测(略)
架构设计和高并发系列
读书_大型网站技术架构01_李智慧
读书_大型网站技术架构02_李智慧
读书_大型网站技术架构03_李智慧
读书_高并发设计40问之一基础
读书_高并发设计40问之二数据库
读书_高并发设计40问之三缓存
读书_高并发设计40问之四消息队列
读书_高并发设计40问之五分布式服务
读书_w3c架构师01通用设计与方法论
读书_w3c架构师02典型架构实践
读书_w3c架构师03数据库与缓存
分布式事务
高并发之缓存
高并发之降级
高并发之限流
数据库_读写分离
消息队列_01消息队列入门
消息队列_02rabbitMQ入门
消息队列_03rabbitMQ安装和使用