网易蜂巢微服务架构:用RabbitMQ实现轻量级通信

本次分享内容由三个部分组成:

  • 微服务架构与MQ
  • RabbitMQ场景分析与优化
  • RabbitMQ在网易蜂巢中的应用和案例分享

1微服务架构与MQ

微服务架构是一种架构模式,它将单体应用划分成一组微小的服务,各服务之间使用轻量级的通信机制交互。

上图左边是单体架构应用,把所有业务功能放在单个进程中,需要扩展时以进程为单位水平复制到多台机器。

上图右边是微服务架构应用,将每个业务功能以独立进程(服务)的方式部署,可以按需将微服务分别部署在多台机器,实现水平扩展。

微服务各服务之间使用“轻量级”的通信机制。所谓轻量级,是指通信协议与语言无关、与平台无关。

微服务通信方式:

  1. 同步:RPC,REST等
  2. 异步:消息队列

 

同步通信方式

优点:

  1. 实现方便。
  2. 协议通用,比如HTTP大家都非常熟知。
  3. 系统架构简单,无需中间件代理。

缺点:

  1. 客户端耦合服务方。
  2. 通信双方必须同时在线,否则会造成阻塞。
  3. 客户端需要知道服务方的Endpoint,或者需要支持服务发现机制。

 

异步通信方式

优点:

  1. 系统解耦和。
  2. 通信双方可以互相对对方无感知。

缺点:

  1. 额外的编程复杂性。比如需要考虑消息可靠传输、高性能,以及编程模型的变化等。
  2. 额外的运维复杂性。比如消息中间件的稳定性、高可用性、扩展性等非功能特性。

 

今天的主题是消息队列在微服务架构中的应用与实践。

 

消息队列中间件如何选型?主要会考虑以下几点:

  1. 协议:AMQP、STOMP、MQTT、私有协议等
  2. 消息是否需要持久化
  3. 吞吐量
  4. 高可用支持,是否单点
  5. 分布式扩展能力
  6. 消息堆积能力和重放能力
  7. 开发便捷,易于维护
  8. 社区成熟度

 

选择RabbitMQ的原因:

  1. 开源,跨平台
  2. 灵活的消息路由策略
  3. 持久化,消息可靠传输
  4. 透明集群,HA支持
  5. 支持高性能高并发访问
  6. 支持多种消息协议
  7. 丰富的客户端、插件和平台支持
  8. 支持RPC解决方案

2RabbitMQ场景分析与优化

RabbitMQ是一个实现了AMQP(高级消息队列协议)协议的消息队列中间件。

AMQP基本模型:

1. Queue

2. Exchange: Direct, Fanout, Topic, Header

3. Binding: BindingKey, RouteKey

总结:生产者将消息发送到Exchange,Exchange通过匹配BindingKey和消息中的RouteKey来将消息路由到队列,最后队列将消息投递给消费者。

 

消息可靠传输是业务系统接入MQ时首先要考虑的问题。一般消息可靠性有三个等级:

  1. At most once: 最多一次
  2. At least once: 最少一次
  3. Exactly once: 恰好一次

RabbitMQ支持其中的“最多一次”和“最少一次”两种。其中“最少一次”投递实现机制:

  1. 生产者confirm。如何开启:使用confirm.select
  2. 消息持久化。
  3. 消费者ack。如何开启:使用basic.consume(…, no-ack=false)

这里说下RabbitMQ消息持久化(写磁盘)的两个场景:

  1. 显式指定消息属性:delivery-mode=2
  2. 内存吃紧时,消息(包括非持久化消息)转存到磁盘,由memory_high_watermark_paging_ratio参数指定阈值。

 

RabbitMQ的消息持久化是通过以下机制实现的:

  1. 消息体写文件
  2. 异步刷盘,合并请求,减少fsync系统调用次数
  3. 当进程mailbox没有新消息时,实时刷盘
  4. confirm机制下,等fsync系统调用完成后返回basic.ack确认

 

RabbitMQ开启消息可靠性参数需要注意:

  1. unack消息在服务器端没有超时,只能等待客户端连接断开,重新入队等待投递。
  2. 消息存在重复投递的情况,需客户端去重:

    a)基于业务层的MsgId。

    b)基于RabbitMQ的Redelivered flag标记: 不完全靠谱,仍旧可能收到重复消息。

  3. 保障性能:

    a)批量publish, ack。

    b)更快的磁盘(SSD,RAID等)。

    c)少堆积。

  4. 消息乱序。

 

生产者confirm机制是三个可靠性参数中对性能影响最大的。一般来说有三种编程方式:

  1. 普通confirm模式。每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm。
  2. 批量confirm模式。每次发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
  3. 异步confirm模式。注册一个回调方法,服务器端confirm了一条(或多条)消息后SDK会回调这个方法。

下面是一些生产者confirm机制的专项性能测试数据:

总结:

  1. 遵循线程数越大,吞吐量越大的规律。当线程数量达到一个阈值之后,吞吐量开始下降。
  2. 不论哪种confirm模式,通过调整客户端线程数量,都可以达到一个最大吞吐量值。
  3. 异步和批量confirm模式两者没有明显的性能差距。所以,只需从可编程性的角度选择异步或批量或者两者结合的模式即可。相比而言,普通confirm模式只剩编程简单这个理由了。

下面讲下RabbitMQ的高可用机制。

 

官方提供的高可用方案:cluster + ha policy

 

cluster机制:多个全联通节点之间元信息(exchange、queue、binding等)保持强一致,但是队列消息只会存储在其中一个节点。

优点:提高吞吐量,部分解决扩展性问题。

缺点:不能提升数据可靠性和系统可用性。

ha policy机制:在cluster机制基础上可以指定集群内任意数量队列组成镜像队列,队列消息会在多节点间复制。实现数据高可靠和系统高可用。

设置参数:ha-mode和ha-params可以细粒度(哪些节点,哪些队列)设置镜像队列。

设置参数:ha-sync-mode=manual(默认)/automatic可以指定集群中新节点的数据同步策略。

有一点需要注意:镜像队列对网络抖动非常敏感,默认参数配置下,出现脑裂后RabbitMQ集群不会自我恢复,需要人工介入恢复,务必加好监控和报警。

RabbitMQ流控机制  流控类型:

  1. 内存流控:由vm_memory_high_watermark参数控制,默认0.4
  2. 磁盘流控:由disk_free_limit参数控制,默认50M
  3. 单条连接流控:触发条件是下游进程的处理速度跟不上上游进程。

RabbitMQ内部进程关系调用图

注意:当触发流控(全局内存/磁盘流控,单条连接流控)时,生产者端的publish方法会被阻塞,生产者需要做的是:

  1. 注册block事件,被流控时,会收到一个回调通知。
  2. 异步化处理生产者发送消息,不要阻塞主流程。

3RabbitMQ在网易蜂巢中的应用和案例分享

网易蜂巢平台的服务化架构如下,服务间通过RabbitMQ实现通信:

 

网易蜂巢消息服务器设计目标:实现一个路由灵活、数据可靠传输、高可用、可扩展的消息服务器。

 

设计要点:

  1. Exchange类型为topic。
  2. BindingKey就是队列名。
  3. 每个服务(图例中的REPO/CTRL/WEB等)启动后会初始化一条AMQP连接,由3个channel复用:一个channel负责生产消息,一个channel从TYPE(REPO/CTRL/WEB等)类型的队列消费消息,一个channel从TYPE.${hostname}类型的队列消费消息。

 

应用场景举例:

  1. 点对点(P2P)或请求有状态服务:消息的RouteKey设置为TYPE.${HOSTNAME}。如host1上的WEB服务需要向host2上的REPO服务发送消息,只需将消息的RouteKey设置为REPO.host2投递到Exchange即可。
  2. 请求无状态服务:如果服务提供方是无状态服务,服务调用方不关心由哪个服务进行响应,那么只需将消息的RouteKey设置为TYPE。比如CTRL是无状态服务,host1上的WEB服务只需将消息的RouteKey设置为CTRL即可。CTRL队列会以Round-Robin的均衡算法将消息投递给其中的一个消费者。
  3. 组播:如果服务调用方需要与某类服务的所有节点通信,可以将消息的RouteKey设置为TYPE.*,Exchange会将消息投递到所有TYPE.${HOSTNAME}队列。比如WEB服务需通知所有CTRL服务更新配置,只需将消息的RouteKey设置为CTRL.*。
  4. 广播:如果服务调用方需要与所有服务的所有节点通信,也就是说对当前系统内所有节点广播消息,可以将消息的RouteKey设置为*.*。

总结:通过对RouteKey和BindingKey的精心设计,可以满足点对点(私信)、组播、广播等业务场景的通信需求。

优缺点分析:

优点:

  1. 路由策略灵活。
  2. 支持负载均衡。
  3. 支持高可用部署。
  4. 支持消息可靠传输(生产者confirm,消费者ack,消息持久化)。
  5. 支持prefetch count,支持流控。

缺点:

  1. 存在消息重复投递的可能性。
  2. 超时管理,错误处理等需业务层处理。
  3. 对于多服务协作的场景支持度有限。比如以下场景:WEB=> CTRL=>REPO=>CTRL=> WEB。这个时候就需要CTRL缓存WEB请求,直至REPO响应。

实践案例分享

案例一:GC引起的MQ crash

1.环境参数:

 

2.现象:

RabbitMQ崩溃,产生的erl_crash.dump文件内容如下:

3.直接原因:

从数据来看,虚拟机内存共4G,Erlang虚拟机已占用约1.98G内存(其中分配给Erlang进程的占1.56G),此时仍向操作系统申请1.82G,因为操作系统本身以及其他服务也占用一些内存,当前系统已经分不出足够的内存了,所以Erlang虚拟机崩溃。

4.分析:

本例有两个不符合预期的数据:
1. 内存流控阈值控制在1.67G左右(4G*0.4),为什么崩溃时显示占用了1.98G?
2. 为什么Erlang虚拟机会额外再申请1.82G内存?

因为:

  1. RabbitMQ每个队列设计为一个Erlang进程,由于进程GC也是采用分代策略,当新老生代一起参与Major GC时,Erlang虚拟机会新开内存,根据root set将存活的对象拷贝至新空间,这个过程会造成新老内存空间同时存在,极端情况下,一个队列可能短期内需要两倍的内存占用量。这就是RabbitMQ将内存流控的安全阈值默认设置为0.4的原因。
  2. 内存流控参数vm_memory_high_watermark 为0.4的意思是,当RabbitMQ的内存使用量大于40%时,开始进行生产者流控,但是该参数并不承诺RabbitMQ的内存使用率不大于40%。

5.如何解决(规避)?

  1. RabbitMQ独立部署,不与其他Server共享内存资源。
  2. 进一步降低vm_memory_high_watermark值,比如设置成0.3,但是这种方式会造成内存资源利用率太低。
  3. 升级RabbitMQ至新版(3.4+),新版RabbitMQ对内存管理有过改进。

案例二:镜像队列的单节点磁盘故障造成消息丢失

1.环境参数:RabbitMQ: 3.1.5 (ha policy, ha-mode:all)

2.现象:

a)节点A的数据盘故障(磁盘控制器故障、无法读写),所有原本A上的生产者消费者failover到B节点。

b)从节点B的WebUI上看,所有队列信息(包括队列元信息和数据)丢失,但是exchange、binding、vhost等依旧存在。

c)节点B的日志中出现大量关于消费请求的错误日志:

d)从生产者端看来一切正常,依旧会收到来自节点B的confirm消息(basic.ack amqp方法)。

3.分析:

上述现象实际上有两个坑在里面:

  1. 在数据可靠传输方面,镜像队列也不完全可靠。这是一个Bug。RabbitMQ 3.5.1版本以下都存在这个问题。
  2. 要保证消息可靠性,生产者端仅仅采用confirm机制还不够。对于那些路由不可达的消息(根据RouteKey匹配不到相应队列),RabbitMQ会直接丢弃消息,同时confirm客户端。

4.如何解决(规避)?

  1. 升级RabbitMQ到新版,至少3.5.1及以上。
  2. 生产者basic.publish方法设置mandatory参数,它的作用是:对于那些路由不可达的消息,RabbitMQ会先通过basic.return消息向生产者返回消息内容,然后再发送basic.ack消息进行确认

 

Q & A

Q1为保障消息队列稳定性,消息队列监控点主要有哪些?

A1首先是服务器的基础监控是要的。CPU,内存,磁盘IO都是敏感项。特别是内存,报警阈值可能要设置在50%以下,原因前面也说过,极端情况下一个队列的内存占用量可能是两倍当前值。

然后MQ业务的监控也需要加,比如消息堆积数,unack的消息数,连接数,channel数等等。RabbitMQ有提供rest api可以拿到这些监控。

还有因为RabbitMQ对网络分区非常敏感,所以日志监控也要加。RabbitMQ出现网络分区或者触发流控都会在它的运行日志中有输出。

大致就是这么几点。

Q2rabbitmq有尝试结合netty提高网络处理能力?

A2erlang是actor模型代表,我觉得它的网络处理能力也不比netty差的。

时间: 2024-10-21 05:04:56

网易蜂巢微服务架构:用RabbitMQ实现轻量级通信的相关文章

NET实现的DDD、CQRS与微服务架构

WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架构的应用程序案例,并结合领域驱动设计(DDD)以及命令查询职责分离(CQRS)体系结构模式,对事件驱动的微服务系统架构进行了一些实战性的探索.现将自己的思考和收获整理成文,分享给大家. 微服务架构 在介绍源代码之前,我还

微服务架构与实践及云原生等相关概念

微服务架构与实践 笔记:<微服务架构与实践> 王磊 著 一 单块架构 1 定义:对于这种功能集中.代码和数据中心化.一个发布包.部署后运行在同一进程的应用程序,我们通常称之为单块架构应用,并非物理上的分层. 2 单层架构:数据 逻辑 页面 混合 3 三层架构: 1)表示层:数据显示和用户交互 2)业务逻辑层:业务逻辑处理 3)数据访问层:数据存储访问 4 优势: 比较适合小项目 易于开发:开发简单直接,集中式管理,基本不会重复开发,集成工具适合 易于测试:单进程 易于部署:单项目包,功能都在本

几种常见的微服务架构方案,2018年是否还一如既往的火

微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合项目的特点来选择某个合适的微服务架构平台,以此稳妥地实施项目的微服务化改造或开发进程. 本文盘点了四种常用的微服务架构方案,分别是ZeroC IceGrid.Spring Cloud.基于消息队列与Docker Swarm. ZeroC IceGrid微服务架构 ZeroC IceGrid作为一种微

【转】常见的微服务架构方案

背景: 工业领域,服务可能涉及多种语言,C++, Java,C#,python 最先考虑thrift,但thrift毕竟只是RPC框架,不包含服务治理的内容,且这个开源项目的维护状况并不算好,因此写个原型之后,仍然pass Zeroc Ice表现优异,基于RPC框架Ice,发展而来的IceGrid包含了完善的服务治理功能,服务发现.负载均衡.发布更新.事件通知... 商用软件,最近两年也开源了,靠服务收费,因此英文的技术手册还算是齐,但是离完善和问题解决还是有距离. 中文资料只一本2015年出版

几种常见的微服务架构方案简述——ZeroC IceGrid、Spring Cloud、基于消息队列

微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合项目的特点来选择某个合适的微服务架构平台,以此稳妥地实施项目的微服务化改造或开发进程.本文选自<架构解密:从分布式到微服务>一书,了解本书详情请点击阅读原文. 本文盘点了四种常用的微服务架构方案,分别是ZeroC IceGrid.Spring Cloud.基于消息队列与Docker Swarm 1

Spring Cloud微服务架构在互联网中应用

夜行侠老师录制的:Spring Cloud微服务架构在互联网中应用 由大象分享网出版:http://www.itjoin.org/course/detail/5934a58c0cf2159b39641f80夜行侠课程集合:http://www.xuetuwuyou.com/user/29 第1节.Springcloud介绍第2节.Eureka的使用第3节.Eureka集群第4节.restful请求第5节.restful请求负载均衡第6节.配置中心第7节.获取配置中心数据第8节.配置中心高可用第9

Spring Cloud微服务架构在互联网中应用_SpringCloud视频教程

Spring Cloud微服务架构在互联网中应用 课程学习地址:http://www.xuetuwuyou.com/course/177 课程出自学途无忧网:http://www.xuetuwuyou.com 一.课程涉及的软件及版本: springcloud版本Dalston.SR1 springboot版本1.5.2 jdk1.8 spring4.3.7 二.适合人群: ①想学分布式微服务架构 ②想学springcloud,spring data flow ③想构建稳定的分布式微服务架构 三

微服务架构:动态配置中心搭建

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 在微服务架构中,服务之间有着错综复杂的依赖关系,每个服务都有自己的依赖配置,在运行期间很多配置会根据访问流量等因素进行调整,传统的配置信息处理方式是将配置信息写入xml..properties等配置文件中,和应用一起打包,每次修改配置信息,都需要重新进行打包,效率极低,动态配置中心就是为了解决这一问题.动态配置中心也是一个微服务,我们把微服务中需要动态配置的配置文件存放在远程git私有仓库上,微服务会去服务器读取配置信息,当我们在本地

基于docker部署的微服务架构(四): 配置中心

原文:http://www.jianshu.com/p/b17d65934b58%20 前言 在微服务架构中,由于服务数量众多,如果使用传统的配置文件管理方式,配置文件分散在各个项目中,不易于集中管理和维护.在 spring cloud 中使用 config-server 集中管理配置文件,可以使用 git.svn.本地资源目录 来管理配置文件,在集成了 spring cloud bus 之后还可以通过一条 post 请求,让所有连接到消息总线的服务,重新从config-server 拉取配置文