你不知道的队列使用技巧

队列

队列是一种先进先出的数据结构

我们的程序在什么情况下会用到队列呢?异步处理

场景

我们一般写的web程序都是同步执行的,比如前端发送一个登录请求,后端一步一步的处理,查询用户,判断密码等等,返回登陆成功或者错误信息,前端阻塞等到后端返回后进行下一步处理。

那么这种程序有一个什么问题呢,首先,前后端建立的http连接是有超时时间的,当后端处理请求时间过长会返回超时错误。

就算没有超时的限制,对于用户体验来讲,当你的程序响应很慢,那么用户就会觉得,这什么玩意,真垃圾!!!

导出场景

基于这些原因,我们可以需要快速的处理用户请求。像导出大文件的时候,没办法很快的给用户响应怎么办呢,我们可以用异步的方式处理,先给用户返回正在导出,或者导出成功。

这个时候我们后台程序真的导出了嘛?没有,我们可以将这个导出请求放入一个队列中,等待另外一个处理程序将队列中的数据取出进行处理,这个处理程序一直监听队列,如果发现队列有消息,就去取出来进行处理。在处理程序中可以根据数据类型的不同,把它送到具体的处理类中,比如导出数据,处理程序会分发给导出类进行处理,由导出类处理完成后,返回处理完成。

这时候我们可以有一个下载管理进行下载,或者发送站内信,邮件等方式将导出文件发给用户。

这个案例中,用户发起导出请求后,可以快速得到响应,而且这种方式还有一个作用,那就是松耦合,他将我们的具体处理程序分离开了,不在耦合到一起。

除了这个还有什么作用嘛,有,秒杀!

秒杀场景

我们经常能遇到10点秒杀,限量100件这种。那他是怎么实现的呢,要知道,当9点59的时候,很多用户就会不断刷新,等待10点开枪。

首先,不断刷新页面,就会有不断的请求过来,几千万的查询请求一瞬间涌入服务器,这个还好说,可以用缓存解决。那一到10点,他们全部都发出购买请求,但是这时候,我们只有100件卖出,100个人能买到,其他人都买不到。这时候我们也可以使用消息队列,先将购买请求放入消息队列,返回正在抢购,然后队列处理程序取出消息进行处理,取出一条就卖出相应的数量,进行库存的删减。我们在做这些操作的时候,为了防止超卖,最好是加上锁,只有拿到锁的程序才可以进行处理,后面的程序会阻塞直到锁被解开。这样的话数量上就会有保证了,但是这样会消耗性能。

这次消息队列使用其实相当于把瞬间的高并发流量挡住了,然后慢慢处理,缓解我们服务器的压力。

其他场景

其实很多时候都可以使用消息队列来解耦,异步处理。只要我们的主要流程不受影响就可以了。比如我之前的一个项目,在注册时候,如果填写了邀请码就会发放优惠券,这个步骤就是可以放到消息队列中处理的。还有比如群发通知这种,也可以放到消息队列来处理。

总结来说:

  • 解耦
  • 异步处理
  • 削峰填谷

缺点

消息队列虽然有这些好处,但是也存在问题,比如注册发放优惠券,但是优惠券没有发放成功怎么办,为什么出现这种问题呢,有可能是消息丢失,或者处理失败。

  • 消息放入消息队列的时候丢失
  • 消息在消息队列中丢失
  • 消息在处理的时候丢失

消息在放入消息队列的时候丢失

这种时候消息丢失一般是因为网络抖动,队列宕机才会发生。为了防止因为这些产生的消息丢失,可以发送没成功的时候重复发送几次,一般重试2-3次就可以了。

但是这样可能会导致消息处理端重复处理,为了解决这种情况,可以给每一个消息生产一个唯一id。

消息在消息队列中丢失

消息存储在消息队列中的时候,不是立即写入磁盘的。为了减少磁盘io,会先将信息写入系统的Page Cache,当Page Cache中的数据达到一定量或者达到一定时间的时候会一起写入磁盘中进行持久化的存储,那再还没有写入磁盘,系统宕机导致系统的Page Cache丢失,那这时候这些数据就会丢失了。

解决方案:

可以布置队列的集群,这样就算一个宕机还有其他的存储着信息不会导致信息丢失。

Kafka集群中,由leader负责写入和消费,有多个follower负责数据的备份,有些follower被叫做ISR(in-sync replicas)。消息进入kafka会由leader先写入,然后异步的同步给其他follower,当leader挂掉之后,会从ISR中选取新的leader,由于leader是异步发送给follower的,那如果还没发送leader就挂掉了,消息还是会丢失。kafka中提供了’acks’这么一个选项,这个选项设置为’all’的话,会等到leader同步给所有follower之后才返回写入成功,这样会保证消息不丢失,但是会降低性能。

消息在处理的时候丢失

这里除了消息丢失还有消息重复的问题,消息丢失可能是网络抖动或者处理程序错误引起的。

消息重复是因为消息重复放入了队列,前面也提到了,可以给每一个消息生产一个唯一id。我们收到消息之后先判断这个id是否被处理过,如果已经处理就丢弃这个消息。如果没有处理,则进行处理。

注意事项

在使用队列的时候,我们需要监控队列的使用情况,比如队列中堆积了多少未处理信息,当堆积信息越来越多的时候我们就要思考为什么堆积了,是什么原因导致的。

除了看堆积信息,还可以发送特殊的监控信息到队列中,处理程序接受到监控信息的时候,对比它的进入时间,看看时间差是多少,如果超过了一定阈值,那么就需要注意了,我们的程序可能处理太慢了。

解决方案:

  • 横向扩展
    横向扩展其实有点类似集群,我们可以部署多个处理程序,来加快处理进度

  • 纵向扩展
    纵向扩展就是提高我们处理程序的性能,优化他的代码,加快处理时间。

还可以提高队列的存储介质,这样也可以加快队列的速度。

一个消息从队列取出的过程:

  • 读取存储到系统缓冲区
  • 从系统缓冲区到用户缓冲区
  • 从用户缓冲区到socket缓冲区
  • 从socket缓冲区到网卡缓冲区

我们如果能加快这四个步骤,也是可以加快队列的速度的。

比如操作系统提供的sendfile函数。

感谢极客时间的唐扬老师开设的《高并发系统设计》专栏,这里的内容都是学完以后自己的总结,提炼,思考。

发布了30 篇原创文章 · 获赞 6 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/Thepatterraining/article/details/105344675