文章目录

一、系统间的通信方式

分布式系统常常需要进行系统间通信:

常见的系统间通信方式有两种:

  • 远程过程调用(Remote Procedure Call,RPC)
  • 消息队列(Message Queue,MQ)

对于RPC,我们可以同步请求(请求、等待;处理、响应),也可以异步请求(请求、标记请求、等待;处理、完成;释放标记/超时处理)。

然而RPC有很多缺点:

(1)任何一方出现故障,都可能会造成消息丢失。

当然你也可以通过应用/框架重试+多实例来缓解,但只要任何一方全部挂掉,消息必然丢失。

(2)平台、协议、开发语言等差异导致对接复杂度高。

虽然GRPC也是跨平台跨语言的,但并非所有的系统都会引入GRPC,protobuf也不能完全包含所有的开发语言。

(3)服务器性能差异,导致消息堆积。

上游数据源(比如上游服务器、或者作为网关的请求)消息数量非常多,下游服务器性能较差,这个时候RPC的请求完全处理不过来,堆积消息或者丢弃消息都是好的情况,更严重的是可能把CPU、内存等打满造成下游服务器崩溃,服务器数量更少了,一台一台服务器服务器接连雪崩。

于是消息中间件出现了,它是异步的、和系统生命周期松耦合的、平台语言无关的。很容易理解,系统A把消息发送给消息中间件,系统B从消息中间件取出消息,就达到了系统AB之间通信的目的。系统A或者系统B任何一方全部挂掉,消息都是保存在消息中间件里的,一般不会丢失。系统A和B各自对接标准的消息中间件消息格式即可,不用管对方是什么语言、什么平台开发和运行的。

二、消息中间件

消息中间件的主要技术标准有:

  • Java消息服务(Java Message Service,JMS)
  • 高级消息队列协议(Advanced Message Queue Protocol,AMQP)

2.1 JMS

(1)JMS历史

在当时有非常多的消息中间件,然而格式互不相同,导致应用需要对接多个平台(当时出名的是IBM的WebSphere MQ)。2001年左右,JMS诞生,它为了打破消息中间件壁垒而生,借助Java来推动标准化。它是一套面向Java平台的API,类似JDBC对接多个数据库一样,它封装了多个消息中间件的对接细节,只需要用Java操作统一API即可。目前JMS开发到了2.0a版本(2015年3月16日),正在开发JMS3.0。

(2)JMS数据模型

JMS API支持两种模型:点对点(Point-to-Point)、发布订阅(Publish-and-Subscribe)。

点对点就是相当于传输通道,多个生产者生产消息,1个消费者绑定了1个消息队列接收消息,消息会一直保留直到被消费或者过期。

发布订阅模型则很常见了,指定一个主题,生产者往这个主题里面写消息,0个或多个消费者可以订阅这个主题的消息。

JMS还支持JVM语言如Scala,但不支持其他语言。

目前你还可以使用Spring封装的JmsTemplate和@JmsListener去玩一玩JMS,目前支持JMS的消息队列中间件有Apache ActiveMQ,尽管我还没有在生产环境见过有使用这类较老的消息队列的项目。

2.2 AMQP

AMQP于2006年左右诞生,不同于JMS是API,AMQP是面向消息中间件的应用层协议,你可以把它类比为消息队列中间件中的HTTP。AMQP由多家大佬级组织联合制定,比如操作系统老师最喜欢的RedHat、比如Cisco等。

这里主要描述一下AMQP的核心数据模型。在AMQP里面,有如下几种角色:发布者(Publisher)、消费者(Consumer)、交换机(Exchange)、队列(Queue)。

发布者发布消息给交换机,交换机根据设定的路由规则将消息投递到与该交换机绑定的队列,消费者消费自己订阅队列里面的消息。这里比较有意思的是交换机的概念,我们忽略掉一些细节(如Durability消息代理重启后,交换机需要销毁),描述一下几种交换机:

(1)直连交换机(Direct Exchange)—— 单播(Unicast Routing)

根据消息携带的路由键(Routing Key),将消息投递给对应绑定键(Binding Key)匹配的队列。

如图,发布者发布一个“mykey”为路由键的消息,直连交换机进行匹配,发现队列1和队列3都匹配该键值,然后发送给了对应的消费者B、D。当然,通常匹配的队列都是1个,只会有1个队列获取消息,这也就是所谓的“单播”场景。

(2)扇形交换机(Fanout Exchange)—— 广播(Broadcase Routing)

扇形交换机会将消息路由给绑定到该交换机的所有队列,例如游戏排行榜、群聊、配置分发系统等。

(3)主题交换机(Topic Exchange) —— 多播(Multicast Routing)

类似于订阅发布,路由键和绑定键都被句点符号分割“.”,允许使用两种特殊字符表示通配,星号“*”表示匹配单层,井号“#”表示匹配0层或多层。

例如发布A.b.CCCCC,*.b.*可以匹配,A.#可以匹配,A.B.C不能匹配,因此消息会被多播给队列1和队列3,系统B、D会接收到消息。

(4)头部交换机(Headers Exchange)—— 特定头部匹配

可以对消息的头部进行匹配,可以匹配any(任意一种规则满足即满足)、all(全部规则满足才可以)等,灵活度很高,消息会根据头部自动路由的不同的队列中去。

(5)默认交换机(Default Exchange)

默认交换机是预先声明好的,没有名字的直连交换机。每个新建队列都会自动绑定到默认交换机上,绑定键和队列名称相同。

RabbitMQ实现了AMQP协议,在生产环境中也有应用。RabbitMQ是Erlang语言写的(虽然这种语言依然小众,但仍然是可以和Java、Go等并列的语言,因为Erlang的性能非常非常非常好,例如MQTT的EMQ就是Erlang写的),它的特点是消息投递非常快、而且很稳定,适用于小型的消息广播等用途。

三、Kafka和JMS、AMQP的关系

Kafka的设计实现上并不是单纯的JMS点对点/订阅发布模型,也不是AMQP的交换机模型规范。它甚至还有自己的“分区顺序保证”、“消费者负载均衡”等独特特性。实际上,LinkedIn的大佬们认为AMQP规范并不适合自己的业务场景,于是才开发了Kafka。

同样,阿里借鉴Kafka开发的RocketMQ,也不符合JMS和AMQP规范。RocketMQ是目前和Kafka流行程度同样高的消息中间件,在分布式一致性上面的实现不太一样,RocketMQ使用Raft协议的Dledger,Kafka依赖Zookeeper,也就是依赖ZAB。其实两个各有优劣,RocketMQ必须实现仲裁,也就是必须要3台及以上的服务器存在才可以正常工作,而Kafka只有1台也可以工作。然而我们要用Kafka,还得给它配一个zookeeper,还要时刻关注zookeeper的网络分区问题(可以通过关注kafka的controller个数间接得知),很麻烦。Kafka现在基本完成了客户端的去zookeeper化,比如客户端的偏移量之前是保存在zookeeper的,现在保存在kafka的一个特殊主题里;客户端之前是要连接zookeeper获取数据的,现在只需要连接kafka。服务端也证实提案开始去掉zookeeper,只是方案还有很多细节需要敲定,但可以预见Raft基本上是未来很多分布式组件的首选,当然也不要一味地把Raft奉为圭臬。


转载请注明出处http://www.bewindoweb.com/296.html | 三颗豆子
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。
你可能还会喜欢
具体问题具体杠