所谓的边车模式,对应于我们生活中熟知的边三轮摩托车。也就是说,我们可以通过给一个摩托车加上一个边车的方式来扩展现有的服务和功能。这样可以很容易地做到"控制"和"逻辑"的分离。

也就是说,我们不需要在服务中实现控制面上的东西,如监视、日志记录、限流、熔断、服务注册、协议适配转换等这些属于控制面上的东西,而只需要专注地做好和业务逻辑相关的代码,然后,由"边车"来实现这些与业务逻辑没有关系的控制功能。

# 边车模式设计

具体来说,你可以理解为,边车就有点像一个服务的Agent,这个服务所有对外的进出通讯都通过这个Agent来完成。这样,我们就可以在这个Agent上做很多文章了。但是,我们需要保证的是,这个Agent要和应用程序一起创建,一起停用。

边车模式有时候也叫搭档模式,或是伴侣模式,或是跟班模式。就像我们在《编程范式游记》中看到的那样,编程的本质就是将控制和逻辑分离和解耦,而边车模式也是异曲同工,同样是让我们在分布式架构中做到逻辑和控制分离。

对于像"监视、日志、限流、熔断、服务注册、协议转换……"这些功能,其实都是大同小异,甚至是完全可以做成标准化的组件和模块的。一般来说,我们有两种方式。

  • 一种是通过SDK、Lib或Framework软件包方式,在开发时与真实的应用服务集成起来。

  • 另一种是通过像Sidecar这样的方式,在运维时与真实的应用服务集成起来。

这两种方式各有优缺点。

  • 以软件包的方式可以和应用密切集成,有利于资源的利用和应用的性能,但是对应用有侵入,而且受应用的编程语言和技术限制。同时,当软件包升级的时候,需要重新编译并重新发布应用。

  • 以Sidecar的方式,对应用服务没有侵入性,并且不用受到应用服务的语言和技术的限制,而且可以做到控制和逻辑的分开升级和部署。但是,这样一来,增加了每个应用服务的依赖性,也增加了应用的延迟,并且也会大大增加管理、托管、部署的复杂度。

注意,对于一些"老的系统",因为代码太老,改造不过来,我们又没有能力重写。比如一些银行里的很老的用C语言或是COBAL语言写的子系统,我们想把它们变成分布式系统,需要对其进行协议的改造以及进行相应的监控和管理。这个时候,Sidecar的方式就很有价值了。因为没有侵入性,所以可以很快地低风险地改造。

Sidecar服务在逻辑上和应用服务部署在一个结点中,其和应用服务有相同的生命周期。对比于应用程序的每个实例,都会有一个Sidecar的实例。Sidecar可以很快也很方便地为应用服务进行扩展,而不需要应用服务的改造。比如:

  • Sidecar可以帮助服务注册到相应的服务发现系统,并对服务做相关的健康检查。如果服务不健康,我们可以从服务发现系统中把服务实例移除掉。

  • 当应用服务要调用外部服务时, Sidecar可以帮助从服务发现中找到相应外部服务的地址,然后做服务路由。

  • Sidecar接管了进出的流量,我们就可以做相应的日志监视、调用链跟踪、流控熔断……这些都可以放在Sidecar里实现。

  • 然后,服务控制系统可以通过控制Sidecar来控制应用服务,如流控、下线等。

于是,我们的应用服务则可以完全做到专注于业务逻辑。

注意,如果把Sidecar这个实例和应用服务部署在同一台机器中,那么,其实Sidecar的进程在理论上来说是可以访问应用服务的进程能访问的资源的。比如,Sidecar是可以监控到应用服务的进程信息的。另外,因为两个进程部署在同一台机器上,所以两者之间的通信不存在明显的延迟。也就是说,服务的响应延迟虽然会因为跨进程调用而增加,但这个增加完全是可以接受的。

另外,我们可以看到这样的部署方式,最好是与Docker容器的方式一起使用的。为什么Docker一定会是分布式系统或是云计算的关键技术,相信你从我的这一系列文章中已经看到其简化架构的部署和管理的重要作用。否则,这么多的分布式架构模式实施起来会有很多麻烦。

# 边车设计的重点

首先,我们要知道边车模式重点解决什么样的问题。

  1. 控制和逻辑的分离。
  2. 服务调用中上下文的问题。

我们知道,熔断、路由、服务发现、计量、流控、监视、重试、幂等、鉴权等控制面上的功能,以及其相关的配置更新,本质来上来说,和服务的关系并不大。但是传统的工程做法是在开发层面完成这些功能,这就会导致各种维护上的问题,而且还会受到特定语言和编程框架的约束和限制。

而随着系统架构的复杂化和扩张,我们需要更统一地管理和控制这些控制面上的功能,所以传统的在开发层面上完成控制面的管理会变得非常难以管理和维护。这使得我们需要通过Sidecar模式来架构我们的系统。

边车模式从概念上理解起来比较简单,但是在工程实现上来说,需要注意以下几点。

  • 进程间通讯机制是这个设计模式的重点,千万不要使用任何对应用服务有侵入的方式,比如,通过信号的方式,或是通过共享内存的方式。最好的方式就是网络远程调用的方式(因为都在127.0.0.1上通讯,所以开销并不明显)。

  • 服务协议方面,也请使用标准统一的方式。这里有两层协议,一个是Sidecar到service的内部协议,另一个是Sidecar到远端Sidecar或service的外部协议。对于内部协议,需要尽量靠近和兼容本地service的协议;对于外部协议,需要尽量使用更为开放更为标准的协议。但无论是哪种,都不应该使用与语言相关的协议。

  • 使用这样的模式,需要在服务的整体打包、构建、部署、管控、运维上设计好。使用Docker容器方面的技术可以帮助你全面降低复杂度。

  • Sidecar中所实现的功能应该是控制面上的东西,而不是业务逻辑上的东西,所以请尽量不要把业务逻辑设计到Sidecar中。

  • 小心在Sidecar中包含通用功能可能带来的影响。例如,重试操作,这可能不安全,除非所有操作都是幂等的。

  • 另外,我们还要考虑允许应用服务和Sidecar的上下文传递的机制。 例如,包含HTTP请求标头以选择退出重试,或指定最大重试次数等等这样的信息交互。或是Sidecar告诉应用服务限流发生,或是远程服务不可用等信息,这样可以让应用服务和Sidecar配合得更好。

  • 我们要清楚Sidecar适用于什么样的场景,下面罗列几个。

  • 一个比较明显的场景是对老应用系统的改造和扩展。

  • 另一个是对由多种语言混合出来的分布式服务系统进行管理和扩展。

  • 其中的应用服务由不同的供应商提供。

  • 把控制和逻辑分离,标准化控制面上的动作和技术,从而提高系统整体的稳定性和可用性。也有利于分工——并不是所有的程序员都可以做好控制面上的开发的。

  • 我们还要清楚Sidecar不适用于什么样的场景,下面罗列几个。

  • 架构并不复杂的时候,不需要使用这个模式,直接使用API Gateway或者Nginx和HAProxy等即可。

  • 服务间的协议不标准且无法转换。

  • 不需要分布式的架构。

# 小结

好了,我们来总结一下今天分享的主要内容。首先,我介绍了什么是边车模式。为了把诸如监视、日志、限流等控制逻辑与业务逻辑分离解耦,我们可以采用边车模式。与之对应的另一种实现控制逻辑的方式是库或框架。虽然相对来说边车模式资源消耗较大,但控制逻辑不会侵入业务逻辑,还能适应遗留老系统的低风险改造。

边车作为另一个进程,和服务进程部署在同一个结点中,通过一个标准的网络协议,如HTTP来进行通信。这样可以做到低延迟和标准化。同时,用Docker来打包边车和服务两者,可以非常方便部署。最后,我指出了边车模式适用和不适用的场景。下篇文章中,我们讲述服务网格。希望对你有帮助。

也欢迎你分享一下你实现服务的同时有没有实现边车模式?有没有用到Docker来打包边车和服务两者?

文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。