分布式消息

1.消息队列应用场景

用于系统内部组件之间的通信,也用于与其他服务之间的交互,增加系统可拓展性
相比于RPC服务,消息队列系统直接更好的实现依赖倒转

  • 请求缓冲:作为缓冲层,平滑各个业务系统之间处理性能的不同等,企业数据总线ESB的概念,实现的就是各个系统之间的集成
  • 数据分发:支持一对多的广播机制,比如数据库binlog的订阅的处理,由于主库的binlog只有一份,但是下游的消费方可能包括各种文件索引,离线数据库等,这时候可以应用消息队列来实现数据分发
  • 分布式事务: 数据库+本地消息表的方式实现一致性

2.类型

  • Kafka:集群部署依赖zookeeper环境,方便水平扩展,支持海量数据传输,高吞吐率,在消息持久化写入磁盘的过程中,使用多种技术实现读写的高性能,包括磁盘的顺序读写,零拷贝技术等
  • RocketMQ: 消息持久化,Linux文件写入磁盘时可能会出现数据丢失情况,RocketMQ在写入磁盘时支持同步刷盘方式,即消息存储磁盘成功,才会返回消息发送成功的响应,其尽可能保证消息投递中的顺序一致性和可靠性,并且优化响应时间
  • RabbitMQ:AMQP是一个异步消息传输的网络协议,同时支持MQTT,STOMP等协议

2.1 比较

  • Kafka:各类数据埋点中使用,比如电商营销的转化率日志收集和计算,它的高性能使得适合在各类监控,大数据分析等场景
  • RocketMQ: 一致性良好保证,可以应用于电商业务调用的拆分中,比如在订单通知用户,物流信息更新以后对订单状态的更新等
  • RabbitMQ:在数据迁移,系统内部的业务调用中应用,比如后台数据同步,各种客服和CRM系统

3.消费模型

分为点对点和发布订阅方式。主要区别为消息是否能被多次消费,发布订阅是广播机制。

  • 点对点:生产者向特定队列发布消息,消费者读取消息,只会被一个消费者处理
  • 发布订阅:

3.1 实现标准

来源于消息队列的JMS实现标准,消息队列有不同的实现标准:AMQP,JMS

  • JMS(Java Message Service)是Java语言平台的一个消息队列规范
  • AMQP定义了几种不同的消息模型:direct exchange,topic,headers,system等,direct点对点,其他发布订阅

3.2 Kafka消费模式

Zookeeper在kafka中用于维护offset偏移量以及集群下的Leader选举。基于topic,属于发布订阅机制,会持久化消息,消息消费完不会立即删除,保留历史信息,比较好的支持多消费者订阅

3.3 RocketMQ消费模式

除了Producer和Consumer主要为Message Topic Queue ConsumerGroup这几部分同时,RocketMQ额外支持Tag类型划分

  • Topic: 消息的第一级归属,每条消息都要有一个Topic,一个Group可以订阅多个主题的消息。对于电商,分为商品创建消息,订单消息,物流消息。
  • Tag:二级消息分类,电商中,一个订单消息分为订单完成消息,创建消息
  • ConsumerGroup:一个消费组可以订阅多个Topic,这个是对订阅模式的拓展

3.3.1 RocketMQ集群和广播消费区别

默认集群消费。

  • 广播消费实现的是发布订阅模式,发送到消费组中的消息,会被多个消费者分别处理一次。
  • 集群消费,为了将消息分发给消费组中的多个实例,需要消息路由,也就是负载均衡:平均分配策略(默认),环形分配策略,手动配置分配策略,机房分配策略,一致性哈希分配策略。

4.消息顺序消费

消息队列的队列是有序的数据结构,消息传递是顺序的,但是分布式场景下,有序性很难保证。
消息传输和消费的有序性,是消息队列应用中的一个非常重要的问题。很多场景需要考虑:订单状态流转,数据库binlog分发。

4.1 问题来源

4.1.1 时钟问题

消息的生产者,消费者,队列存储可能分布在不同机器上,不同机器使用各自的本地时钟,由于服务器存在时钟偏斜问题,本地时间会出现不一致,不能用消息发送和到达时间戳作为时序判断标准。
分布式系统下缺乏全局时钟,使得绝对时间顺序实现起来困难

4.1.2 集群

生产者和消费者都是集群部署,通过peoducerGroup和ConsumerGroup方式运行,消息发送端发送的时序不能用来作为消息发送的有序判断。消费端存在多个实例,即使队列内部有序,由于存在消息分发过程,不同消费实例的顺序难以全局统一,也无法实现绝对有序消费。

4.1.3 消息重传

传输消息时,出现网络抖动导致消息发送失败,通过合理的重传解决。重传发生在什么时候不可预知,导致消息传输出现乱序

4.1.4 网络及内部并发

一个生产者和一个消费者情况。由于网络传输不稳定以及内部消费的并发仍然无法实现绝对有序。除非在服务器内部,并且一个生产者对应一个消费者。

4.2 顺序消费保证

从消息队列自身角度分为:

  • 全局有序:无法使用多分区进行性能的优化
  • 局部有序:业务消息分发到一个固定分区,也就是单个队列内传输方式。

4.3 Kafka顺序消息

Kafka保证消息在Partition内的顺序。

  • 单分区:天然满足消息有序性
  • 多分区:通过制定的分发策略,将同一类消息分发到同一个Partition中
    电商系统中的订单流转信息,在写入Kafka时通过订单ID进行分发,保证同一个订单ID消息被发送到同一个Partition中 。

4.3.1 消息失败重发

同一个订单下的消息1,2 如果1失败,重发后可能会在2后边。
通过设置'max.in.flight.requests.per.connection'

4.4 RocketMQ顺序消息

RocketMQ保证消息在同一个Queue中的顺序性,也就满足队列的先进先出原则,如果吧对应一个业务主键的消息都路由到同一个Queue中就可以实现消息有序传输。并且RocketMQ额外支持Tag的方式,可以对业务消息做进一步拆分,在消息时更灵活。

4.5 业务角度

  • 有序性是否有必要:订单的流转状态需要保证有序性。根据发货状态进行物流通知,只关注最后是否已发货的状态。
  • 根据不同场景,以发送端或消费端时间戳为准。秒杀场景中,可以使用秒杀提交时服务端时间戳,不需要绝对有序。
  • 每次消息发送时生产唯一递增的ID:消费端消费时,缓存最多序列ID,只消费超过当前最大序列ID的消息。保证每次只处理最新的数据。
  • 缓存时间戳:生产者发送消息时,添加一个时间戳,消费端处理消息时,通过缓存时间戳方式,判断消息产生的时间是否最新,不是则丢弃。

5.消息幂等

消息的不重复消费,幂等是业务的一个特性。
幂等问题体现在对于不满足幂等性的业务,在消息重复消费,或者远程服务调用失败重试时出现的数据不一致,业务错乱等现象

  • Http协议中定义交互的不同方法,GET DELETE幂等,POST不是
  • 业务上的幂等指的是操作不影响资源本身,并不是每次读取结果都一致,比如GET接口查询记录,数据随着更新而更新,但是读接口本事仍然是幂等。
  • CRUD中,create不是幂等,update可能幂等

5.1 各类中间件

  • binlog分发进行数据同步,如果数据库更新消息被多次消费,可能会导致数据的不一致。

5.2 远程服务调用的幂等问题

远程服务调用出现失败,一般是配置重试,保证请求调用成功率,提高整体服务可用性,Dubbo支持多种集群容错的方式,可以针对业务特性,配置不同失败重试机制,包括Failover失败自动切换,Failsafe失败安全,Failfast快速失败

  • Failover下,失败重试俩次
  • Failfast,失败不会重试,直接抛异常
    Dubbo容错机制考虑多种业务场景需求,根据不同业务场景,可以选择不同容错机制,进而有不同重试策略,保证业务正确性。

5.3 消息消费重试问题

消息发送重试和微服务失败调用重试。
通过重试方式,解决网络抖动,传输不稳定导致偶发调用失败,需要从中间件和业务不同层面,来保证服务调用幂等性。

5.4 消息投递的语义

  • At most once:最多被送达一次,消息可能会丢,但不会重复传输,一般用于消息可靠性没有太高要求的场景,比如一些允许数据丢失的日志报表,监控信息
  • At least one :至少被送到一次,消息绝不丢,但可能出现重复传输大部分消息队列支持到这个级别,应用最广泛。
  • Exactly one :没条消息肯定被传输一次且仅一次,并且保证送达,绝对的这种级别很难实现,统用的Exacty once 方案几乎不可能存在,FLP不可能定理
    消费端也可以定义类似消费语义,比如消费端保证最多被消费一次,至少被消费一次等,这俩种语义可以认为同一个级别的俩种描述

5.4.1 不同消息列队支持的投递方式

RocketMQ支持At least once,通过消费端ACK机制实现:在消费过程中,消费端消费完成后返回ACK,如果消息pull到本地,但没有消费,则不会返回ack

5.5 业务上处理

  • 天然幂等不需要额外设计,允许通过合理重试来提高成功率
  • 利用数据库去重:唯一索引
  • 设置全局唯一消息ID:消息投递时,设置附加的唯一的ID,被消费后利用分布式锁机制,实现唯一消息,在缓存设置一个key对应ID,代表数据被消费。

6.消息队列高可用

依赖副本技术,当出现某个节点宕机时,能快速替换,提升系统可靠性。消息队列承担数据存储和数据传输的功能。

6.1 Kafka

高可用依赖副本机制。
一个Broker可以容纳多个Topic。Kafka为了实现可拓展性,将衣蛾Topic分散到多个Partition中,Partition是一段连续的存储,如果在同一个Broker上,不能挂载到多个磁盘。一个Broker可以有多个Topic,对应多个Partition,Partition可以细分为一个或多个Segment,每个Segement都对应一个index索引文件,以及一个log数据文件。

  • 如果没有副本,当某个Broker挂掉,或者服务器宕机,存储其上的消息就不能被正常消费,导致可用性降低,或者数据丢失,
    配置参数副本因子replication-factor, 调整为分区下副本数量。注意副本因子包含原来的Partition,俩个副本,配置为3。

6.1.1 副本之间同步数据

一个Topic,分区数为3,每个分区有三个副本。
多个副本必须有同步机制:

  • Leader:处理所有Producer,Consumer请求,进行读写处理
  • Follower: 数据备份,不处理客户端请求
    使用Git的fetch命令,会为数据同步开辟一个单独线程,成为ReplicationFetcherThread,该线程主动从Leader批量拉取数据

6.1.2 副本分配约定

  • 为了负载均衡,Kafka将所有Partition均匀分配到集群上
  • 为了提高性能,一个Partition的副本也要分散到不同Broker上。
    Kafka分区和副本分配遵循原则:一个Topic的Partition数量大于Broker数量,使Partition尽量分配到整个集群,同一个分区,所有副本尽量分配到集群多个Broker上,尽可能保证同一个分区下的主从副本,分配到不同的Broker上

6.1.3 选取Leader

引入Replication机制后,同一个Partition有多个副本,如果Leader挂掉,需要在副本选出新Leader,Kafka数据同步有一个ISR副本同步机制,Leader节点在返回ACK响应时,会关注ISR中节点的同步状态,所有副本都和Leader保持一致。
Kafka的ISR依赖zookeeper管理,ISR副本同步队列的节点,有优先选举的权利,如果某个Broker挂掉,Kafka从ISR列表中选择一个分区作为新的Leader副本,如果ISR列表为空,直接抛出异常保证一致性,或从其他副本选择一个作为Leader,可能会丢失数据。

6.1.4 所有副本挂掉

Kafka等待某个副本恢复

  • 等待ISR中某个副本恢复正常,作为新Leader,会保证数据不丢失,但是如果全部ISR节点彻底宕机,对应分区彻底不可用
  • 等待任意副本恢复正常,作为Leader,会存在数据丢失
    生产者进行投递时,考虑不同副本的状态,Leader节点如何进行ACK? 如果Leader节点等待所有Follower节点同步才返回ACK,系统整体性能和吞吐量大幅降低。

7.Kafka高性能

Kafka使用普通服务器实现TB级别传输性能,广泛用于大数据处理,流式计算,各类日志监控等处理海量数据场景

7.1 磁盘读写

设计操作系统知识:文件系统,PageCache。
Kafka对磁盘的应用,得益于消息队列的存储特性,消息队列对外提供的主要方法是生产和消费,写入磁盘时,使用顺序追加的方式来避免低效磁盘寻址。

  • 机械硬盘:成本低,容量大,但每次读写都会寻址,再写入
  • SSD固态硬盘:性能高,有着非常低的寻道时间和存取时间,但成本也高。
    Kafka采用append方式进行顺序写入,提高在机械硬盘读写速度。
    再Linux系统中,把数据写入文件系统后,数据存放在操作系统的pagecache中,写入磁盘的过程叫做Flush,刷盘的方式:依靠操作系统进行管理,定时刷盘。同步刷盘,比如调用fsync等系统函数。
    Kafka可以配置异步刷盘,不开启同步刷盘,异步不需要等待写入磁盘后返回消息投递的ACK,提高消息发送的吞吐量,降低了请求的延时。

7.2 批量优化

Redis实现pipeline管道批量操作
Kafka批量包括批量写入,批量发布,在消息投递时将消息缓存起来,进行批量发送,消费端在消费消息时,批量进行拉取,提高消息的处理速度。
Kafka的数据传输可以配置压缩协议,比如Gzip和Snappy压缩协议,进行数据压缩时,可以减少网络传输的数据大小,优化网络IO,提升传输速率

7.3 零拷贝Sendfile

OS文件读写的一种技术
用户进程进行系统调用,由用户态切换到内核态,待内核处理完之后再返回用户态,传统IO流程:把数据拷贝到内核缓冲区,把内核缓冲拷贝到用户空间,应用程序处理完以后,再拷贝回内核缓冲区。
Kafka依赖Linux内核提供的sendfile系统调用,数据在内核缓冲区完成输入输出,不需要拷贝到用户空间处理,Kafka吧所有消息都存放在单独文件里,在消息投递时直接通过Sendfile方法发送文件

7.4 MMAP技术

Kafka节点运行需要JVM支持,但是Kafka不直接依赖JVM堆内存,Kafka使用Memory Mapped Files完成内存映射,Memory Mapped Files直接对内存地址操作,调用文件read操作,把数据先读取到内核空间中,然后再复制到用户空间,MMAP将文件直接映射到用户态的内存空间,省去用户空间到内核空间复制的开销。

8.RocketMQ应用

提供低延时,高可靠的消息发布和订阅服务。

8.1 Borker横向拓展

如果集群不能满足目前业务场景,可以增加新机器,拓展Broker集群,新的Broker节点启动后,注册到NameServer上,集群中的生产者和消费者通过NameServer感知到新的节点,RocketMQ支持Tag,对Topic进一步扩展。

8.2 Tag

把订单消息统一为OrderTopic,继续创建OrderCreateMessage等子主题

8.3 实现binlog分发

大多数业务场景下,需要同步数据如文件索引,各类缓存
Canal是基于MYSQL数据库进行增量日志解析,实现增量数据订阅和消费。内置对RocketMQ支持,支持开箱即用的配置方式

8.4 实现分布式一致性

事务消息是支持类似XA规范的分布式事务功能,通过RocketMQ到达分布式事务的最终一致。分布式事务可以使用TCC进行改造,也可以使用基于消息队列的本地消息表,RocketMQ在事务消息实现中添加一个Half Message概念,Half Message表示事务消息处于未完成状态,可以实现一个类似俩阶段提交的流程,实现最终一致性

posted @ 2023-10-28 20:53  lwx_R  阅读(2)  评论(0编辑  收藏  举报