中间件
1. 介绍
中间件(Middleware)是处于操作系统和应用程序之间的软件。也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台),但在这组中间件中必须要有一个通信中间件,即中间件 = 平台 + 通信。这个定义也先定了只有用于分布式系统中才能称为中间件,同时还可以把它与支撑软件和实用软件区分开来。
中间件技术可以屏蔽操作系统的复杂性。使用中间件技术可以减少程序设计的复杂性,将注意力集中到业务上,减少技术负担。
中间件负责数据的传递、存储和分发消费三个部分。
举例: 1,RMI(Remote Method Invocations, 远程调用) 2,Load Balancing(负载均衡,将访问负荷分散到各个服务器中) 3,Transparent Fail-over(透明的故障切换) 4,Clustering(集群,用多个小的服务器代替大型机) 5,Back-end-Integration(后端集成,用现有的、新开发的系统如何去集成遗留的系统) 6,Transaction事务(全局/局部)全局事务(分布式事务)局部事务(在同一数据库联接内的事务) 7,Dynamic Redeployment(动态重新部署,在不停止原系统的情况下,部署新的系统) 8,System Management(系统管理) 9,Threading(多线程处理) 10,Message-oriented Middleware面向消息的中间件(异步的调用编程) 11,Component Life Cycle(组件的生命周期管理) 12,Resource pooling(资源池) 13,Security(安全) 14,Caching(缓存)
2. 特点
中间件是位于平台(硬件和操作系统)和应用之间的通用服务,这些服务具有标准的程序接口和协议。
- 满足大量应用的需要
- 运行于多种硬件和OS平台
- 支持分布计算,提供跨网络、硬件和OS平台的透明性的应用或服务的交互
- 支持标准的协议
- 支持标准的接口
中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求的模块。这使得中间件一定是可替换的。
3. 单体架构
所有的业务和模块,源代码,静态资源文件等都放在一个一工程中,如果其中的一个模块升级或者迭代发生一个很小变动都会重新编译和重新部署项目。 这种的架构存在的问题就是:
- 耦合度太高。
- 运维的成本过高。
- 不易维护。
- 服务器的成本高。
- 以及升级架构的复杂度也会增大。
4. 分布式架构
和单体架构不同的是,单体架构是一个请求发起jvm调度线程(确切的是tomcat线程池)分配线程Thread来处理请求直到释放,而分布式是系统,一个请求是由多个系统共同来协同完成,jvm和环境都可能是独立。
存在问题
- 学习成本高,技术栈过多。
- 运维成本和服务器成本增高。
- 人员的成本也会增高。
- 项目的负载度也会上升。
- 面临的错误和容错性也会成倍增加。
- 占用的服务器端口和通讯的选择的成本高。
- 安全性的考虑和因素逼迫可能选择RMI/MQ相关的服务器端通讯。
好处
- 服务系统的独立,占用的服务器资源减少和占用的硬件成本减少,可以合理的分配服务资源,不造成服务器资源的浪费。
- 系统的独立维护和部署,耦合度降低,可插拔性。
- 系统的架构和技术栈的选择可以变的灵活。
- 弹性的部署,不会造成平台因部署造成的瘫痪和停服的状态。
5. 消息中间件
5.1. 地位
- 消息中间件可以利用可靠的消息机制进行系统和系统的直接通讯。
- 通过提供消息传递和消息排队的机制,可以在分布式系统环境下扩展进程间的通讯。
5.2. 场景
- 跨系统数据传递。
- 流量的削峰填谷。
- 数据的分发和异步处理。
- 大数据分析与传递。
- 分布式事务。
请求过多的时候,可以在消息入库之前,将消息堆积到消息队列中,使之可以文件可靠的入库和执行。
5.3. 常用
常用的消息中间件有ActiveMQ、RabbitMQ、Kafka、RocketMQ等。
5.4. 本质
是一种接受数据、接受请求、存储数据、发送数据等功能的技术服务。客户端是生成者和消费者。
5.5. 核心组成部分
- 消息的协议。
- 持久化机制。
- 分发策略。
- 消息的高可用、高可靠。
- 容错机制。
协议
常用的消息中间件的协议有:OpenWire, AMQP, MQTT, Kafka, OpenMessage协议。
数据的存储和分发的过程中要遵循某种约定成俗的规范,这些约定成俗的规范称之为协议。
网络协议的三要素: 语法、语义、时序。
- 语法:是用户数据与控制信息的结构与格式,以及数据出现的顺序。
- 语义:解释控制信息每部分的意义。规定了需要发出何种控制信息,以及完成的动作与作出什么样的响应。
- 时序:对事件发生顺序的详细说明。
这里参考HTTP:
- 语法:http规定了请求报文和响应报文的格式。
- 语义:客户端主动发起请求称之为请求(post/get/...).
- 时序:一个请求对应一个响应,先有请求再有响应。
为什么消息中间件不直接使用HTTP协议:
- http请求报文头和响应报文头复杂,包含cookie、数据加解密、状态码、响应码等消息中间件不需要的附加功能。消息中间件负责数据传递、存储、分发,需要追求高性能、快速简洁。
- 大部分http都是短连接,在实际交互过程中,一个请求到响应很有可能会中断,中断后就不会进行持久化,会造成请求的丢失,不利于消息中间件的业务场景。消息中间件是一个长期获取消息的过程,出现问题和故障要对数据或消息进行持久化,目的保证消息和数据的高可靠和稳健执行。
AMQP协议
AMQP的支持者:RabbitMQ、ActiveMQ.
AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。 特性: 1:分布式事务支持。 2:消息的持久化支持。 3:高性能和高可靠的消息处理优势。
MQTT协议
MQTT的支持者:RabbitMQ、ActiveMQ.
MQTT协议:(Message Queueing Telemetry Transport)消息队列是IBM开放的一个即时通讯协议,物联网系统架构中的重要组成部分。 特点: 1:轻量。 2:结构简单。 3:传输快,不支持事务。 4:没有持久化设计。 应用场景: 1:适用于计算能力有限。 2:低带宽。 3:网络不稳定的场景。
OpenMessage协议
是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式消息中间件、流处理等领域的应用开发标准。 特点: 1:结构简单。 2:解析速度快。 3:支持事务和持久化设计。
Kafka协议
Kafka协议是基于TCP/IP的二进制协议。消息内部是通过长度来分割,由一些基本数据类型组成。 特点是: 1:结构简单。 2:解析速度快。 3:无事务支持。 4:有持久化设计。
消息队列持久化
数据存入磁盘,而不是在内存中随服务器重启断开而消失,使数据能够永久保存。
常见的持久化方式
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
文件存储 | 支持 | 支持 | 支持 | 支持 |
数据库 | 支持 | / | / | / |
消息的分发策略
MQ消息队列有如下角色:
- 生产者
- 存储消息
- 消费者
一般获取数据的方式为push或pull两种方式。经典的git就有推拉机制。
场景:
- 系统和服务较多,需要得知消息被哪个系统或者服务进行消费,需要一个分发策略。这就需要消费策略,或者称之为消费的方法论。
- 在发送消息的过程中可能出现网络抖动、故障等异常,需要消息中间件,必须支持消息重试机制策略。也就是在出现问题和故障的情况下需要保持消息的不丢失并且可以进行重发。
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
发布订阅 | 支持 | 支持 | 支持 | 支持 |
轮询分发(绝对公平) | 支持 | 支持 | 支持 | 支持 |
公平分发(能者多劳) | / | 支持 | 支持 | / |
重发 | 支持 | 支持 | / | 支持 |
消息拉取 | / | 支持 | 支持 | 支持 |
性能上:Kafka > RabbitMQ > RocketMQ > ActiveMQ.
消息队列高可用和高可靠
高可用:指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力。
当业务量增加时,请求也会变得过大,消息中间件必须支持集群部署,来达到高可用的目的。
集群模式
主从共享数据部署模式
所有的中间件采用一主多从的方式,共享一块数据区域。生产者将消息发送到Master节点,Master节点负责写入,Slave节点负责读取。
缺点:Master挂掉后,无法进行写入。
主从同步部署方式
写入在Master节点上,主节点会同步数据到Slave节点形成副本,和zookeeper或者redis的主从机制类同,可以达到负载均衡的效果。如果消费者有多个,就可以去不同的节点进行消费。
缺点:消息的拷贝和同步会占用较大的带宽资源。
在RabbitMQ中会有使用。
多主群同步部署模式
与主从同步部署方式相似,但可以从任意节点进行写入。
多主集群转发部署模式
元数据同步
如果插入的数据在broker-1中,元数据信息会存储数据的相关描述和记录存放的位置(队列)。它会对描述信息也就是元数据进行同步。如果在broker-2中进行消费,broker-2发现没有对应的消息,可以通过元数据信息去查询,然后返回对应的消息信息。
Master-Slave和Broker-Cluster组合方案
实现多主多从的热备机制来完成消息的高可用和数据的热备机制。在生产规模达到一定阶段的时候,使用这种的频率比较高。
集群的目的是保证消息服务器不会挂掉,如果出现故障依然可以保证消息服务继续使用。
集群:要么消息共享、要么消息同步、要么元数据共享。
高可靠机制
指系统可以无故障低持续运行。比如系统崩溃、报错、异常等不影响线上业务的正常运行。
可以从两方面考虑:
- 消息的传输:通过协议来保证系统间数据解析的正确性。
- 消息的存储:通过持久化来保证消息的可靠性。
5.6. 分布式事务
分布式事务指事务的操作位于不同的节点的时候,需要保证事务的ACID特性。
两阶段提交
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
准备阶段
协调者询问参与者事务是否执行成功,参与者返回事务执行结果。
提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在的问题:
- 同步阻塞,所有事务参与者在等待其他参与者响应的时候都处于同步阻塞状态,无法进行其他操作。
- 单点问题,协调者在2PC中起到非常大的作用,发生故障将会造成较大影响。特别是如果在阶段二发生故障,所有参与者会一直处于等待状态,无法完成其他操作。
- 数据不一致问题,在阶段二,如果协调者只发送了部分Commit信息,此时网络发生异常,那么只有部分参与者接收到Commit信息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
- 太过保守,任意一个阶段失败就会导致整个事务失败,没有完善的容错机制。
补偿事务(TCC)
严选、阿里、蚂蚁金服
TCC采用补偿机制。核心思想:针对每个操作都要注册一个与其对应的确认和补偿(撤销)操作。分为三个阶段:
(以转账为例)
- Try阶段主要是对业务系统做检测及资源预留。(调用远程接口巴双方的钱冻结起来)
- Confirm阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不出错的,只要Try成功,那么Confirm一定成功。(执行远程调用的转账操作,转账成功进行解冻)
- Cancel阶段主要是在业务执行错误,需要回滚的状态下执行的业务撤销,预留资源释放。(如果第二步执行失败,调用远程接口对应的解冻方法)
优点:相比于2PC,实现和流程相对简单,但数据一致性也要差一点。
缺点:Confirm和Cancel都有可能失败。TCC属于应用层的一种补偿方式,所以需要实现的时候写很多补偿代码。在一些场景和业务流程中TCC不太好定义和处理。
本地消息表
支付宝、微信支付主动查询支付状态,对账单的形式。
异步确保。
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证对这两个表的操作满足事务的特性,并且使用了消息队列来保证最终一致性。
- 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定被写入到本地消息表中。
- 之后将本地消息表中的消息转发到RabbitMQ、Kafka等消息队列中,如果转发成功则消息从本地消息表中删除,否则继续重新转发。
- 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
优点:避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,实现相对复杂。
MQ事务消息
异步场景,通用型较强,扩展性较高。
部分三方的MQ支持事务消息,如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上主流的MQ,如Kafka不支持。
RocketMQ实现事务大致思路:
- 第一阶段Prepared消息,会拿到消息的地址。第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
- 也就是说在业务方法内要向消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了,RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优点:实现了最终一致性,不需要依赖本地数据库事务。通用型强,扩展方便,耦合度低,方案成熟。
缺点:实现难度大,主流MQ不支持。基于消息中间件,只适合异步场景。消息会延迟处理,需要业务能够容忍。
尽量避免分布式事务,将非核心业务做成异步。