本文介绍微服务之间交互的通用设计模式,这些设计模式对为服务之间的交互定义契约,服务的生产者和调用者都需要遵守这些契约,才能保证微服务不出问题。
读者容错模式
读者容错模式(Tolerant Reader)指微服务化中服务提供者和消费者之间如何对接口的改变进行容错。从字面上来讲,消费者需要对提供者提供的功能进行兼容性设计,尤其对服务提供者返回的内容进行兼容,或者解决在服务者改变接口或者数据的格式的情况下,如何让服务消费者正常进行。
任何一个产品在设计时都无法遇见将来可能增加的所有需求,服务的开发者通常通过迭代及时的增加新功能,或者让服务提供的API自然的演进。不过,服务提供者对外提供的接口的数据格式的改变、增加和删除,都会导致服务的消费者不能正常工作。
因此,在服务消费者处理服务提供者返回的消息的过程中,需要对服务返回的消息进行过滤,只提取自己需要的内容,对多余或者未知的内容采取抛弃的策略,而不是硬生生的抛错处理。
在实现过程中不推荐使用严格的校验策略,而是推荐使用宽松的校验策略,即使服务消费者拿到的消息报文发生了改变,程序也只需尽最大努力提取需要的数据,同时忽略不可识别的数据。只有在服务消费者完全不能识别接收到的消息,或者无法通过识别的信息继续处理流程时,才能抛出异常。
服务的消费者的容错模式忽略了新的消息项、可选的消息项、未知的数据值及服务消费者不需要的数据项。
在很多业务都需要使用枚举类型,在微服务平台下,在研发流程规范中定义了一条枚举值使用规范:
在服务接口的定义中,参数可以使用枚举值,在返回值的DTO中进制使用枚举值。
这条规范就是读者容错模式在实践中的一个实例,之所以在参数中允许使用枚举值,是因为如果服务的提供者升级了接口,增加了枚举,若服务的消费者并没有感知,则服务的消费者得知新的枚举值就可以传递新的枚举值了;但是如果接口的返回DTO中使用了枚举值,并且因为某种原因增加了枚举值,则服务消费者在反序列化枚举时就会报错,因此在返回值中我们应该使用字符串等互相认可的类型,来做到双方的互相兼容,并实现读者容错模式。
消费者驱动契约模式
消费者驱动契约模式用来定义服务化中服务之间交互接口改变的最佳规则。
服务契约分为:提供者契约、消费者契约及消费者驱动的契约,他从期望与约束的角度描述了服务提供者与服务消费者之间的联动关系。
-
提供者契约:是我们最常用的一种服务契约,顾名思义,提供者契约是以提供者为中心的,提供者提供了什么功能和消息格式,各消费者都会无条件的遵守这些约定,不论消费者实际需要多少功能,消费者接受了提供者的契约时,都会提供服务提供者的规则来使用服务。
-
消费者契约:是对某个消费者的需求进行更为精确的描述,在一次具体的服务交互场景下,代表消费者需要提供者提供功能中的哪部分数据。消费者契约可以被用来标识现有的提供者契约,在可以用来发现一个尚未明确的提供者契约。
-
消费者驱动的契约:代表服务提供者向其所有当前消费者承诺遵守的约束。一旦各消费者把自己的具体期望告知提供者,则提供者无论在什么时候和场景下,都不应该打破契约。
在现实的服务交互设计中,上面这三种契约是同时存在的,比如在交易系统在完成一笔支付后,需要到账务系统为商户入账,在这个过程中,服务契约表现如下。
- 生产者契约:服务系统提供Dubbo服务化接口,参数为商户账户ID、入账订单号和入账金额。
- 消费者契约:账务系统返回DTO,包含商户账户ID、入账订单号、入账金额、入账时间、账务流水号、入账状态等,而交易系统只需使用其中的入账订单号和入账状态。
- 消费者驱动的契约:为了保证资金安全,交易系统作为入账的发起者向账务提出要求,需要服务做幂等和滤重处理,对重复的入账请求进行拦截;账务系统在接受这个契约后,即使将来有任何改变,也不能打破这个限制,否则就会造成资金的损失,这在金融系统中是最严重的问题。
服务之间的交互需要使用的三种服务契约如下图所示。
从上图可以看到,服务提供者契约是服务提供者单方面定下的规则,而一个消费者契约会成为提供者契约的一部分,多个服务消费者可以对服务提供者提供约束,服务提供者需要在将来遵守服务消费者提出的契约,这就是消费者驱动的契约。
去数据共享模式
与SOA服务化对比,微服务是去ESB总线、去中心化及分布式的;而SOA还是以ESB为核心实现遗留系统的集成,以及基于Web Service为标准实现的通用的面向服务的架构。在微服务领域,微服务之间的交互通过定义良好的接口来实现,不允许使用共享数据来实现。
在实践的过程中,有些方案的设计使用缓存或者数据库作为两个微服务之间的纽带,在业务流程的处理过程中,为了处理简单,前一个服务将中间结果存入数据库或者缓存,下一个服务从缓存或者数据库中拿出数据继续处理。处理流程如下图所示。
这种交互流程的缺点如下。
- 使得微服务之间的交互除了接口契约,还存在数据存储契约。
- 上游的数据格式发生变化时,可能导致下游的处理逻辑出现问题。
- 多个服务共享一个资源服务,对资源服务的运维难以划清职责和界限。
- 在做双机房独立部属时,需要考虑服务和资源的路由情况,跨机房的服务调用不能使用独立的资源部署模式,因此难以实现服务自治。
因此,在设计微服务架构时,一定不要共享缓存和数据库等资源,也不要使用总线模式,服务之间的通信和交互只能依赖定义良好的接口,通常使用RESTful样式的API或者透明的RPC调用框架。