Spring Cloud微服务和分布式系统实践
上QQ阅读APP看书,第一时间看更新

随着移动互联网的兴起以及手机和平板电脑的普及,当今时代已经从企业的管理系统时代走向了移动互联网系统的时代。自2000年以来,我国移动互联网获得了长足的发展,越来越多的人通过移动设备连接上了互联网。在诸多移动设备中,手机无疑是最具代表性的。图1-1为中国互联网络信息中心(CNNIC)在2018年底发布的第43次调查报告的数据。

图1-1 中国手机网民增长统计分析图(由中国互联网络信息中心发布)

从图1-1中可以看出,经过近10年的发展,以手机上网为代表的移动互联网已经成了时代的主流。到2018年年末,手机网民更是占据了整体网民的98.6%。在这一波浪潮下诞生出了许多新鲜事物,如电商、移动支付、共享汽车和共享单车,它们深刻地改变了人们的生活。

从图1-1中可以看出,2008年到2009年,手机网民占整体网民的比例呈极速增长趋势;2009年到2016年,手机网民人数快速增长;但是2016年以后,手机网民人数的增长速度就渐渐慢下来了。由此可见,手机网民数量的增速已然减缓。这只是表明,以手机上网为代表的移动互联网的普及速度开始减缓,但深入移动互联网业务的时代却才刚刚开始,尤其是企业互联网化已经成为当前我国发展的一个重要方向。简而言之,现今移动互联网已经从高速的普及阶段转变为深耕阶段。

基于我国人口众多、业务繁杂的现状,移动互联网系统也存在着特殊性。我国网站的会员数比其他国家的多得多,随之而来的必然是业务数据的增加,这就造成了热门网站面临大数据存储的问题。对于热门网站来说,在推出热门商品时,一般都会在发售之前先打广告。因此,在热门商品上线销售的刹那,往往会有大量会员抢购,这便会引发互联网系统特有的高并发现象。一般来说,网站对请求和响应的时间间隔要求在5秒之内,否则会导致客户等待时间过长,影响客户对网站的忠诚度,因为没人会愿意使用一个需要等待几十秒都不能响应的网站。

大数据、高并发和快响应是互联网系统的必然要求。但是在大数据和高并发的情况下,要求快响应是比较苛刻的,因为大量的数据会导致查找数据的时间变长,高并发会使互联网系统因繁忙而变慢,进而影响响应速度。

在大数据、高并发和快响应的要求下,单机系统已经不可能满足现今互联网了。为了满足互联网的苛刻要求,网站系统已经从单机系统发展为多台机器协作的系统,因而互联网系统已经从单机系统演变为多台机器的系统,我们把这种多台机器相互协作完成企业业务功能的系统,称为分布式系统。虽然与此同时,分布式系统也会引入许多新的问题,并且这些问题也很复杂,很难处理,但互联网技术经过多年的发展和积累,已经拥有了许多成功的分布式系统经验和实践,因此我们可以站在巨人的肩膀上,无须重复发明轮子。

分布式系统由一组为了完成共同任务而协调工作的计算机节点组成,它们通过网络进行通信。分布式系统能满足互联网对大数据存储、高并发和快响应的要求,采用了分而治之的思想。从实际成本来说,可以使用廉价的普通机器进行独立运算。然后通过相互协助来完成网站业务,这样就可以完成单个计算机节点无法完成的计算和存储任务了。为了让大家能够更好地了解分布式系统的好处,这里先给出一个简易的分布式架构,如图1-2所示。

我们先结合图1-2来讨论这个架构的工作原理。首先,移动端或者PC端会通过互联网对网站发出请求,当请求到达服务器后,网关通过合理的路由算法将请求分配到各个真实提供服务的Web服务器中,这样多台廉价的普通Web服务器就可以处理来自各方的请求了。严格来讲,这个架构也有诸多问题,但是因为这些问题相当复杂,所以本节暂且不讲,留到后面的章节再谈。这里先讨论它的几个好处。

高性能:因为大量请求被合理地分摊到各个节点,使每一台Web服务器的压力减小,并且多个请求可以使用多台机器处理,所以能处理更多的请求和数据,性能更高。如此,便解决了互联网系统的3个关键问题——大数据、高并发和快响应。

高可用:在单机系统中,机械故障会造成网站不可用。但在分布式系统中,如果某个节点出现故障,系统会自动发现这个故障,不再向这个节点转发请求,系统仍旧可以工作。自动避开存在故障的节点,继续对外提供服务,这就是分布式的高可用性。

可伸缩性:当现有机器的性能不能满足业务的发展时,我们需要更多的机器提供服务。只要改造路由算法,就能够路由到新的机器,从而将更多的机器容纳到系统中,继续满足大数据、高并发和快响应的要求。从另一个方面来说,如果现有的机器已经大大超出所需,则可以减少机器,从而节省成本。

可维护性:如果设备当中有一台机器因某种原因不能对外提供服务,如机器出现故障,此时只需要停止那些出现故障的节点,对其进行处理,然后重新上线即可。

灵活性:例如,当需要更新系统时,只需要在非高峰期,停用部分节点,将这些节点更新为最新版本,然后再通过路由算法将请求路由到这些更新后的节点,最后更新那些旧版本的节点,就可以让网站在更新系统时不间断地对外提供服务了。

图1-2 简易分布式架构

综上所述,分布式系统的使用,带来了许多的便利,满足了当今互联网系统的需求,成了时下的技术热点。

使用分布式系统,就意味着需要将系统按照不同的维度(如业务、数据等)切分给各个不同节点的机器。因此,需要对业务或者数据进行合理切分,让多个机器节点能够相互协作,以满足业务功能的需要。在下面几节中,我们将讨论分布式常见的切分方法。但是请注意,这里只是讨论了常用的切分方法,并不是只有这些切分方法。

1.水平切分

所谓水平切分,就是将同一个系统部署到多台机器上,如图1-3所示。

图1-3 水平切分

从图1-3中可以看到,单体的Web服务器变成了多个Web服务器节点,每台服务器都有相同的应用,都能独立完成计算,互不相干。这样的切分有以下几个好处。

简单:只需要实现一个路由算法,将请求合理地分配到各个节点即可。目前能够快速方便地实现这个功能的网关包括Nginx、Netflix Zuul和Spring Cloud Gateway等。

独立:每个节点都有完整的运算功能,不需要依赖其他节点,因此系统之间不需要太多的交互。

高可用:当出现不能工作的节点时,系统仍然可以继续运行,无须停机,因为路由算法不会给不能工作的节点分配请求。

可伸缩:可以随着业务的增长,增加服务节点,也可以随着业务的缩减,减少服务节点,二者都十分容易。

高性能:因为都是在单机内完成,不需要做外部调用,因此可以得到很高的性能。

以上就是水平切分的优势,但是这样的分法,也有很大的弊端。随着业务的发展,业务会从简单变复杂。例如,一个电商的网站,用户和业务不断膨胀,所需的产品、卖家、交易和评论业务也会日趋复杂。如果此时还将所有的业务全部集中在一套系统里开发,那么显然所有业务都会耦合到一套系统里,日后的扩展和维护会越来越困难。一方面,我们会不断地通过打包来升级系统,使得系统的稳定性和可靠性不断下降;另一方面,维护起来也不方便,例如,要升级产品业务,就需要对全部节点进行升级,而这样的升级会比较麻烦。

2.垂直切分

如上所述,随着业务的增加和深入,以及用户数的膨胀,有时候,单一业务也会随之变得异常复杂,有必要按照业务的维度进行拆分,将各个业务独立出来,单独开发和维护。假设,我们将用户、交易和产品系统拆分出来,独立开发,就可以得到图1-4所示的架构。

图1-4 垂直切分

从图1-4中可以看出,我们把系统按照业务维度进行了切分,这样每一个系统就都能独立开发和维护了。垂直切分有以下好处。

提高业务独立性:只要根据业务把系统划分成高内聚、低耦合的模块,就能极大地降低开发难度。

提高灵活性:任何一个业务发生改变,都只需要维护相关的系统,而无须将全部系统打包上线。

提高可维护性:独立的系统更容易发现问题。因为将业务分离出去后,发生的异常情况更容易被定位了,所以开发者和业务人员维护起来更方便了。

虽然这样的划分带来了以上诸多好处,但其存在的弊端也是值得我们重视的。

增加了系统之间的协作:系统之间往往需要协作完成任务,也就是说,系统之间是相互依赖的。例如,购买一件商品,需要买家系统提供买家信息,产品系统扣减产品库存,交易系统记录商品交易,这需要3个系统共同协作来完成。从这个角度来说,系统之间必须进行协作。现今流行的系统交互有远程过程调用(RPC)、面向服务的架构(SOA)、REST风格请求和消息机制等。

降低了可用性:因为系统之间存在依赖,所以任何一个系统出现问题,都会影响其他系统。例如,产品系统不可用,就无法扣减库存,也就无法进行购买产品的交易了。由此可见,可用性大大降低。

数据一致性难以保证:因为节点之间需要通信,而网络通信往往并不可靠,所以节点之间数据的一致性难以保证。只能通过某些方法尽量减少不一致性。

3.混合切分

上文我们讨论了水平切分和垂直切分,也讨论了它们的利弊。本节的混合切分是将水平切分和垂直切分结合起来的一种切分方法。现今微服务架构大部分采用了这种分法,因此这也是本章的重点内容之一。先看一下图1-5。

图1-5 混合切分

在图1-5中,先是把系统按照业务维度切分到不同的服务器群里。然后,又对其中一种业务系统进行了水平切分,使得这种业务系统可以在多个节点中运行。最后,系统之间采用了交互机制进行协作。

通过其中的垂直切分,我们能够将业务分隔为独立的系统。这样就不会形成大耦合,有利于灵活的管理和简化后续的开发。而对每一个独立的业务系统又采用了水平切分,即使某个节点因为某种原因不可用,也有其他业务系统节点可以代替它。这样系统就可以变为高可用的了。这样的划分依旧不能克服系统之间大量交互和难以维护的数据一致性的问题,同时,切分节点比较多也会使实施分布式系统的硬件成本提高。实际上,无论何种划分,都不可能使得分布式系统只有优点,没有缺点。相对于耦合性和缺乏灵活性来说,大量交互和数据一致性的问题则更容易处理,因此混合划分渐渐成为主流划分方式。

前面我们简单地对分布式系统的架构进行了分析。实际上,对系统进行切分后会造成很多问题。例如,会让许多初学者陷入误区,以为只要按上述架构进行设计就可以了。然而,在非单机节点的情况下,分布式系统只能通过网络来完成协作,它存在许多不确定性。

早在1994年,Peter Deutsch就提出了分布式计算的七大谬论,后来被James Gosling(Java之父)等人完善为八大谬论。

●网络是可靠的。(The network is reliable.)

●网络是没有延迟的。(Latency is zero.)

●带宽是无限的。(Bandwidth is infinite.)

●网络是安全的。(The network is secure.)

●网络拓扑不会改变。(Topology doesn't change.)

●肯定至少有一个(在值班的)管理员。(There is one administrator.)

●传输开销为零。(Transport cost is zero.)

●网络是同质的。(The network is homogeneous.)

从这八大谬论可以看出,网络的不可靠性(如丢包、延时等)、拓扑结构的差异和传输速率大小等因素对分布式系统都存在很多的限制,我们再归结为下面3点。

异构的机器与网络:在分布式系统中,机器的配置、架构、性能、系统等都是不一样的。在不同的网络之间,通信带宽、延时、丢包率也是不一样的。那么在多机的分布式系统中,如何才能让所有的机器齐头并进,为同一个业务目标服务,这是一个相当复杂的问题。

普遍的节点故障:在分布式系统中,存在很多机器因为某些原因(如断电、磁盘损坏等)不能继续工作。分布式系统怎么去发现它们,并且自动将它们剔除出去,将请求分配到能够正常工作的节点,以保证系统能够持续提供服务,这也是需要面对的问题。

不可靠的网络和机器:多机器之间的交互是通过网络进行的,而网络传输必然发生分隔、延时、乱序、丢包等问题。机器也会因为请求量的增加而降低处理能力。例如,我们回到图1-5中,买家购买的商品会从产品系统中的库存扣减,但此时因为丢包导致交易系统没有记录此次交易,这样就会出现扣减了商品库存而交易却没有被记录的严重错误。又如,产品页面被用户过多地打开,导致产品机器都在满负荷工作,此时买家在购买商品时,产品机器会响应缓慢,甚至导致购买请求超时。这些都会影响买家对网站的忠诚度。

此处可以简单地总结为:因为网络和机器的众多不确定性,注定了分布式的难点在于,如何让多个节点之间保持一致性,服务于企业实际业务。因为数据一致性是分布式的核心问题之一,所以下面举两个执行交易的实例进行说明,如表1-1和表1-2所示。

表1-1 不一致数据情况(一)

表1-2 不一致数据情况(二)

注意,这里并不是讨论线程并发的问题,因为这里的产品和交易是在不同的机器节点上运行的。表1-1中,当时刻为T5时,尽管业务1做了减库存,但是此时业务2并不知晓业务1减库存的情况,它依旧认为产品库存为6,认为可以继续扣减业务请求的5个产品,但实际情况已经不是它认为的那样了。这是一种情况,那么这里再举另外一种情况,如表1-2所示。

表1-2看似是数据库事务回滚的简单问题,实际上并不是。正如之前论述的那样,产品和交易是两个不同的机器节点。换句话说,T3时刻是产品节点提供操作,而到了T4时刻则是交易节点提供操作。因为它们不是在同一个事务内进行操作的,所以不能通过一个简单的事务进行处理。因此,需要通过分布式事务或者其他方式提供保证。

正因为分布式存在数据或者业务操作大量的不一致性,因此需要协议或者相应手段来保证其数据的一致性。但引入过多的协议来保证数据一致性,会使系统性能大幅下降,影响用户体验。因此,除了考虑数据的一致性问题,还需要考虑系统的性能问题,这是分布式系统的难点问题,也是核心问题之一。

既然分布式存在那么多的问题需要解决,那么应该如何衡量一个分布式系统的标准呢?

在Andrew S. Tanenbaum创作的《分布式系统:原理与范例》中,指出了以下几点。

透明性:所谓透明性,就是指一个分布式系统对外来说如同一个单机系统,使用者不需要知道其内部的实现,只需要知道其参数、功能和返回结果即可。

可伸缩性:当分布式系统的全部现有节点都无法满足业务膨胀的需求时,可以根据需要加入新的节点来应对业务数据的增加。当业务缩减时,又可以根据需要减少节点来达到节省资源的效果。

可用性:一般来说,分布式系统可全天候不间断地提供服务,即使在出现故障的情况下,也尽可能对外提供服务。因而,可以通过正常服务时间和不可用时间的比值来衡量其可用性。

可靠性:可靠性,主要是针对数据来说的,数据要计算正确且不丢失地存储。

高性能:因为有多个节点分摊请求,所以能更快地处理请求。再加上每一个节点都可以高性能地处理请求,所以分布式系统的性能比单机性能高得多。

一致性:因为分布式系统采用了多个节点,所以在一个业务处理中,需要多台机器协作处理数据。然而,网络延迟、丢包、不稳定性或者协作时序错乱,会造成数据的不一致或者丢失。对于一些重要的数据,如账户金额和产品库存等参数,是不允许发生错误和丢失的,所以如何保证数据的一致性和防止丢失是分布式系统的一个重要的衡量标准。

鉴于分布式系统的复杂性,一些专家和学者提出了不同的理论,其中最著名、最有影响力的当属CAP原则和BASE理论。

分布式系统有许多优点和缺点,其主要特点是一致性、可用性和分区容忍。它们的具体含义如下。

一致性(consistency):保持所有节点在同一个时刻具有相同的、逻辑一致的数据。

可用性(availability):保证每个请求不管成功还是失败都有响应。

分区容忍性(partition tolerance):系统中任何的信息丢失或者失败都不会影响系统的继续运作。

针对这3个特点,Eric Brewer教授在2000年提出了CAP原则,也称为CAP定理。该原则指出,任何分布式系统都不能同时满足3个特点,如图1-6所示。

图1-6 CAP原则

根据CAP原则,从图1-6中可以看出分布式系统只能满足3种情况。

CA:满足一致性和可用性的系统。在可扩展性上难有建树。

CP:满足一致性和分区容忍性的系统。通常性能不是特别高。

AP:满足可用性和分区容忍性的系统。通常对一致性要求低一些,但性能会比较高。

也就是说,任何的分布式系统都只能较好地完成其中的两个指标,无法完成3个指标。

在当今互联网中,保持可用性往往是第一位的,其次是性能。因为从客户的感知来说,可用和快速响应能够提供更好的体验。一致性可以通过其他手段来保证,本书后面会给出具体的方法。

微服务主要追求可用性和分区容忍性(AP),轻一致性(C)。

在现实的业务中,金额和商品的库存数据是企业生产的核心数据,在分布式系统中保证这些数据的一致性,是分布式系统的核心任务之一。在不同的线程和机器之间保持数据的一致性是十分困难的,需要使用很多协议才能保证。在保证一致性的同时,也会给系统带来复杂性和性能的丢失。在BASE理论中,一致性又分为强一致性和弱一致性。需要注意的是,CAP原则中的一致性是指强一致性。这里,我们先来了解什么是强一致性和弱一致性。

强一致性:当用户完成数据更新操作之后,任何后续线程或者其他节点都能访问到最新值。这样的设计是最友好的,即用户上一次的操作,下一次都能读到。但根据CAP原则,这种实现需要对性能做出较大的牺牲。

弱一致性:当用户完成数据更新操作之后,并不能保证后续线程或者其他节点马上访问到最新值。它只能通过某种方法来保证最后的一致性。

BASE理论是eBay的架构师Dan Pritchett在ACM上发表的文章中正式提出来的,是对大型分布式系统的实践总结。

BASE理论的核心思想是:即使分布式系统无法做到强一致性,也可以采用适当的方法达到最终一致性。

BASE并非一个英文单词,而是几个英文单词的简写。

BA(Basically Available,基本可用):在分布式系统中,最重要的需求是保证基本可用,有响应结果返回。例如,在“双十一抢购”的苛刻环境下,用户到电商处进行抢购。即使抢购失败,系统也会提示“系统繁忙,请过会儿再来”。我们分析一下这样的场景。用户是来购买商品的,而在抢购的环境下可能因为资源瓶颈,无法完成。为了避免用户长时间等待,系统会提示用户过会儿再来。这里的提示信息不需要消耗太多的系统资源,因而这样的场景就是典型的降级服务。虽然没有完成客户需要的抢购,但是却给了用户明确的信息,避免了用户长时间等待的情况,这样会给用户带来良好的体验。

S(Soft State,软状态):其意义在于允许系统存在中间状态。一般来说,系统之间的数据通信都会存有副本,而这些副本都会存在一定的延迟。这时推荐使用弱一致性代替强一致性。这样的好处在于,提高系统的可用和性能。在网站用户的体验中,快速显示结果往往比一致性更为重要,因为没人愿意使用一个几十秒都不能响应的网站。

E(Eventual Consistency,最终一致性):是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,以保证数据的正确性。

BASE理论的应用场景是大型分布式系统,它的核心内容是放弃强一致性,保证系统的可用性。因为分布式系统自身的融合和扩展就相当复杂,如果需要保证强一致性就需要额外引入许多复杂的协议,这会导致技术的复杂化,同时对性能也有影响。BASE理论则建议让数据在一段时间内不一致,从而降低技术实现的复杂性,并提高系统的性能,最后再通过某种手段使得数据达成最终一致即可。

因为分布式非常复杂,所以一直以来都没有权威的架构和设计,更多的只是前人的积累和实践。前人总结出了许多有用的理念,积累了许多经验,开发了很多实施分布式的软件。近几年来,最热门的分布式架构非微服务架构莫属。它是由美国科学家Eric Brewer在其博客上发表的概念。微服务是当前分布式开发的热点内容,也是本书的核心内容。下面先来了解什么是微服务架构。

在讲解微服务架构前,我们需要先了解单体系统的概念和弊端。

一个单体应用,一般分为用户接口(包含移动端和网页端)、服务端应用(类似Java、PHP等动态网站服务器)和数据源(数据库和NoSQL等)。因为它是一个整体,所以如果当中某个业务模块发生了变化或者出现了bug,就需要整体重新构建和部署。随着时间的累积,各个业务模块很难保持很好的模块化结构,很容易有一个业务模块影响别的业务模块的情况。从可扩展的角度来说,扩展任何业务模块都需要扩展整个单体服务,而不能部分扩展。从部署和维护的角度来说,任何的扩展和修正都需要重新升级所有的服务,做不到部署和维护单个模块。从业务的角度来说,随着业务的复杂化,系统模块之间的耦合也会日趋严重。

事实上,微服务架构只是将一个单体应用程序拆分为多个相对独立的服务,每一个服务拥有独立的进程和数据,每一个服务都是以轻量级的通信机制进行交互的,一般为HTTP API(现今最流行的是REST风格)。一般来说,这些服务都是围绕着业务模块来建设的,是独立的产品,因此完全可以独立地自动化部署和维护,这样更加有利于我们进行更小粒度的开发、维护和部署。这些服务可以由不同的语言编写,采用不同的数据存储,最低限度地集中管理。

微服务是一个模糊的概念,而不是一个标准,没有明确的定义。但微服务存在一定的风格,只要系统架构满足一定的风格,就可以被称为微服务架构。接下来,我们来了解一下微服务的风格。

为了更好地实现微服务的风格,Eric Brewer提出了微服务架构的九个风格。也就是说,对于满足以下九种风格的系统架构,我们都可以称之为微服务。

1.组件化和服务

这里,首先明确定义组件化(componentization)和服务(service)的含义。把一个单体系统拆分为一个个可以单独维护和升级的软件单元,每一个单元就称为组件。每一个组件能够运行在自己独立的进程里,可以调用自己内部的函数(或方法)来完成自身独立的业务功能。但是更多的时候组件之间需要相互协作才能完成业务,这些就需要通过服务来完成了。这里的服务是指进程外的组件,它允许我们调用其他的组件,服务一般会以明确的通信机制提供,如HTTP协议、Web Service或者远程过程调用(RPC)等。

这样的组件化和服务有助于简化系统的开发,我们可以单独维护和升级。其次,在开发人员明确了组件的含义之后,只需要开发自己的组件,无须处理其他人的组件。在他人的组件需要调用我们开发的组件功能时,我们只需要提供编写服务即可。服务只需要明确以什么协议(如HTTP协议)和规范进行提供即可,这样各个组件之间的交互就相对简单和明确了。

这显然带来了开发和维护的便利,但是也会引来其他的问题。首先,如何将一个单体系统拆分为各个组件,这是一个边界界定的问题。其次,在使用通信机制进行交互的情况下,性能远没有在单机内存的进程中运行高。

2.围绕业务功能组织团队

上文谈过单体应用包含用户界面、服务逻辑和数据源等内容。如果对团队进行划分,可以分为前端团队、后端团队、数据库团队和运维团队等。如果以这样的团队划分作为微服务的划分,会出现比较大的问题。因为一个改动往往会同时牵涉到前端、后端和运维团队,所以即使是很小的业务改动,也会牵涉跨团队的协作。而跨团队的协作必然会引发沟通成本,严重时甚至会出现内耗,这会极大地增加系统的维护成本。为了避免这个问题,微服务架构建议按业务模块来划分团队。这样,每次修改系统的工作,就只需要在相关的业务团队之间进行了,不需要牵涉全局。如此,牵涉的团队最少,也减少了不必要的沟通和内耗。

3.是产品而不是项目

传统的软件开发组织一开始会按业务模块进行划分,然后进行开发。一旦开发完成,将软件交付给维护部门,开发团队就解散了。而微服务则认为,这样的模式是不可取的,并且认为开发团队应该维护整个产品的生命周期,也就是谁开发谁负责后续的改进。因为微服务是帮助用户持续处理业务功能的,所以开发者持续关注软件,不断地改善软件,让软件更好地服务于业务,而且越小的粒度也越容易促进用户和服务供应商之间的关系。

4.强化终端及弱化通道

微服务的应用致力松耦合和高内聚,也就是业务模块的划分具有高内聚的特点,而各个业务组件则呈现出松耦合的特点。但是系统拆分后,需要各个组件相互协助才能完成业务,因此组件之间需要相互通信,为此开发者需要引入各种各样的通信协议。通信协议分很多种,如HTTP、Web Service和RPC等。在微服务的构建中,建议弱化通信协议的复杂性,因此推荐使用以下两种。

●包含资源API的HTTP的请求-响应和轻量级消息通信协议,尤其是现在流行的REST风格。

●用轻量级消息总线来发布消息,如RabbitMQ或者ZeroMQ等,可以提供可靠消息的中间件。

在一些非常强调性能的网站,也许还会使用二进制来传递协议,但是这仍然不能解决分布式的丢包和请求丢失等问题。微服务推荐使用的两种方式,虽然在性能和可靠性上比不上其他的一些协议,但是在可读性上却大大提高了。也许绝大部分的系统并不需要在两者之间做出选择,因为能获得可读性的便利就已经很不错了。毕竟引入那些性能高或者可靠的协议会大大降低可读性,并且在很大程度上会提高系统的开发和日后维护的难度。

5.分散治理

和单体系统构建不一样,微服务架构允许我们分散治理。微服务架构的每一个组件所面对的业务焦点都是不一样的,因此在选型上有很大的差异。例如,C++适合做那些实时高效的组件,Node.js适合做报表组件,而Matlab则适合做数字图像分析。不同的业务组件也许需要不同的语言进行开发,而微服务架构允许我们使用各类语言构建组件,各组件之间只需要约定好服务接口即可。微服务架构没有编程语言的限制,不同的业务组件可以根据自己的需要来选择构建平台。

分散治理带来了很大的灵活性。与此同时,我们只需要通过接口约定即可实现组件之间的相互通信。例如,使用现在流行的HTTP请求的REST风格,就能够使系统之间十分简单地交互。

6.分散数据管理

单体系统拆分后,微服务架构建议使用分散的数据管理,也就是每一个组件都应该拥有自己的数据源,包括数据库和NoSQL等。这样,我们就可以按照微服务组件划分的规则,划分对应的数据。这有助于更为精确地管理数据,可以使数据存储更加合理,同时还可以简化数据模型。

但是,分散数据管理也会引发两个弊端。

第一个弊端是,因为数据库的拆分会导致原有的ACID特性不复存在,所以需要实现分布式数据库事务的一致性。为了实现它,还需要引入其他协议,如XA协议等。然而,这会使开发变得十分复杂,大大提高开发难度。所以微服务并不建议使用分布式事务来处理数据的一致性,而是建议使用最终一致性的原理。在第15章中,我们会再谈到这些问题。

第二个弊端是,拆分之后关联计算会十分复杂。例如,交易组件要查看产品详情的时候,而产品详情却放在产品组件里,如果是在统计分析的情况下,则无法进行数据库的表关联计算,需要大量的远程过程调用才行,这样会造成性能低下,但是从现实来说在分布式系统中使用统计分析的场景较少,所以这样的场景出现频率较低。需要统计分析时,可以抽取数据到对应的系统再进行统计分析,毕竟统计分析一般不需要实时数据。

7.基础设施自动化

因为微服务是将一个单体系统拆分为多个组件,所以势必造成多个组件的测试和部署,这样就会大大增加测试人员和开发人员的工作量。在业务不断扩大的情况下,这些将会成为测试和运维人员的噩梦。好在当前的云计算、测试开发、容器(如Docker)等技术已经有了长足发展,减少了微服务的测试、构建和发布的复杂性。

正如之前所提到的,实施微服务是对每一个组件都是以产品的态度不断深化改造以满足用户需求,所以每次进行改造之时必然会涉及构建、测试和发布。对于自动化测试,当前已有许多语言可用,如Node.js、Python等语言,都可以构建测试开发,验证测试案例。这是部署之前需要做的事情,可以降低测试人员的工作量。对于部署来说,借助容器化技术(如Docker)进行构建、部署微服务,可以极大地简化部署人员重复的操作和多环境的配置。

8.容错性设计

使用服务作为组件的一个结果,在于应用需要有能容忍服务的故障的设计。一般来说会出现两种情况。

第一种情况是,任何服务器都可能出现故障、断电和宕机。在这样的情况下,微服务架构应当可以给出仪表盘,监控每一个节点的状态是否正常、吞吐情况、内存等。一旦出现故障不可用时,微服务系统自动就会切断转发给它的请求,给出故障节点的提示,并且将被切断的请求转发给其他可用节点。微服务系统也允许监测组件节点的状态(上线、下线或不可用),在某些组件节点出现故障、断点和宕机时,系统允许组件节点优雅下线进行维护。在企业维护成功后,允许其重新上线,再次提供服务。

第二种情况是,当系统接收大量请求时,可能出现某个组件响应变得缓慢的情况。此时,如果其他的组件再调用该组件,就需要等待大量的时间。这样,其他的组件也会因为等待超时而引发自身组件不可用,继而出现服务器雪崩的场景。当一个组件变得响应缓慢,造成大量超时,如果微服务能够发现它,并且通过一些手段将其隔离出去,这种情况就不会蔓延到调用者了。这就好比电流突然增大,可能会发生危险,保险丝便自动熔断保护用电安全一样。因此,我们把这种情况称为断路,把微服务中处理这种情况的组件称为断路器(Circuit Breaker)。

9.设计改进

从上述的特征来看,实施微服务比实施一个单体系统复杂得多,代价也大得多。从实践的角度来说,微服务的设计是循序渐进的,在起初业务量不大的时候,系统是相对简单的,业务也是相对单一的。早期的核心架构在后期不会发生很大的变化,但系统会引入新的业务,使得一些内容发生变化,有些组件会被停用,有些组件会被加入进来。例如,用户数量不断增大且构成变得更复杂,这个时候可以把现有的用户服务拆分为高级用户服务和普通用户服务两个微服务产品,对外提供服务。经过时间的推移,那些核心架构的组件往往就会相对稳定下来,从而成为微服务的核心。而那些需要经常变化的组件,则需要不断地进行维护和改进,来满足业务的发展需要。

应该说,微服务是分布式系统设计和架构的理念之一。但是从微服务的风格来看,它并不是为了克服所有的分布式系统的缺陷而设计的,而是为了追求更高的可读性、可用性和简易性。但与此同时,也弱化了其一致性,正如这句老话——“两害相较取其轻者”。

所以,微服务并不能解决所有的分布式系统的问题,它只是寻求一个平衡点,让架构师能够更为简单、容易地构建分布式系统。但微服务并非金科玉律,对于一些特殊的分布式需求,还需要我们使用其他的方法来得以实现,正如方法是死的,而人是活的,需要实事求是地解决问题。

如上所述,实现微服务需要大量的软件,而这些软件是十分复杂的。应该说,大部分的企业,包括一些大企业,都无力支持这些软件的开发。但是我们并不沮丧,因为我们可以“站在巨人的肩膀上”,无论是国内还是国外,都为分布式系统做了大量的尝试,积累了丰富的成果。例如,下面的工具是我们常常在构建分布式系统中见到的。

●服务治理:阿里巴巴的Dubbo、Netflix的Eureka、Apache的Consul等。

●分布式配置管理:阿里巴巴的Diamond、百度的Disconf、Netflix的Archaius等。

●API网关:俄罗斯程序员Igor Sysoev开发的Nginx、Netflix的Zuul、Spring Cloud的Gateway等。

目前,国内最流行的是阿里巴巴的Dubbo,它已经在很多互联网企业广泛使用。但无论如何,这些软件都是某些公司为了解决各自某些问题而开发出来并将其开源的。严格来说,它们并不是一套完整的解决方案。而在国外,Spring Cloud大行其道。Spring Cloud是由Pivotal团队开发的,它没有重复造轮子,而是通过考察各家开源的分布式服务框架,把经得起考验的技术整合起来,形成了现在的Spring Cloud的组件。Spring Cloud就是通过这种方式构建了一个较为完整的企业级实施微服务的方案。更令人振奋的是,Pivotal团队将这些分布式框架通过Spring Boot进行了封装,屏蔽了那些晦涩难懂的细节,给开发者提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。在引入国内之后,Spring Cloud渐渐成了构建微服务系统的主要方案,成为市场的主流。当然,这也是本书需要深入讨论的核心内容之一。

通过上述介绍,大家可以知道,Spring Boot是构建Spring Cloud微服务的基石,所以它是阅读本书的基础。但本书只是简单介绍Spring Boot的知识,如果读者想要进一步深入,还需自行学习。

为了构建微服务架构,Spring Cloud容纳了很多分布式开发的组件。

Spring Cloud Config:配置管理,允许被集中化放到远程服务器中。目前支持本地存储、Git和SVN等。

Spring Cloud Bus:分布式事件、消息总线、用于集群(如配置发生变化)中传播事件状态,可以与Spring Cloud Config联合实现热部署。

Netflix Eureka:服务治理中心,它提供微服务的治理,包括微服务的注册和发现,是Spring Cloud的核心组件。

Netflix Hystrix:断路器,在某个组件因为某些原因无法响应或者响应超时之际进行熔断,以避免其他微服务调用该组件造成大量线程积压。它提供了更为强大的容错能力。

Netflix Zuul:API网关,它可以拦截Spring Cloud的请求,提供动态路由功能。它还可以限流,保护微服务持续可用,还可以通过过滤器提供验证安全。

Spring Cloud Security:它是基于Spring Security的,可以给微服务提供安全控制。

Spring Cloud Sleuth:它是一个日志收集工具包,可以提供分布式追踪的功能。它封装了Dapper和log-based追踪以及Zipkin和HTrace操作。

Spring Cloud Stream:分布式数据流操作,它封装了关于Redis、RabbitMQ、Kafka等数据流的开发工具。

Netflix Ribbon:提供客户端的负载均衡。它提供了多种负载均衡的方案,我们可以根据自己的需要选择某种方案。它还可以配合服务发现和断路器使用。

Netflix Turbine:Turbine是聚合服务器发送事件流数据的工具,用来监控集群下Hystrix的metrics情况。

OpenFeign:它是一个声明式的调用方案,可以屏蔽REST风格的代码调用,而采用接口声明方式调用,这样就可以有效减少不必要的代码,进而提高代码的可读性。

Spring Cloud Task:微服务的任务计划管理和任务调度方案。

●……

通过上述组件描述可以相对容易地构建微服务系统。只是本书不会介绍所有的组件,而是根据需要介绍最常用的组件,这些将是后续章节的重点内容。当前,Spring Cloud以Netflix公司的各套开源组件作为主要组件,通过Spring Boot的封装,给开发者提供了简单易用的组件。但由于Netflix的断路器Hystrix已经宣布进入维护阶段,不再开发新的功能,因此,Spring Cloud即将把Resilience4j作为新的熔断器加入进来。本书会对Resilience4j进行详细的讲解,以适应未来的需要。Spring Cloud的未来趋势是去Netflix组件,因为需要大幅度地更新组件,所以周期较长。但是,即使更替新的组件,其设计思想也是大同小异的,正如这句老话——“换汤不换药”,所以我们还是会讲解Netflix组件。

因为Spring Cloud融入了大量的其他企业的开源组件,所以这些组件的版本往往并不一致,不同的组件由不同的公司进行维护。为了统一版本号,Pivotal团队决定使用伦敦地铁站点名称作为版本名。首先是将这些站点名称进行罗列,然后按顺序使用。Spring Cloud发布的版本历史(截至本书编写时)如表1-3所示。

表1-3 Spring Cloud版本更替史

在编写本书时,版本已更替到了Greenwich.SR2,其中,Greenwich是伦敦的一个地铁站名,SR2代表的意思是“Service Releases 2”,这里的2代表的是第2个Release版本,它代表着Pivotal团队在发布Greenwich版本后,进行修正的第二个版本。

由于Spring Boot已经发展到了2.1.x版本,Spring Cloud也发布到了Greenwich版本,因此本书是基于Spring Boot 2.1.0和Greenwich.RELEASE进行讲解的。

为了更好地讲述Spring Cloud微服务和分布式系统的知识,这里我们来模拟微服务系统。当今互联网的世界中,互联网金融是一个很大的课题,所以这里采用互联网金融的例子来讲解微服务系统和分布式应用的知识。

假设,有一家互联网金融公司主营互联网金融借贷业务。它先收集借款人信息,再根据借款人的资金需要生成理财产品。然后,通过理财产品约定利息、时间、还款方式等内容后,发送到公司的互联网平台。最终用户就可以在该公司互联网平台上看到这些理财产品了。那些拥有闲置资金的用户就可以购买这些理财产品,从而获得较高的利息收益。其业务如图1-7所示。

图1-7 互联网金融平台

为了使业务能够进行,这里先做业务分析,此为开发系统的第一步。

这里需要管理和审核借款人,公司信贷审核人员将审核借款人的身份、信用、资质和财产等情况,以保证不发生金融诈骗,因此我们需要一个借款人微服务。

而投资人是平台的用户,因为大额投资和经常投资的用户应该要被给予更多的优惠,所以投资人也会根据具体的情况分成不同的等级。为了更好地管理,需要一个用户微服务。

平台会根据借款人的资金需要来生成对应的理财产品,理财产品分为定期和活期。用户购买产品的交易记录也会记录在内。这里需要一个理财产品微服务。

因为涉及金钱,所以这里需要一个资金微服务,帮助投资人和借款人管理自己资金。投资人可以将自己银行卡上的闲置资金转入系统来购买理财产品,而平台也会根据借款人的资金需要将资金转到借款人账户。

平台也许还会和第三方合作,让第三方介绍投资人或者借款人,或者进行广告等,因此还需要一个第三方微服务……

不过也许并不需要考虑那么多的微服务,因为大部分情况是类似的,而且全部考虑也会太复杂。因此,本书只讨论用户(投资人)、理财产品和资金微服务,如图1-8所示。

图1-8 本书互联网金融平台粗略架构

从图1-8中可以看到,请求会先到网关,网关会拦截请求,进行验证、路由和过滤。这样做可以保护微服务,避免一些恶意的攻击,同时还可以限制通过的流量,避免过大的请求量压垮系统。各个微服务则提供实际的业务功能,对于微服务之间需要交互才能共同完成相关的业务,按照微服务的建议进行集成,这里采用REST风格的请求进行集成。