mq使用场景、不丢不重、时序性、削峰前端
参考:web
http://zhuanlan.51cto.com/art/201704/536407.htm数据库
http://zhuanlan.51cto.com/art/201703/535090.htm后端
http://zhuanlan.51cto.com/art/201704/536306.htm数组
http://zhuanlan.51cto.com/art/201611/521602.htm缓存
http://zhuanlan.51cto.com/art/201611/521602.htm安全
http://zhuanlan.51cto.com/art/201703/534752.htm服务器
http://zhuanlan.51cto.com/art/201703/534475.htm微信
微信公众号:架构师之路markdown
1、缘起
一切脱离业务的架构设计与新技术引入都是耍流氓。
引入一个技术以前,首先应该解答的问题是,这个技术解决什么问题。
就像微服务分层架构以前,应该首先回答,为何要引入微服务,微服务究竟解决什么问题(详见《互联网架构为何要作微服务?》)。
最近分享了几篇MQ相关的文章:
很多网友询问,究竟何时使用MQ,MQ究竟适合什么场景,故有了此文。
2、MQ是干吗的
消息总线(Message Queue),后文称MQ,是一种跨进程的通讯机制,用于上下游传递消息。
在互联网架构中,MQ是一种很是常见的上下游“逻辑解耦+物理解耦”的消息通讯服务。
使用了MQ以后,消息发送上游只须要依赖MQ,逻辑上和物理上都不用依赖其余服务。
3、何时不使用消息总线
既然MQ是互联网分层架构中的解耦利器,那全部通信都使用MQ岂不是很好?这是一个严重的误区,调用与被调用的关系,是没法被MQ取代的。
MQ的不足是:
1)系统更复杂,多了一个MQ组件
2)消息传递路径更长,延时会增长
3)消息可靠性和重复性互为矛盾,消息不丢不重难以同时保证
4)上游没法知道下游的执行结果,这一点是很致命的
举个栗子:用户登陆场景,登陆页面调用passport服务,passport服务的执行结果直接影响登陆结果,此处的“登陆页面”与“passport服务”就必须使用调用关系,而不能使用MQ通讯。
不管如何,记住这个结论:调用方实时依赖执行结果的业务场景,请使用调用,而不是MQ。
4、何时使用MQ
【典型场景一:数据驱动的任务依赖】
什么是任务依赖,举个栗子,互联网公司常常在凌晨进行一些数据统计任务,这些任务之间有必定的依赖关系,好比:
1)task3须要使用task2的输出做为输入
2)task2须要使用task1的输出做为输入
这样的话,tast1, task2, task3之间就有任务依赖关系,必须task1先执行,再task2执行,载task3执行。
对于这类需求,常见的实现方式是,使用cron人工排执行时间表:
1)task1,0:00执行,经验执行时间为50分钟
2)task2,1:00执行(为task1预留10分钟buffer),经验执行时间也是50分钟
3)task3,2:00执行(为task2预留10分钟buffer)
这种方法的坏处是:
1)若是有一个任务执行时间超过了预留buffer的时间,将会获得错误的结果,由于后置任务不清楚前置任务是否执行成功,此时要手动重跑任务,还有可能要调整排班表
2)总任务的执行时间很长,老是要预留不少buffer,若是前置任务提早完成,后置任务不会提早开始
3)若是一个任务被多个任务依赖,这个任务将会称为关键路径,排班表很难体现依赖关系,容易出错
4)若是有一个任务的执行时间要调整,将会有多个任务的执行时间要调整
不管如何,采用“cron排班表”的方法,各任务耦合,谁用过谁痛谁知道(采用此法的请评论留言)
优化方案是,采用MQ解耦:
1)task1准时开始,结束后发一个“task1 done”的消息
2)task2订阅“task1 done”的消息,收到消息后第一时间启动执行,结束后发一个“task2 done”的消息
3)task3同理
采用MQ的优势是:
1)不须要预留buffer,上游任务执行完,下游任务总会在第一时间被执行
2)依赖多个任务,被多个任务依赖都很好处理,只须要订阅相关消息便可
3)有任务执行时间变化,下游任务都不须要调整执行时间
须要特别说明的是,MQ只用来传递上游任务执行完成的消息,并不用于传递真正的输入输出数据。
【典型场景二:上游不关心执行结果】
上游须要关注执行结果时要用“调用”,上游不关注执行结果时,就可使用MQ了。
举个栗子,58同城的不少下游须要关注“用户发布帖子”这个事件,好比招聘用户发布帖子后,招聘业务要奖励58豆,房产用户发布帖子后,房产业务要送2个置顶,二手用户发布帖子后,二手业务要修改用户统计数据。
对于这类需求,常见的实现方式是,使用调用关系:
帖子发布服务执行完成以后,调用下游招聘业务、房产业务、二手业务,来完成消息的通知,但事实上,这个通知是否正常正确的执行,帖子发布服务根本不关注。
这种方法的坏处是:
1)帖子发布流程的执行时间增长了
2)下游服务当机,可能致使帖子发布服务受影响,上下游逻辑+物理依赖严重
3)每当增长一个须要知道“帖子发布成功”信息的下游,修改代码的是帖子发布服务,这一点是最恶心的,属于架构设计中典型的依赖倒转,谁用过谁痛谁知道(采用此法的请评论留言)
优化方案是,采用MQ解耦:
1)帖子发布成功后,向MQ发一个消息
2)哪一个下游关注“帖子发布成功”的消息,主动去MQ订阅
采用MQ的优势是:
1)上游执行时间短
2)上下游逻辑+物理解耦,除了与MQ有物理链接,模块之间都不相互依赖
3)新增一个下游消息关注方,上游不须要修改任何代码
典型场景三:上游关注执行结果,但执行时间很长
有时候上游须要关注执行结果,但执行结果时间很长(典型的是调用离线处理,或者跨公网调用),也常用回调网关+MQ来解耦。
举个栗子,微信支付,跨公网调用微信的接口,执行时间会比较长,但调用方又很是关注执行结果,此时通常怎么玩呢?
通常采用“回调网关+MQ”方案来解耦:
1)调用方直接跨公网调用微信接口
2)微信返回调用成功,此时并不表明返回成功
3)微信执行完成后,回调统一网关
4)网关将返回结果通知MQ
5)请求方收到结果通知
这里须要注意的是,不该该由回调网关来调用上游来通知结果,若是是这样的话,每次新增调用方,回调网关都须要修改代码,仍然会反向依赖,使用回调网关+MQ的方案,新增任何对微信支付的调用,都不须要修改代码啦。
5、总结
MQ是一个互联网架构中常见的解耦利器。
何时不使用MQ?
上游实时关注执行结果
何时使用MQ?
1)数据驱动的任务依赖
2)上游不关心多下游执行结果
3)异步返回执行时间长
==【完】==
相关阅读:
1、缘起
上周讨论了两期环形队列的业务应用:
两期的均有大量读者提问:
今天就简单聊聊消息队列(MsgQueue)的消息必达性架构与流程。
2、架构方向
MQ要想尽可能消息必达,架构上有两个核心设计点:
(1)消息落地
(2)消息超时、重传、确认
3、MQ核心架构
上图是一个MQ的核心架构图,基本能够分为三大块:
(1)发送方 -> 左侧粉色部分
(2)MQ核心集群 -> 中间蓝色部分
(3)接收方 -> 右侧黄色部分
粉色发送方又由两部分构成:业务调用方与MQ-client-sender
其中后者向前者提供了两个核心API:
蓝色MQ核心集群又分为四个部分:MQ-server,zk,db,管理后台web
黄色接收方也由两部分构成:业务接收方与MQ-client-receiver
其中后者向前者提供了两个核心API:
MQ是一个系统间解耦的利器,它可以很好的解除发布订阅者之间的耦合,它将上下游的消息投递解耦成两个部分,如上述架构图中的1箭头和2箭头:
(1)发送方将消息投递给MQ,上半场
(2)MQ将消息投递给接收方,下半场
4、MQ消息可靠投递核心流程
MQ既然将消息投递拆成了上下半场,为了保证消息的可靠投递,上下半场都必须尽可能保证消息必达。
MQ消息投递上半场,MQ-client-sender到MQ-server流程见上图1-3:
MQ消息投递下半场,MQ-server到MQ-client-receiver流程见上图4-6:
1. 若是消息丢了怎么办?
MQ消息投递的上下半场,均可以出现消息丢失,为了下降消息丢失的几率,MQ须要进行超时和重传。
2. 上半场的超时与重传
MQ上半场的1或者2或者3若是丢失或者超时,MQ-client-sender内的timer会重发消息,直到指望收到3,若是重传N次后还未收到,则SendCallback回调发送失败,须要注意的是,这个过程当中MQ-server可能会收到同一条消息的屡次重发。
3. 下半场的超时与重传
MQ下半场的4或者5或者6若是丢失或者超时,MQ-server内的timer会重发消息,直到收到5而且成功执行6,这个过程可能会重发不少次消息,通常采用指数退避的策略,先隔x秒重发,2x秒重发,4x秒重发,以此类推,须要注意的是,这个过程当中MQ-client-receiver也可能会收到同一条消息的屡次重发。
MQ-client与MQ-server如何进行消息去重,如何进行架构幂等性设计,下一次撰文另述,此处暂且认为为了保证消息必达,可能收到重复的消息。
5、总结
消息总线是系统之间的解耦利器,但切勿滥用,将来也会撰文细究MQ的使用场景,消息总线为了尽可能保证消息必达,架构设计方向为:
1、缘起
如《消息总线消息必达》所述,MQ消息必达,架构上有两个核心设计点:
(1)消息落地
(2)消息超时、重传、确认
再次回顾消息总线核心架构,它由发送端、服务端、固化存储、接收端四大部分组成。
为保证消息的可达性,超时、重传、确认机制可能致使消息总线、或者业务方收到重复的消息,从而对业务产生影响。
举个栗子:
购买会员卡,上游支付系统负责给用户扣款,下游系统负责给用户发卡,经过MQ异步通知。无论是上半场的ACK丢失,致使MQ收到重复的消息,仍是下半场ACK丢失,致使购卡系统收到重复的购卡通知,均可能出现,上游扣了一次钱,下游发了多张卡。
消息总线的幂等性设计相当重要,是本文将要讨论的重点。
2、上半场的幂等性设计
MQ消息发送上半场,即上图中的1-3
若是3丢失,发送端MQ-client超时后会重发消息,可能致使服务端MQ-server收到重复消息。
此时重发是MQ-client发起的,消息的处理是MQ-server,为了不步骤2落地重复的消息,对每条消息,MQ系统内部必须生成一个inner-msg-id,做为去重和幂等的依据,这个内部消息ID的特性是:
(1)全局惟一
(2)MQ生成,具有业务无关性,对消息发送方和消息接收方屏蔽
有了这个inner-msg-id,就能保证上半场重发,也只有1条消息落到MQ-server的DB中,实现上半场幂等。
3、下半场的幂等性设计
MQ消息发送下半场,即上图中的4-6
须要强调的是,接收端MQ-client回ACK给服务端MQ-server,是消息消费业务方的主动调用行为,不能由MQ-client自动发起,由于MQ系统不知道消费方何时真正消费成功。
若是5丢失,服务端MQ-server超时后会重发消息,可能致使MQ-client收到重复的消息。
此时重发是MQ-server发起的,消息的处理是消息消费业务方,消息重发势必致使业务方重复消费(上例中的一次付款,重复发卡),为了保证业务幂等性,业务消息体中,必须有一个biz-id,做为去重和幂等的依据,这个业务ID的特性是:
(1)对于同一个业务场景,全局惟一
(2)由业务消息发送方生成,业务相关,对MQ透明
(3)由业务消息消费方负责判重,以保证幂等
最多见的业务ID有:支付ID,订单ID,帖子ID等。
具体到支付购卡场景,发送方必须将支付ID放到消息体中,消费方必须对同一个支付ID进行判重,保证购卡的幂等。
有了这个业务ID,才可以保证下半场消息消费业务方即便收到重复消息,也只有1条消息被消费,保证了幂等。
3、总结
MQ为了保证消息必达,消息上下半场都可能发送重复消息,如何保证消息的幂等性呢?
上半场
下半场
结论:幂等性,不只对MQ有要求,对业务上下游也有要求。
分布式系统中,不少业务场景都须要考虑消息投递的时序,例如:
(1)单聊消息投递,保证发送方发送顺序与接收方展示顺序一致
(2)群聊消息投递,保证全部接收方展示顺序一致
(3)充值支付消息,保证同一个用户发起的请求在服务端执行序列一致
消息时序是分布式系统架构设计中很是难的问题,ta为何难,有什么常见优化实践,是本文要讨论的问题。
1、为何时序难以保证,消息一致性难?
为何分布式环境下,消息的时序难以保证,这边简要分析了几点缘由:
【时钟不一致】
分布式环境下,有多个客户端、有web集群、service集群、db集群,他们都分布在不一样的机器上,机器之间都是使用的本地时钟,而没有一个所谓的“全局时钟”,因此不能用“本地时间”来彻底决定消息的时序。
【多客户端(发送方)】
多服务器不能用“本地时间”进行比较,假设只有一个接收方,可否用接收方本地时间表示时序呢?遗憾的是,因为多个客户端的存在,即便是一台服务器的本地时间,也没法表示“绝对时序”。
如上图,绝对时序上,APP1先发出msg1,APP2后发出msg2,都发往服务器web1,网络传输是不能保证msg1必定先于msg2到达的,因此即便以一台服务器web1的时间为准,也不能精准描述msg1与msg2的绝对时序。
【服务集群(多接收方)】
多发送方不能保证时序,假设只有一个发送方,可否用发送方的本地时间表示时序呢?遗憾的是,因为多个接收方的存在,没法用发送方的本地时间,表示“绝对时序”。
如上图,绝对时序上,web1先发出msg1,后发出msg2,因为网络传输及多接收方的存在,没法保证msg1先被接收到先被处理,故也没法保证msg1与msg2的处理时序。
【网络传输与多线程】
多发送方与多接收方都难以保证绝对时序,假设只有单一的发送方与单一的接收方,可否保证消息的绝对时序呢?结论是悲观的,因为网络传输与多线程的存在,仍然不行。
如上图,web1先发出msg1,后发出msg2,即便msg1先到达(网络传输其实还不能保证msg1先到达),因为多线程的存在,也不能保证msg1先被处理完。
【怎么保证绝对时序】
经过上面的分析,假设只有一个发送方,一个接收方,上下游链接只有一条链接池,经过阻塞的方式通信,难道不能保证先发出的消息msg1先处理么?
回答:能够,但吞吐量会很是低,并且单发送方单接收方单链接池的假设不太成立,高并发高可用的架构不会容许这样的设计出现。
2、优化实践
【以客户端或者服务端的时序为准】
多客户端、多服务端致使“时序”的标准难以界定,须要一个标尺来衡量时序的前后顺序,能够根据业务场景,以客户端或者服务端的时间为准,例如:
(1)邮件展现顺序,实际上是以客户端发送时间为准的,潜台词是,发送方只要将邮件协议里的时间调整为1970年或者2970年,就能够在接收方收到邮件后一直“置顶”或者“置底”
(2)秒杀活动时间判断,确定得以服务器的时间为准,不可能让客户端修改本地时间,就可以提早秒杀
【服务端可以生成单调递增的id】
这个是毋庸置疑的,不展开讨论,例如利用单点写db的seq/auto_inc_id确定能生成单调递增的id,只是说性能及扩展性会成为潜在瓶颈。对于严格时序的业务场景,能够利用服务器的单调递增id来保证时序。
【大部分业务能接受偏差不大的趋势递增id】
消息发送、帖子发布时间、甚至秒杀时间都没有这么精准时序的要求:
(1)同1s内发布的聊天消息时序乱了
(2)同1s内发布的帖子排序不对
(3)用1s内发起的秒杀,因为服务器多台之间时间有偏差,落到A服务器的秒杀成功了,落到B服务器的秒杀还没开始,业务上也是能够接受的(用户感知不到)
因此,大部分业务,长时间趋势递增的时序就可以知足业务需求,很是短期的时序偏差必定程度上可以接受。
关于绝对递增id,趋势递增id的生成架构,详见文章《细聊分布式ID生成方法》,此处不展开。
【利用单点序列化,能够保证多机相同时序】
数据为了保证高可用,须要作到进行数据冗余,同一份数据存储在多个地方,怎么保证这些数据的修改消息是一致的呢?利用的就是“单点序列化”:
(1)先在一台机器上序列化操做
(2)再将操做序列分发到全部的机器,以保证多机的操做序列是一致的,最终数据是一致的
典型场景一:数据库主从同步
数据库的主从架构,上游分别发起了op1,op2,op3三个操做,主库master来序列化全部的SQL写操做op3,op1,op2,而后把相同的序列发送给从库slave执行,以保证全部数据库数据的一致性,就是利用“单点序列化”这个思路。
典型场景二:GFS中文件的一致性
GFS(Google File System)为了保证文件的可用性,一份文件要存储多份,在多个上游对同一个文件进行写操做时,也是由一个主chunk-server先序列化写操做,再将序列化后的操做发送给其余chunk-server,来保证冗余文件的数据一致性的。
【单对单聊天,怎么保证发送顺序与接收顺序一致】
单人聊天的需求,发送方A依次发出了msg1,msg2,msg3三个消息给接收方B,这三条消息可否保证显示时序的一致性(发送与显示的顺序一致)?
回答:
(1)若是利用服务器单点序列化时序,可能出现服务端收到消息的时序为msg3,msg1,msg2,与发出序列不一致
(2)业务上不须要全局消息一致,只须要对于同一个发送方A,ta发给B的消息时序一致就行,常见优化方案,在A往B发出的消息中,加上发送方A本地的一个绝对时序,来表示接收方B的展示时序
msg1{seq:10, receiver:B,msg:content1 }
msg2{seq:20, receiver:B,msg:content2 }
msg3{seq:30, receiver:B,msg:content3 }
潜在问题:若是接收方B先收到msg3,msg3会先展示,后收到msg1和msg2后,会展示在msg3的前面。
不管如何,是按照接收方收到时序展示,仍是按照服务端收到的时序展示,仍是按照发送方发送时序展示,是pm须要思考的点,技术上都可以实现(接收方按照发送时序展示是更合理的)。
总之,须要一杆标尺来衡量这个时序。
【群聊消息,怎么保证各接收方收到顺序一致】
群聊消息的需求,N个群友在一个群里聊,怎么保证全部群友收到的消息显示时序一致?
回答:
(1)不能再利用发送方的seq来保证时序,由于发送方不单点,时间也不一致
(2)能够利用服务器的单点作序列化
此时群聊的发送流程为:
(1)sender1发出msg1,sender2发出msg2
(2)msg1和msg2通过接入集群,服务集群
(3)service层到底层拿一个惟一seq,来肯定接收方展现时序
(4)service拿到msg2的seq是20,msg1的seq是30
(5)经过投递服务讲消息给多个群友,群友即便接收到msg1和msg2的时间不一样,但能够统一按照seq来展示
这个方法能实现,全部群友的消息展现时序相同。
缺点是,这个生成全局递增序列号的服务很容易成为系统瓶颈,还有没有进一步的优化方法呢?
思路:群消息其实也不用保证全局消息序列有序,而只要保证一个群内的消息有序便可,这样的话,“id串行化”就成了一个很好的思路。
这个方案中,service层再也不须要去一个统一的后端拿全局seq,而是在service链接池层面作细小的改造,保证一个群的消息落在同一个service上,这个service就能够用本地seq来序列化同一个群的全部消息,保证全部群友看到消息的时序是相同的。
关于id串行化的细节,可详见《利用id串行化解决缓存与数据库一致性问题》,此处不展开。
3、总结
(1)分布式环境下,消息的有序性是很难的,缘由多种多样:时钟不一致,多发送方,多接收方,多线程,网络传输不肯定性等
(2)要“有序”,先得有衡量“有序”的标尺,能够是客户端标尺,能够是服务端标尺
(3)大部分业务可以接受大范围趋势有序,小范围偏差;绝对有序的业务,能够借助服务器绝对时序的能力
(4)单点序列化,是一种常见的保证多机时序统一的方法,典型场景有db主从一致,gfs多文件一致
(5)单对单聊天,只需保证发出的时序与接收的时序一致,能够利用客户端seq
(6)群聊,只需保证全部接收方消息时序一致,须要利用服务端seq,方法有两种,一种单点绝对时序,另外一种id串行化
1、缘起
不少时候,业务有“在一段时间以后,完成一个工做任务”的需求。
例如:滴滴打车订单完成后,若是用户一直不评价,48小时后会将自动评价为5星。
通常来讲怎么实现这类“48小时后自动评价为5星”需求呢?
1. 常见方案:
启动一个cron定时任务,每小时跑一次,将完成时间超过48小时的订单取出,置为5星,并把评价状态置为已评价。
假设订单表的结构为:t_order(oid, finish_time, stars, status, …),更具体的,定时任务每隔一个小时会这么作一次:
若是数据量很大,须要分页查询,分页update,这将会是一个for循环。
2. 方案的不足:
(1)轮询效率比较低
(2)每次扫库,已经被执行过记录,仍然会被扫描(只是不会出如今结果集中),有重复计算的嫌疑
(3)时效性不够好,若是每小时轮询一次,最差的状况下,时间偏差会达到1小时
(4)若是经过增长cron轮询频率来减小(3)中的时间偏差,(1)中轮询低效和(2)中重复计算的问题会进一步凸显
如何利用“延时消息”,对于每一个任务只触发一次,保证效率的同时保证明时性,是今天要讨论的问题。
2、高效延时消息设计与实现
高效延时消息,包含两个重要的数据结构:
同时,启动一个timer,这个timer每隔1s,在上述环形队列中移动一格,有一个Current Index指针来标识正在检测的slot。
Task结构中有两个很重要的属性:
假设当前Current Index指向第一格,当有延时消息到达以后,例如但愿3610秒以后,触发一个延时消息任务,只需:
Current Index不停的移动,每秒移动到一个新slot,这个slot中对应的Set,每一个Task看Cycle-Num是否是0:
使用了“延时消息”方案以后,“订单48小时后关闭评价”的需求,只需将在订单关闭时,触发一个48小时以后的延时消息便可:
3、总结
环形队列是一个实现“延时消息”的好方法,开源的MQ好像都不支持延迟消息,不妨本身实现一个简易的“延时消息队列”,能解决不少业务问题,并减小不少低效扫库的cron任务。
1、缘起
不少时候,业务有定时任务或者定时超时的需求,当任务量很大时,可能须要维护大量的timer,或者进行低效的扫描。
例如:58到家APP实时消息通道系统,对每一个用户会维护一个APP到服务器的TCP链接,用来实时收发消息,对这个TCP链接,有这样一个需求:“若是连续30s没有请求包(例如登陆,消息,keepalive包),服务端就要将这个用户的状态置为离线”。
其中,单机TCP同时在线量约在10w级别,keepalive请求包大概30s一次,吞吐量约在3000qps。
通常来讲怎么实现这类需求呢?
1. “轮询扫描法”
1)用一个Map
2)当某个用户uid有请求包来到,实时更新这个Map
3)启动一个timer,当Map中不为空时,轮询扫描这个Map,看每一个uid的last_packet_time是否超过30s,若是超过则进行超时处理
2. “多timer触发法”
1)用一个Map
2)当某个用户uid有请求包来到,实时更新这个Map,并同时对这个uid请求包启动一个timer,30s以后触发
3)每一个uid请求包对应的timer触发后,看Map中,查看这个uid的last_packet_time是否超过30s,若是超过则进行超时处理
特别在同时在线量很大时,很容易CPU100%,如何高效维护和触发大量的定时/超时任务,是本文要讨论的问题。
2、环形队列法
废话很少说,三个重要的数据结构:
1)30s超时,就建立一个index从0到30的环形队列(本质是个数组)
2)环上每个slot是一个Set,任务集合
3)同时还有一个Map
同时:
1)启动一个timer,每隔1s,在上述环形队列中移动一格,0->1->2->3…->29->30->0…
2)有一个Current Index指针来标识刚检测过的slot
1. 当有某用户uid有请求包到达时:
1)从Map结构中,查找出这个uid存储在哪个slot里
2)从这个slot的Set结构中,删除这个uid
3)将uid从新加入到新的slot中,具体是哪个slot呢 =>Current Index指针所指向的上一个slot,由于这个slot,会被timer在30s以后扫描到
(4)更新Map,这个uid对应slot的index值
2. 哪些元素会被超时掉呢?
Current Index每秒种移动一个slot,这个slot对应的Set中全部uid都应该被集体超时!若是最近30s有请求包来到,必定被放到Current Index的前一个slot了,Current Index所在的slot对应Set中全部元素,都是最近30s没有请求包来到的。
因此,当没有超时时,Current Index扫到的每个slot的Set中应该都没有元素。
3. 优点:
(1)只须要1个timer
(2)timer每1s只须要一次触发,消耗CPU很低
(3)批量超时,Current Index扫到的slot,Set中全部元素都应该被超时掉
3、总结
这个环形队列法是一个通用的方法,Set和Map中能够是任何task,本文的uid是一个最简单的举例。
问:为何会有本文?
答:上一篇文章《到底何时该使用MQ?》引发了普遍的讨论,有朋友回复说,MQ的还有一个典型应用场景是缓冲流量,削峰填谷,本文将简单介绍下,MQ要实现什么细节,才能缓冲流量,削峰填谷。
问:站点与服务,服务与服务上下游之间,通常如何通信?
答:有两种常见的方式
一种是“直接调用”,经过RPC框架,上游直接调用下游。
在某些业务场景之下(具体哪些业务场景,见《到底何时该使用MQ?》),能够采用“MQ推送”,上游将消息发给MQ,MQ将消息推送给下游。
问:为何会有流量冲击?
答:无论采用“直接调用”仍是“MQ推送”,都有一个缺点,下游消息接收方没法控制到达本身的流量,若是调用方不限速,颇有可能把下游压垮。
举个栗子,秒杀业务:
上游发起下单操做
下游完成秒杀业务逻辑(库存检查,库存冻结,余额检查,余额冻结,订单生成,余额扣减,库存扣减,生成流水,余额解冻,库存解冻)
上游下单业务简单,每秒发起了10000个请求,下游秒杀业务复杂,每秒只能处理2000个请求,颇有可能上游不限速的下单,致使下游系统被压垮,引起雪崩。
为了不雪崩,常见的优化方案有两种:
1)业务上游队列缓冲,限速发送
2)业务下游队列缓冲,限速执行
无论哪一种方案,都会引入业务的复杂性,有“缓冲流量”需求的系统都须要加入相似的机制(具体怎么保证消息可达,见《消息总线可否实现消息必达?》),正所谓“通用痛点统一解决”,须要一个通用的机制解决这个问题。
问:如何缓冲流量?
答:明明中间有了MQ,而且MQ有消息落地的机制,为什么不能利用MQ来作缓冲呢?显然是能够的。
问:MQ怎么改能缓冲流量?
答:由MQ-server推模式,升级为MQ-client拉模式。
MQ-client根据本身的处理能力,每隔必定时间,或者每次拉取若干条消息,实施流控,达到保护自身的效果。而且这是MQ提供的通用功能,无需上下游修改代码。
问:若是上游发送流量过大,MQ提供拉模式确实能够起到下游自我保护的做用,会不会致使消息在MQ中堆积?
答:下游MQ-client拉取消息,消息接收方可以批量获取消息,须要下游消息接收方进行优化,方可以提高总体吞吐量,例如:批量写。
结论
1)MQ-client提供拉模式,定时或者批量拉取,能够起到削平流量,下游自我保护的做用(MQ须要作的)
2)要想提高总体吞吐量,须要下游优化,例如批量处理等方式(消息接收方须要作的)
58到家架构优化具有总体性,须要通用服务和业务方一块儿优化升级。
1、消息队列概述
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
2、消息队列应用场景
如下介绍消息队列在实际应用中经常使用的使用场景。异步处理,应用解耦,流量削锋和消息通信四个场景。
2.1异步处理
场景说明:用户注册后,须要发注册邮件和注册短信。传统的作法有两种 1.串行的方式;2.并行方式
a、串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务所有完成后,返回给客户端。
b、并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差异是,并行的方式能够提升处理的时间
假设三个业务节点每一个使用50毫秒钟,不考虑网络等其余开销,则串行方式的时间是150毫秒,并行的时间多是100毫秒。
由于CPU在单位时间内处理的请求数是必定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)
小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?
引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构以下:
按照以上约定,用户的响应时间至关因而注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,所以写入消息队列的速度很快,基本能够忽略,所以用户的响应时间多是50毫秒。所以架构改变后,系统的吞吐量提升到每秒20 QPS。比串行提升了3倍,比并行提升了两倍。
2.2应用解耦
场景说明:用户下单后,订单系统须要通知库存系统。传统的作法是,订单系统调用库存系统的接口。以下图:
传统模式的缺点:假如库存系统没法访问,则订单减库存将失败,从而致使订单失败,订单系统与库存系统耦合
如何解决以上问题呢?引入应用消息队列后的方案,以下图:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操做
假如:在下单时库存系统不能正常使用。也不影响正常下单,由于下单后,订单系统写入消息队列就再也不关心其余的后续操做了。实现订单系统与库存系统的应用解耦
2.3流量削锋
流量削锋也是消息队列中的经常使用场景,通常在秒杀或团抢活动中使用普遍。
应用场景:秒杀活动,通常会由于流量过大,致使流量暴增,应用挂掉。为解决这个问题,通常须要在应用前端加入消息队列。
a、能够控制活动的人数
b、能够缓解短期内高流量压垮应用
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
秒杀业务根据消息队列中的请求信息,再作后续处理
2.4日志处理
日志处理是指将消息队列用在日志处理中,好比Kafka的应用,解决大量日志传输的问题。架构简化以下
日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
Kafka消息队列,负责日志数据的接收,存储和转发
日志处理应用:订阅并消费kafka队列中的日志数据
2.5消息通信
消息通信是指,消息队列通常都内置了高效的通讯机制,所以也能够用在纯的消息通信。好比实现点对点消息队列,或者聊天室等
点对点通信:
客户端A和客户端B使用同一队列,进行消息通信。
聊天室通信:
客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现相似聊天室效果。
以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。
3、消息中间件示例
3.1电商系统
消息队列采用高可用,可持久化的消息中间件。好比Active MQ,Rabbit MQ,Rocket Mq。
(1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功能够开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性)
(2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。
(3)消息将应用解耦的同时,带来了数据一致性问题,能够采用最终一致性方式解决。好比主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。
3.2日志收集系统
分为Zookeeper注册中心,日志收集客户端,Kafka集群和Storm集群(OtherApp)四部分组成。
Zookeeper注册中心,提出负载均衡和地址查找服务
日志收集客户端,用于采集应用系统的日志,并将数据推送到kafka队列
Kafka集群:接收,路由,存储,转发等消息处理
Storm集群:与OtherApp处于同一级别,采用拉的方式消费队列中的数据
综合选择RabbitMq
Kafka是linkedin开源的MQ系统,主要特色是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,0.8开始支持复制,不支持事务,适合产生大量数据的互联网服务的数据收集业务。
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
RocketMQ是阿里开源的消息中间件,它是纯Java开发,具备高吞吐量、高可用性、适合大规模分布式系统应用的特色。RocketMQ思路起源于Kafka,但并非Kafka的一个Copy,它对消息的可靠传输及事务性作了优化,目前在阿里集团被普遍应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。