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

  • 微服务架构与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差的。

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

  1. WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例

    最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架 ...

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

    WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间 ...

  3. springcolud 的学习(二).微服务架构的介绍

    什么是微服务微服务架是从SOA架构演变过来,比SOA架构粒度会更加精细,让专业的人去做专业的事情(专注),目的提高效率,每个服务于服务之间互不影响,微服务架构中,每个服务必须独立部署,互不影响,微服务 ...

  4. 微服务理论之二:面向微服务架构与传统架构、SOA对比,以及云化对比

    一.Monolith 网上对Microservice进行介绍的文章常常以Monolith作为开头,我也不会例外.原因是,知道了Monolith的不便之后才能更容易地理解Microservice架构模式 ...

  5. 微服务实战(三):落地微服务架构到直销系统(构建基于RabbitMq的消息总线)

    从前面文章可以看出,消息总线是EDA(事件驱动架构)与微服务架构的核心部件,没有消息总线,就无法很好的实现微服务之间的解耦与通讯.通常我们可以利用现有成熟的消息代理产品或云平台提供的消息服务来构建自己 ...

  6. 8.rabbitmq RPC模拟微服务架构中的服务调用

    标题 : 8.rabbitmq RPC模拟微服务架构中的服务调用 目录 : RabbitMQ 序号 : 8 { var connectionFactory = new ConnectionFactor ...

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

    微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...

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

    微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...

  9. 【DDD/CQRS/微服务架构案例】在Ubuntu 14.04.4 LTS中运行WeText项目的服务端

    在<WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例>文章中,我介绍了自己用Visual Studio 2015(C# 6.0 with .NET Frame ...

随机推荐

  1. iOS开发-UI 从入门到精通(五)

    近日在做项目的时候,为了快捷适配屏幕采用了Storyboard,添加约束以后运行后发现一个问题(下面将以普通案例展示该问题);在4.7 甚至更大的屏幕下是没有问题的,如下图(4.7屏幕): 但是放到更 ...

  2. iOS多线程实现2-NSThread

    NSThread是轻量级的多线程开发,OC语言编写,更加面向对象,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期.在iOS开发中很少使用它来创建一个线程,但是经常使用它做一些延时 ...

  3. 初识java之变量、数据类型和运算符(一)

    博友目标: 1.掌握变量的概念 2.引子----会使用常用数据类型 众所周知,每台电脑都有一个内存这么个必不可少的元素,那么到底内存到底是用来干什么的呢?其实啊,计算机内存相当于人类的大脑,计算机在处 ...

  4. iOS 系统根据导航栏和状态栏自动修改布局

    问题 条件:1.有一个全屏大小的带导航的controller 2.隐藏导航栏,最顶上还会留出状态栏的位置,而不是全屏显示 解决方法 self.automaticallyAdjustsScrollVie ...

  5. React Native 红屏之Could not connect to development server.

    React Native 是目前最火的开发框架,其他不说了,上Bug. 按照  React Native iOS环境搭建 高级版 在mac上  搭建 React Native  环境,运行 项目 若出 ...

  6. vim 使用 YouCompleteMe

    当然前提是先装好vundle 1 在vimrv中加入Bundle 'Valloric/YouCompleteMe' 2 vim +PluginInstall +qall 3 安装一对底层需要的编译的东 ...

  7. 一致性哈希算法与Java实现

    原文:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  8. window 和 linux 环境下杀死tomcat进程——也可以解决其他端口被占用的问题

    1.应用场景 在Windows或者linux操作系统中,我们在启动一个tomcat服务器时,经常会发现8080端口已经被占用的错误,而我们又不知道如何停止这个tomcat服务器. 2.window环境 ...

  9. windows 2012 r2下安装sharepoint 2013错误解决

    日前,我在安装sharepoint 2013时,需要预部署一些软件,我们知道运行产品准备工具“prerequisiteinstaller”后就可以自动下载安装配置这些软件,但是使用系统为windows ...

  10. WPF -Enum的三种绑定方法

    一.使用ObjectDataProvider <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentat ...