BLOG
Enjoy when you can, and endure when you must.
AUG 05, 2016/后端开发与架构
微服务的原则

最近静下心来阅读了一下 Sam Newman 的《微服务设计》(Building Microservices)一书,受到很多启发,还是称得上收获颇多。这里算是做一个读书笔记,因此大部分的内容来自于原书,对一些重点关注的点进行记录。以下主要围绕书中的最后一章,即总结,然后回归到一些具体的点上。

围绕业务概念建模

围绕业务的限界上下文定义的接口,比围绕技术概念定义的接口更加稳定。针对系统如何工作这个领域进行建模,不仅可以帮助我们形成更稳定的接口,也能确保我们能够更好地反映业务流程的变化。使用界限上下文来定义可能的领域边界。

在这句话中,有几个重要的概念值得理解:

1. 限界上下文:一个由显式边界限定的特定职责

任何一个给定的领域都包含多个限界上下文,每个限界上下文中的东西分成两部分,一部分不需要与外部通信,另一部分则需要。每个上下文都有明确的接口,该接口定义了它会暴露哪些模型给其他的上下文。如果你需要从一个限界上下文获取信息,或者向其发起请求,需要使用模型和它的显式边界进行通信。

一般来讲,微服务应该清晰地和限界上下文保持一致

由上可知,明确限界上下文是划分出微服务的关键所在。而为服务找到合适的限界上下文就要求你必须了解一个领域。服务的界限划分错误,可能会导致不得不频繁地更改服务间的协作,而这种更改成本很高,最后反而得不偿失。所以花时间了解系统是做什么的,然后尝试识别出清晰的模块边界,这是划分服务之前所必须的。

2. 松耦合和高内聚

如果做到了服务之间的松耦合,那么修改一个服务就不需要修改另一个服务。这也符合微服务最重要的一点:能够独立修改及部署单个服务而不需要修改系统的其他部分。一个松耦合的服务应该尽可能少地知道与之协作的那些服务的信息。高内聚则是要求把相关的行为聚集在一起,把不相关的行为放在别处。

这里的关键也是找到问题域的边界,它可以确保相关的行为能放在同一个地方,并且它们会和其他边界以尽量松耦合的形式进行通信。

 

自动化的文化

微服务引入了很多复杂性,其中的关键部分是,我们不得不管理大量的服务。引入持续集成持续交付这些元素势在必行。自动化测试也必不可少。

通过调用一个统一的命令行,以相同的方式把系统部署到各个环境是一个很有用的实践,这也是采用持续交付对每次提交后的产品质量进行快速反馈的一个关键部分。

使用环境定义来帮助明确不同环境间的差异。

创建自定义镜像来加快部署。这对应当今云服务来说非常简单且易于实施。

创建全自动化不可变服务器,这会更容易定位系统本身的问题。通过把配置都存到版本控制中,我们可以自动化重建服务,甚至重建整个环境。但如果部署完成后,有人登录到机器上修改了一些东西呢?这就会导致机器上的实际配置和源代码管理中的配置不再一致,这个问题叫配置漂移。为了避免这个问题,可以禁止对任何运行的服务器做手动修改。

 

隐藏内部实现细节

为了使一个服务独立于其他服务,最大化独自演化的能力,隐藏实现细节至关重要。这一点实际和“围绕业务建模”息息相关。但另一点值得注意的是,服务还应该隐藏它们的数据库,以避免陷入数据库耦合。这对于单块系统来说,也通常是一个大的耦合点。就此延伸,我们可能需要考虑对数据库进行分离,并且要关注一个非常严肃的问题就是事务。事务可以保证一些事情要么都发生,要么都不发生。一个事务可以帮助我们的系统从一个一致的状态迁移到另一个一致的状态。在使用单块表结构时,所有的创建或者更新操作都可以在一个事务边界内完成。分离之后,这种好处就没有了,所以必须考虑如何“换个思路”保证系统状态的一致性。以下是几种思路:

  • 对关联或失败的操作在之后进行尝试或重试,以在未来的某个时间达到最终一致性
  • 拒绝整个操作,做手动回退。如果已经提交了的事务,则考虑发起补偿事务来抵消之前的操作。但如何该补偿事务还是失败了,在这种情况下,要么再次重试,要么使用一些后台任务来清除这些不一致的状态;
  • 还有一种方法是使用分布式事务。分布式事务会横跨多个事务,然后使用一个叫作事务管理器的工具来统一编配其他底层系统中运行的事务。一个分布式事务会保证整个系统处于一致的状态。唯一不同的是,这里的事务会运行在不同系统的不同进程中,通常它们之间使用网络进行通信。处理分布式事务常用的算法是两阶段提交。在这种方式中,首先是投票阶段。在这个阶段,每个参与者会告诉事务管理器它是否应该继续。如果事务管理器收到的所有投票都是成功,则会告知它们进行提交操作。只要收到一个否定的投票,事务管理器就会让所有的参与者回退。

但无论如何,这些方案都会大大增加复杂性。也许我们应该从另一个方面来思考,就是是否真的需要这种跨系统的单事务?或者从一些非技术性的角度,显式地创建一个概念来表示这个事务。

另外,类似于报表这样的功能可能需要将大量的数据进行整个。这个时候,可以使用数据泵或事物数据泵以将跨多个服务的数据整合到一起。

对于服务间的交互,应尽量选用与技术无关的 API,从而让你能在不同的服务内自由选择不同的技术栈。

 

让一切去中心化

为了最大化微服务能带来的自治性,我们需要持续寻找机会,给拥有服务的团队委派决策和控制权。在这个过程初期,只要有可能,就尝试使用资源自助服务,允许人们按需部署软件,使开发和测试尽可能简单,并且避免让独立的团队来做这些事。

康威定律告诉我们,任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

构建微服务不仅要从技术的角度去思考,更得从团队的角度入手。伟大的软件来自于伟大的人。对于技术领导者,更重要的事情是帮助团队一起成长,只有让团队所有人理解这个愿景,才能让他们积极地参与到愿景的实现和调整中来。

 

可独立部署

应始终努力确保微服务可独立部署。这会使得无论微服务本身 ,还是团队,都越来越具有自治性。

部署应考虑采用单主机单服务的模式。这样可以减少部署一个服务引发的副作用,比如影响另一个完全不相干的服务。同时,这样还可以降低监控和错误恢复的难度。

考虑使用蓝/绿部署金丝雀部署技术,区分部署和发布,降低发布出错的风险。使用蓝/绿部署时,我们会部署两份软件,但只有一个接受真正的请求。在新版本部署之后,首先对其运行一些测试,等测试没有问题后,再切换生产负荷到新版本上。通常情况下,也应保留旧版本一小段时间,这样如果发现任何错误,都能快速恢复到旧版本。

金丝雀发布是指通过将部分生产流量引流到新部署的系统,来验证系统是否按预期执行。金丝雀发布与蓝/绿发布的不同之处在于,新旧版本共存的时间更长,而且经常会调整流量。金丝雀发布可以帮助我们用实际的请求来验证软件的新版本是否可靠,是否可能是推出的一个糟糕的新版本,提供工具来帮助控制风险。

 

隔离失败

微服务架构能比单块架构更具弹性,前提是我们了解系统的故障模式,并做出相应的计划。如果我们不考虑调用下游可能会失败的事实,系统会遭受灾难性的级联故障,系统也会比以前更加脆弱。当使用网络调用时,不要像使用本地调用那样处理远程调用,因为这样会隐藏不同的故障模式。所以每个服务的实例都应该追踪和显示其下游服务的健康状态,从数据库到其他合作服务。

应确保正确的设置超时,实现舱壁隔离不同的连接池,并实现一个断路器,以便在第一时间避免给一个不健康的系统发送调用,从而限制故障组件的连带影响。

  • 超时:应给所有的跨进程调用设置超时,并选择一个默认的超时时间。当超时发生后,记录到日志里看看发生了什么,并相应地调整它们。这里必须斟酌的一点是需要等待多长时间?
  • 断路器:当对一个下游资源的请求发生一定数量的失败后,应采取一种“断路”机制,对接下来继续涌入的请求采取快速失败的做法,以免造成整个系统的崩溃。在一段时间后,客户端发送一些请求查看下游服务是否已经恢复,如果得到了正常响应,则将断路器重置。
  • 舱壁:舱壁是把自己从故障中隔离开的一种方式。我们应该为每个下游服务的连接使用不同的连接池。这样的话,如果下游服务器将来运行缓慢,只有那一个连接池会受影响,其他调用仍可正常进行。关注点分离也是实现舱壁的一种方式。通过把功能分离成独立的微服务,减少了因为一个功能的宕机而影响另一个的可能性。

在三种模式中,超时和断路器能够帮助我们在资源受限时释放它们,舱壁可以在第一时间确保它们不称为限制

另外,应从反脆弱的角度来思考,通过引发故障来确保系统的容错性。

还有一个值得思考的是 CAP 定理,其核心是告诉我们,在分布式系统中有三方面需要彼此权衡:一致性(consistency)可用性(availability)分区容忍性(partition tolerance)。一致性是当访问多个节点时能得到同样的值。可用性意味着每个请求都能获得响应。分区容忍性是指集群中的某些节点在无法联系后,集群整体还能继续进行服务的能力。现实中具体怎么做,应该视情况而定。更多的时候,我们可以把关于 CAP 定义的权衡,推到单独服务的每个功能中去。

 

高度可观察

在微服务的模式下,我们不能依靠观察单一服务实例,或一台服务器的行为,来看系统是否运行正常。相反,应从整体上看待正在发生的事情。通过注入合成事务到系统中,模拟真实用户的行为,从而使用语义监控来查看系统是否运行正常。聚合日志和数据,这样当遇到问题时,就可以深入分析原因。

当需要重现系统在生产环境中的交互时,可以使用关联标识帮助跟踪系统间的调用。在触发第一个调用时,生成一个 GUID。然后把它传递给所有的后续调用。传递关联标识时需要保持一致性,这是使用共享的、薄客户端库的一个强烈的信号。

COMMENTS
LEAVE COMMNT