最常见的方式就是异步消息通信。使用消息机制时,服务之间的通信采用异步交换消息的方式完成。基于消息机制的应 用程序通常使用消息代理,它充当服务之间的中介。另一种选择是使用无代理架构,通过直 接向服务发送消息来执行服务请求。服务客户端通过向服务发送消息来发出请求。如果希望 服务实例回复,服务将通过向客户端发送单独的消息的方式来实现。由于通信是异步的,因 此客户端不会堵塞和等待回复。相反 ,客户端都假定回复不会马上就收到。1.1 消息的组成
消息由消息头部和消息主体组成。标题是名称与值对的集合,描述正在发送的数据的元数据。除 了消息发送者提供的名称与值 对之外,消息头部还包含其他信息,例如发件人或消息传递基础设施生成的唯 一消息ID,以及可选的返回地址,该地址指定发送回复的消息通道。消息正文是以文本或二进制格式发送 的数据。
• 文档:仅包含数据的通用消息。接收者决定如何解释它。对命令式消息的回复是文档 消息的一种使用场景。• 命令:一条等同于RPC请求的消息。它指定要调用的操作及其参数。• 事件:表示发送方这一端发生了重要的事件。事件通常是领域事件,表示领域对象 (如 Order 或 Customer) 的状态更改 。消息通过消息通道进行交换。发送方中的业务逻辑调用发送端接口,该接口封装底层 通信机制。发送端由消息发送适配器类实现,该消息发送适配器类通过消息通道向接收器发送消息。消息通道是消息传递基础设施的抽象。调用接收器中的消息处理程序适配器类来处理消息。它调用接收方业务逻辑实现的接收端接又。任意数量的发送方都可以向通道发送消息。类似地,任何数量的接收方都可以从通道接收消息。• 点对点通道向正在从通道读取的一个消费者传递消息。服务使用点对点通道来实现前 面描述的 一对一交互方式。例如,命令式消息通常通过点对点通道发送。• 发布- 订阅通道将一条消息发给所有订阅的接收方。服务使用发布—订阅通道来实现 前面描述的一对多交互方式。例如,事件式消息通常通过发布-订阅通道发送。当客户端和服务使用请求/响应或异步请求/响应进行交互时,客户端会发送请求,服务会发回回复。两种交互方式之间的区别在于,对于请求/响应,客户端期望服务立即响应, 而对于异步请求/响应,则没有这样的期望。消息机制本质上是异步的,因此只提供异步请求/响应。但客户端可能会堵塞,直到收到回复。客户端和服务端通过交换一对消息来实现异步请求/响应方式的交互。如图所示,客户端发送命令式消息,该消息指定要对服务执行的操作和参数,这些内容通过服务拥有的点对点消息通道传递。该服务处理请求,并将包含结果的回复消息发送到客户端拥有的点对点通道。1.2 使用消息的好处
- 松耦合:客户端发起请求时只要发送给特定的通道即可,客户端完全不需要感知服务 实例的情况,客户端不需要使用服务发现机制去获得服务实例的网络位置。
- 消息缓存:消息代理可以在消息被处理之前 一直缓存消息 。像HTTP这样的同步请求/ 响应协议,在交换数据时,发送方和接收方必须同时在线 。然而,在使用消息机制的 情况下,消息会在队列中缓存,直到它们被接收方处理。这就意味着,例如,即使订 单处理系统暂时离线或不可用,在线商店仍旧能够接受客户的订单。订单消息将会在 队列中缓存(并不会丢失)。
- 明确的进程间通信:基于RPC的机制总是企图让远程服务调用跟本地调用看上去没 什么区别(在客户端和服务端同时使用远程调用代理)。然而,因 物理定律(如服 务器不可预计的硬件失效)和可能的局部故障 ,远程和本地调用还是大相径庭的。消 息机制让这些差异变得很明确,这样程序员不会陷入 一种“太平盛世” 的错觉。
2. 测试分析
测试分析需要从局部(发送方/订阅方/中间件)出发,放眼于整体。针对异步通信(如消息)测试,需要考虑消息在发送发是否能正常发送、消息中间件能否正常处理、订阅方能否正常订阅到消息以及处理消息。2.1 发送方角度
其中,消息体报文正确性属于契约测试,报文正确性由生产方保证(即发送方),需要针对消息报文正确性做出口报文验证。消息发送成功与否,需要发送方断言是否正常发送消息成功。(可以类比于对接口响应的断言)。消息是否重复投递需要着重说明一下,由于异步通信增加了高可用性,但是降低了可靠性。一般情况下,相同的消息是不能重复发送的,但是特殊情况除外,例如消息在中间件处理过程丢失了,这样就需要重复投递;当然我主要说明的是正常场景。2.2 中间件
前文也介绍了中间件的作用。存储顺序对于确保消息的正确处理和维护数据一致性至关重要,一般有下列几种存储策略:- FIFO(First-In-First-Out,先进先出): 许多消息队列和主题支持FIFO顺序,这意味着消息按照它们被发送的顺序被消费。这是最常见的存储顺序,适用于需要维持消息顺序的应用场景,如交易系统、实时数据流处理等。
- Priority-Based(基于优先级): 在这种模式下,消息根据预定义的优先级排序存储和处理,而非严格按照到达顺序。高优先级的消息会在低优先级消息之前被处理,适用于紧急或重要消息需要优先处理的场景。
- Partitioning(分区): 对于大规模系统,消息可能会被分布在多个分区中以提高吞吐量和可扩展性。在这种情况下,每个分区内部可能遵循FIFO或其他顺序原则,但跨分区则不保证顺序。例如,Apache Kafka支持按键(key)分区,同一个键的消息会在同一分区中保持顺序。
- Timestamp Ordering(时间戳排序): 消息根据其携带的时间戳进行排序。这对于需要按事件发生时间处理消息的应用来说很有用,如时间序列数据分析、日志处理等。
- Custom Order Policies(自定义排序策略): 部分高级消息中间件允许用户定义自己的排序逻辑,以满足特定业务需求。
通常采取FIFO(First-In-First-Out,先进先出)策略,许多消息队列和主题支持FIFO顺序,这意味着消息按照它们被发送的顺序被消费。这是最常见的存储顺序,适用于需要维持消息顺序的应用场景,如交易系统、实时数据流处理等。针对基于优先级/时间戳排序存储策略场景,需要测试消息有序排列的正确性。而消息是否阻塞则是需要测试中间件自身的消息处理性能,挖掘其性能瓶颈。2.3 订阅方
针对订阅方的测试点,我重点介绍下消息乱序和消息重复消费。例如下单支付后发起退款的场景。因为支付和退款,收单都要监听下游支付的回执信息,则很有可能退款回执(消息)先于支付回执到达收单。那么针对这种场景,订阅方的处理逻辑则需要做到兼容。理论上,任何中间件都是不可靠的,重复发送消息的可能性仍然存在。那么订阅方则需要针对重复投递的消息做幂等处理,当然可以基于消息的messageId做判断,监听到消费成功后的messageId则丢弃即可。
下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!