架构系列-消息队列篇

什么是消息队列?

  • 消息队列的定义
    消息队列中的“消息”即指同一台计算机的进程间,或不同计算机的进程间传送的数据;“消息队列”是在消息的传输过程中保存消息的容器。

为什么使用消息队列?

  • 根据应用场景,使用消息队列可以异步、解耦、削峰等,引入消息队列,可以解决传统消息传递的很多弊端

使用消息队列有什么好处?

  • 保证消息的可靠性
  • 异步
  • 解耦
  • 削峰
  • 提高系统的响应速度
  • 提高系统的稳定性
  • 消息通讯,是实现高性能、高可用、可伸缩和最终一致性架构中不可或缺的一环


    消息队列

使用消息队列有哪些缺点

  • 延时
  • 降低系统的可用性
  • 增加系统的复杂度
  • 数据的一致性问题

消息队列的模型和类型

  • 生产-消费模型
  • request-reply模型
  • 点对点模型、publish-subscribe模型
  • 推(push)/ 拉(pull)模型

消息队列的应用场景是什么?

  • 异步处理:用户注册之后发送短信、邮件等
  • 应用解耦:用户下单后,订单系统需要通知库存系统
  • 流量削峰:秒杀或者团抢活动
  • 日志处理:在日志处理中应用消息队列,如kafka的应用,解决大量日志传输的问题
  • 消息通讯
    • 点对点通讯:通讯的两个客户端使用同一个消息队列
    • 聊天室通讯:多个客户端订阅同一个消息队列,进行消息的接收和发送

常用的消息队列及其特点?

  • 常用的MQ产品:ActiveMQ(新产品Apollo), RabbitMQ, RocketMQ, kafka


    常用消息队列对比图

该在哪些关键指标上考虑怎么选择合适的消息队列?

  • 高可用性(什么是高可用性?如何保证消息队列的高可用性?)
  • 消息数据可持久化(如何持久化?,没有持久化的情况下,消息溢出时,是阻塞知道队列可用还是直接丢弃部分数据)
  • 数据一致性(应用解耦时产生的数据一致性问题,如何保障数据一致性(使用最终一致性方法解决))
  • 多语言、多协议支持
  • 易用性、拓展性(集群拓展?)、高可用性
  • 异步处理、同步处理(request-reply的消息队列对消息被消耗之后有回复)
  • 可维护性和容错性
  • 数据的串行化协议,数据读写效率、缓冲区溢出、短消息的效率、大消息的效率、流媒体传输等
  • 如何处理各种网络异常

使用消息队列关注哪些事项?

如何保证消息的高可用性

回答这个问题,需要对消息队列的集群模式有深刻的了解,生产环境中是没有人用单机模式的。

RocketMQ

以RocketMQ为例,它的集群就有多master模式、多master多slave异步复制模式、多master多slave同步双写模式。多master多slave模式部署架构图如下:

RocketMQ架构图

上图中与kafaka比较像,只是NameServer集群,在kafka中是用zookeeper代替,都是用来保存和发现master和slave用的。

通信过程如下:
Producer和NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer后期topic路由信息,并向Topic服务的Broker Master 建立长连接,且定时向Broker发送心跳。Producer只能将消息发送到Broker Master,但是Consumer则不一样,它同时和提供Topic服务的master和Slave建立长连接,既可以从Broker Master订阅消息,也可以从Broker Slave订阅消息。

kafka

作为对比,直接上kafka的拓扑架构图

kafka架构图

如上图所示,一个典型的kafka集群中包含若干Producer(可以是web前端产生的page view,或是服务器日志,系统CPU、memory等), 若干Broker(kafka支持水平拓展,一般Broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。kafka通过zookeeper管理集群配置选举leader,以及在Consumer Group发送变化时进行Rebalance,PRoducer使用push模式将消息发布到Broker,Consumer使用pull模式从Broker订阅并消费消息。

RabbitMQ

RabbitMQ则有单机模式、普通集群模式、镜像集群模式

  • 单机模式
    就是demo级别的,一般就是本地启动玩玩,生产环境是没人用单机模式的
  • 普通集群模式
    有多台机器,每台机器启动一个RabbitMQ实例。但是你创建的queue,只会放在一个rabbtimq实例上,但是每个实例都同步queue的元数据。消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。

这种方式没有做到所谓分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个queue所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。

而且如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让rabbitmq落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个queue拉取数据。
这就没有什么所谓的高可用性可言了,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。


RabbitMQ架构图
  • 镜像集群模式
    这种模式,才是所谓的rabbitmq的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。但是将所有消息同步到所有机器,开销会很大,也没有拓展性可言。
    rabbitmq有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。


    镜像集群模式

在回答高可用问题时,应该能逻辑清晰的画出自己的MQ集群架构或清晰的描述出来

如何保证消息不被重复消费

  • 先说一下为何会造成消息被重复消费

其实不管是哪种消息队列,造成重复消费的原因都是类似的。
正常情况下,消费者在消费消息消费完毕时,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,简单说就是每个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。
那么,造成消息队列重复消费消息的原因,就是因为网络传输等故障,确认消息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。

  • 如何解决这个问题
    • 比如,拿这个消息做数据库的insert插入操作,那就容易了,给这个消息做一个唯一主键,那么就是出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据
    • 再比如,拿这个消息做Redis的set的操作,这样不管set几次,结构都是一样的,因为set操作就是幂等的
    • 上面都不行的话,可以准备一个第三方介质,来做消费记录,以Redis为例,给消息分配一个全局id,只要消费过该消息,将<id, message>以K-V形式写入Redis,那消费者开始消费前,先去Redis中查询有没有消费记录即可(消息队列消息数量很大的情况下怎么办?可以自行考虑下)。

如何保证消息的可靠性传输

这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据。

RabbitMQ

  • 生产者弄丢数据

从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。

然而缺点就是吞吐量下降了。因此,按照经验,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。处理Ack和Nack的代码如下所示:

channel.addConfirmListener(new ConfirmListener() {  
                @Override  
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {  
                    System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
                }  
                @Override  
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
                    System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
                }  
            });  
  • 消息队列弄丢数据
    处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
    那么如何持久化呢?分下面两步:
  1. 将queue的持久化标识durable设置为true,则代表是一个持久的队列
  2. 发送消息的时候将deliveryMode=2
    这样设置以后,RabbitMQ就算挂了,重启后也能恢复数据
  • 消费者弄丢数据
    消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rahbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。
    至于解决方案,采用手动确认消息即可。

kafka

引用一张kafka Replication的数据流向图


kafka数据流向图

Producer在发布消息到某个Partition时,先通过Zookeeper找到该Partition的leader,然后无论该Topic的Replication Factor为多少(也即该Partition有多少个Replication),Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader中pull数据。
针对上述情况,得出如下分析:

  • 生产者弄丢数据
    在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。因此,为了避免生产者丢数据,做如下两点配置:
    第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
    第二个配置是在producer端设置retries=MAX,一旦写入失败,将无限重试

  • 消息队列弄丢数据
    针对消息队列丢数据的情况,无外乎就是,数据还没同步,leader就挂了,这时zookpeer会将其他的follwer切换为leader,那数据就丢失了。针对这种情况,应该做两个配置。
    第一个配置是replication.factor参数,这个值必须大于1,即要求每个partition必须有至少2个副本。
    第二个配置是min.insync.replicas参数,这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系。
    这两个配置加上上面生产者的配置联合起来用,基本可确保kafka不丢数据

  • 消费者弄丢数据
    这种情况一般是自动提交了offset,然后你处理程序过程中挂了。kafka以为你处理好了。
    再强调一次offset是干嘛的:
    offset:指的是kafka的topic中的每个消费组消费的下标。简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。
    比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。
    解决方案也很简单,改成手动提交即可。

ActiveMQ和RockerMQ,大家自行查阅

如何保证消息的顺序性?

并非所有公司都有这种业务需求,但是还是需要有所了解
针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。

那如果为了吞吐量,有多个消费者去消费怎么办?
这个问题,没有固定回答的套路。比如我们有一个微博的操作,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要重试就行。比如你一个消费者先执行了写评论的操作,但是这时候,微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者,先执行写评论的操作后,再执行,就可以成功。

总之,针对这个问题,我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,428评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,024评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,285评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,548评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,328评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,878评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,971评论 3 439
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,098评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,616评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,554评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,725评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,243评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,971评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,361评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,613评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,339评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,695评论 2 370

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,776评论 18 139
  • 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下: 以时间复杂度为O...
    高广超阅读 12,857评论 8 167
  • Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下: 以时间复杂度为O(1)的方...
    Alukar阅读 3,090评论 0 43
  • 这两天是孩子期末考试前的最后一个周末,按我们家长的经验,这两天该卯足劲儿拼命学习,才不致于在考试时留遗憾的。可是,...
    静待花开Julia阅读 275评论 2 0
  • 我觉得不管是微信还是QQ,亦或是其他什么交流软件,作为一个社交工具来说,它们在被人使用的时候,就跟两个人面对面交流...
    夜跑女王阅读 1,609评论 0 1