转自: http://www.infoq.com/cn/articles/high-availability-broker-design?utm_source=tuicool&utm_medium=referral

在要求严格顺序消息的场景下,消息的发送者,BROKER端(BROKER端和消息存储放在一起),消息的消费者都要求按照顺序进行,三者任何一个环节的乱序都会导致消息最终的消费顺序被打乱。

如果为每一个消息维护一个有序的ID,发送和存储消息无序,消费逻辑会变得非常复杂,消费端要对消息进行重新编排,会影响消费的性能。

为了保证消息发送、保存、消费三个环节都有顺序,就要求在同一个时刻只能有一个同步发送消息的线程,消息必须按照接收到的顺序进行保存,消息的消费也只能由一个线程处理。

发送端,消费端为了高可用需要部署多个实例,然后再通过一个协调者,比如ZOOKEEPER等,控制单个实例工作,其他实例处于待命状态。当工作实例发生了故障,协调者就会唤醒待命的实例进行工作。由于发送端、消费端实例是无状态的,切换工作实例不会产生乱序的问题。消息保存的BROKER端是一个有状态的应用,如果部署多个实例,当发生故障时,由于故障实例上可能还有未消费的消息就不能进行切换。

在一些要求数据不丢失、必须有序、BROKER高可用的场景下(比如跨数据中心数据库表的同步,需要按照数据库LOG顺序回放到另一个数据中心,数据乱序或者丢失信息都可能导致两个数据中心的数据不一致),BROKER往往采用MASTER-SLAVE同步双写,或者同一个消息被同步写到多台机器上,为了保证服务宕机等情况下消息不丢失,有的业务要求每条消息都落到磁盘上。如果采用同步写多份会严重影响性能,如果采用单组MASTER-SLAVE的结构,当MASTER宕机后,SLAVE成为新的MASTER可以接受发送者的消息,但是无法满足数据任一时刻都有两份的要求。

我们现在需要一种设计方案,在保证数据可靠性的条件下性能尽可能的高,同时满足任一时刻数据至少写入2份。

下面提供一种BROKER高可用,又能满足数据任一时刻都有两份的方案 :

  1. 采用MASTER-SLAVE结构方式,同步写入消息(消息允许重复),MASTER-SLAVE上的消息在逻辑上保持一致;
  2. SLAVE在MASTER宕机后不接受发送请求,但可以进行消费;
  3. 一个消息队列分配两组以上的BROKER组(一个BROKER组由MASTER-SLAVE组成),BROKER组的集群信息在协调者上保存为一个单向的链表,消费者和发送者各有一份独立的链表数据。有消息的BROKER组一定会按受理发送请求的先后顺序保存在消费者对应的链表上,消费者只能从链表表头的BROKER组上消费,当BROKER组上的消息消费完且不为当前受理发送请求的BROKER组则从消息链表中移除;
  4. 没有积压消息的BROKER组才能被添加到发送链表的表尾,当有BROKER组发生故障时会从BROKER组中移除,移除的BROKER组必须保证没有积压消息后才能被添加回链表;
  5. 只有发送链表表头的BROKER组才能接受发送请求,同时新切换为受理发送请求的BROKER组会添加到消费链表的表尾。

异常处理流程:

  1. BROKER组有机器宕机则从发送链表中移除;
  2. 当新BROKER组被挑选为当前发送者,则把该组BROKER添加到消费链表的表尾;
  3. 当异常BROKER组的消息消费完成则从消费链表表头移除;
  4. 当BROKER组机器都恢复正常,且没有可以消费的消息则添加到发送链表的表尾。

(点击放大图像)

 

具体的处理流程描述如下所述。

发送者处理流程

正常情况下,我们可以采用单组MASTER-SLAVE结构的集群方案,MASTER接收到发送者的消息后同步转发给SLAVE。发送者只有接收到MASTER,SLAVE都写入成功的信息才算成功,否则这条消息需要发送者再次进行发送。但是当有一台机器发生故障时这个集群无法满足MASTER,SLAVE都写入成功的条件。这个时候我们需要把发送者的发送请求FAILOVER到其他的集群上。如果只是简单地进行发送请求的切换,如果切换到的BROKER集群上有未消费的消息就可能破坏数据的顺序要求。同时消费者还必须知道发送者切换的过程,否则消费者无法知道自己应该先从哪个BROKER集群上消费,一旦获取消费的BROKER集群顺序与发送时的顺不一致,顺序性就会被破坏。我们需要记录好发送到不同BROKER集群的先后顺序,消费者按照记录的顺序进行消费。

如果BROKER集群发生过切换,当前接受请求的BROKER集群可能和消费者当前应该消费的集群不同,需要对发送者和消费者单独维护当前应该使用的集群信息。

BROKER集群发生故障后怎么通知发送者,可以有多种方式,比如由ZOOKEEPER协调,或者由客户端处理。我们可以采用发送者来处理BROKER集群故障的问题,当发送者感知到发送失败或者连接失败时向协调者发起请求,由协调者返回当前可用的BROKER集群。

协调者判断BROKER集群是否可以接收新的消息,除了要判断BROKER是否存活外,还需要查询其是否有未消费的消息,只有集群上没有可消费的消息时才能接收新的发送请求。因此协调者需要知道每个BROKER集群上存放的消息情况。我们可以在BROKER集群被选中为可以接收发送请求时,标识其为有未消费消息的状态,当消费者把上面的消息都消费完成后,由该BROKER集群向协调者汇报自己已经消费完成。如果该集群服务都不可用时,无法汇报自己的消息积压情况,协调者会一直标记其为有未消费的消息,直到该集群服务恢复后,汇报完是否存在有未消费的消息。

(点击放大图像)

消费者处理流程

消费者需要消费消息时,先从协调者上获取当前应该获取消息的BROKER集群,当消费完成时,BROKER集群会向协调者汇报自己已经没有积压消息了。协调者接收到汇报后就把当前BROKER集群从需要消费的列表中移除。消费者从一个集群上获取不到消息后会再次请求协调者,获取下一个可以消费的集群信息,从新的集群上继续消费消息。

协调者处理流程

当协调者接收到发送者的请求时,先查看发送列表中是否存在可用的集群,如果没有就会检查消息分配的所有集群,把满足条件(消息无积压,MASTER-SLAVE都工作正常)的集群加入到可发送集群列表中。如果也没有找到可用集群,那么发送者会被阻塞,直到找到可以使用的集群。

当集群被选为当前可用集群时,需要在未返回给发送者之前把该集群信息同步添加到消费集群列表中,防止协调者出现故障时,消费者获取不到这个集群的信息,被跳过导致消费乱序。

当协调者接收到消费者的请求时,协调者只需要把消费集群列表表头第一个集群返回给消费者就可以了。消费者消费完消息会通知相应的BROKER集群,该集群感知到消息都已经被消费后马上汇报给协调者,协调者收到汇报信息就会把该集群从消费集群列表的表头移除。

(点击放大图像)

如何控制单个实例发送

上面主要描述了对BROKER集群的控制,防止消息由于BROKER集群调度顺序不对导致消息乱序。

顺序消息还需要满足发送者顺序发送,消费者顺序消费,通常为了保证应用的高可用。我们会对发送者和消费者部署多个实例,当一个实例发生异常宕机时,其他的实例可以继续工作,防止单点故障。对于顺序消息同一个时间点只能有一个线程在工作,单个实例只启动一个线程进行发送和消费,只需要编写代码的时候控制就可以做到,但是当我们把应用部署为多个实例时,实例之间就需要一个协调者,保证每次都只有一个工作实例。

发送者启动时先注册一个ZOOKEEPER的监听事件,通过ZOOKEEPER选举出来一个LEADER,只有拿到LEADER权限的发送者实例才能够发送消息,没有取到LEADER权限的发送者需要马上中断发送消息的线程。消费者应用可以按照上述方案进行相同的处理。

注意事项

MASTER-SLAVE集群中单台机器接收到消息,发送者视为发送失败,可能存在消息重复发送,SLAVE成为MASTER后继续接受消费请求,消费者可能取到已经消费过的消息,因此需要业务逻辑做可以重复消费的处理。

如果有积压的消息,MASTER和SLAVE同时宕机,由于顺序的要求,消费者会被阻塞,不能继续进行消费,虽然这种情况极少发生,还是需要注意。消费者被阻塞,但是不会影响发送者,只要有可以接收消息的BROKER集群,发送者可以继续进行工作。

主从之间同步复制消息也需要保证顺序处理,避免SLAVE上消息的顺序与MASTER上的顺序不一致。

单个线程发送和消费,在一些业务场景下可能不能满足性能需求,用户可以根据自己的业务逻辑,把没有顺序要求的业务进行拆分,分成不同的消息类型进行发送,单个消息类型保证顺序。

高可用保证消息绝对顺序消费的BROKER设计方案的更多相关文章

  1. kafka 分布式(不是单机)的情况下,如何保证消息的顺序消费?

    Kafka 分布式的单位是 partition,同一个 partition 用一个 write ahead log 组织, 所以可以保证 FIFO 的顺序.不同 partition 之间不能保证顺序. ...

  2. 分布式消息队列RocketMQ&Kafka -- 消息的“顺序消费”

    在说到消息中间件的时候,我们通常都会谈到一个特性:消息的顺序消费问题.这个问题看起来很简单:Producer发送消息1, 2, 3... Consumer按1, 2, 3...顺序消费. 但实际情况却 ...

  3. Pulsar の 保证消息的顺序性、幂等性和可靠性

    原文链接:Pulsar の 保证消息的顺序性.幂等性和可靠性 一.背景 前面两篇文章,已经介绍了关于Pulsar消费者的详细使用和自研的Pulsar组件. 接下来,将简单分析如何保证消息的顺序性.幂等 ...

  4. RabbitMQ保证消息的顺序性

    当我们的系统中引入了MQ之后,不得不考虑的一个问题是如何保证消息的顺序性,这是一个至关重要的事情,如果顺序错乱了,就会导致数据的不一致.       比如:业务场景是这样的:我们需要根据mysql的b ...

  5. keepalived安装配置实战心得(实现高可用保证网络服务不间断)

    keepalived安装配置实战心得(实现高可用保证网络服务不间断) 一.准备2台虚拟机     安装的系统是:centos-release-7-1.1503.el7.centos.2.8.x86_6 ...

  6. RocketMQ 原理:消息存储、高可用、消息重试、消息幂等性

    目录 消息存储 消息存储方式 非持久化 持久化 消息存储介质 消息存储与读写方式 消息存储结构 刷盘机制 同步刷盘 异步刷盘 小结 高可用 高可用实现 主从复制 负载均衡 消息重试 顺序消息重试 无序 ...

  7. 关于MQ的几件小事(三)如何保证消息不重复消费

    1.幂等性 幂等(idempotent.idempotence)是一个数学与计算机学概念,常见于抽象代数中. 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.幂等函数,或 ...

  8. 关于MQ的几件小事(五)如何保证消息按顺序执行

    1.为什么要保证顺序 消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常.举例: 比如通过mysql binlog进行两个数据库的数据 ...

  9. kafka分布式的情况下,如何保证消息的顺序?

    作者:可期链接:https://www.zhihu.com/question/266390197/answer/772404605来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

随机推荐

  1. [转载]java日志框架log4j详细配置及与slf4j联合使用教程

    一.log4j基本用法 首先,配置log4j的jar,maven工程配置以下依赖,非maven工程从maven仓库下载jar添加到“build path” 1 2 3 4 5 <dependen ...

  2. MSSQL分组取后每一组的最新一条记录

    数据库中二张表,用户表和奖金记录表,奖金记录表中一个用户有多条信息,有一个生效时间,现在要查询: 奖金生效时间在三天前,每个用户取最新一条奖金记录,且用户末锁定 以前用的方法是直接写在C#代码中的: ...

  3. Enum,Int,String的互相转换 枚举转换

    Enum为枚举提供基类,其基础类型可以是除 Char 外的任何整型.如果没有显式声明基础类型,则使用 Int32.编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举. 注意:枚举类型的基 ...

  4. cuteftp 9 显示中文乱码

    当用FTP连接空间时,中文命名的文件名会显示乱码,原来是编码设置错误.怎么修改呢? 修改方法如下: 选择. 工具--> 全局选项->传输:1. 传输方法: ASCII2. SFTP档案名称 ...

  5. django rest_framework入门四-类视图APIView

    上节,我们使用函数视图,用了@api_view装饰器来修饰,这一节,我们介绍类视图APIView,显然,类视图更符合面向对象的原则. 1.使用类视图APIView重写API 类视图APIView,取代 ...

  6. running boot2docker -> error in run: Failed to get machine “boot2docker-vm”: machine does not exist

    登陆和使用.详细请看.....https://github.com/boot2docker/boot2docker boot2docker start error in run: Failed to ...

  7. windows下IntelliJ IDEA搭建kafka源码环境

    于kafka核心原理的资料,网上有很多,但是如果不自己研究其源码,永远是知其然而不知所以然.下面就来演示如何在windows环境下来编译kafka源码,并通过IntelliJ IDEA开发工具搭建ka ...

  8. spring AspectJ切入点语法详解 记录以便查阅

    AspectJ切入点语法详解 6.5.1  Spring AOP支持的AspectJ切入点指示符 切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spri ...

  9. eclipse Maven 使用记录 ------ 建立 webapp项目

    maven 建立 webapp 项目 有2种方式 ,  1.在原先app上转换为webapp项目  2.建立maven项目的时候  filter 选择webapp 该选项把webapp文件目录建好,其 ...

  10. hdu1217(spfa,存在环,但需要将环的元素历遍一次.....求乘积的最大)

    题意:有n个国家货币,给出m种两个国家之间的货币兑换率,求是否可以盈利....... 思路:其实就是看国家货币兑换间是否存在一个环,使得从v点出发时,dis[v]=1,经过环回到v点时,dis[v]& ...