谈谈我对微服务的理解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/disdouble/article/details/83271917

微服务是一个近些年说的非常热的概念,尤其在互联网的大背景下,微服务的理论有机会被广泛实践。但是在实践过程中,大家对微服务的理解确大相径庭,到底要怎样做才能真正掌握微服务的架构理论呢?通过此文笔者想和大家分享一下对微服务架构的认识和理解。

什么是微服务

微服务的出现给我们的系统带来了很多好处,比如子系统与子系统之间的技术异构,单个系统的弹性扩容等。那怎样的服务才算是微服务呢?我们先看看它的定义:微服务是一种分布式系统解决方案,推动细粒度服务的使用,每个服务都可以独立运行, 且这些服务可以协同工作。微服务的关键就是“微”,而怎样才算微,笔者的理解就是小而独立。
先说说“小”,小就是简单,简单到通过名字或者定义就可以知道它大概能干什么事了,而且只干这一件事。
再说“独立”,笔者在遇到过的一些项目中,见过有的人为了能搭建一套基于微服务的系统,就把本属于一个独立的系统拆分成多个,这样看起来好像更符合“微”的定义,但其实不然,微服务定义中的任何一个系统都应该可以独立部署、独立运行,并能独立完成一个业务闭环。如果你的系统在启动之后,并不能对外提供什么实质的功能,而是要完全依赖其它的服务,那这个系统就不算一个独立的系统。
微服务的概念我们了解了,但是它又和同样是最近几年说的比较多的SOA架构有什么区别和联系呢。首先微服务架构和SOA架构都是一种基于分布式系统的而且面向服务的架构风格,这里请注意,是“风格”,不是框架。这些概念的提出只是给我们定义了它的规范和原则,并没有给我们输出可以直接落地的技术方案。那它们有什么区别吗,如果只从定义上好像很难说的清。但是从它们实践的过程和方式上来看,还是有很大区别的。下面我从几个主要方面说一下.

  • 领域驱动设计(DDD):

    笔者认为这应该是最重要的一点,微服务的提出最根本原因是解决越来越复杂的系统设计与开发的问题。当系统过于复杂时,它所面临的问题难度要比只有几个简单功能的系统面临问题难度大得多。而原因就在于系统内部已经有了太多的耦合,系统的每一次迭代都可能是致命的,而DDD理论的出现就是为了解决这样的问题。微服务把它作为了业务划分的重要指导理论依据。

  • 去中心化:

    SOA在提出时有一个重要的角色ESB(企业服务总线),它的根本是协调各系统进行功能输出,所以天生就是一个带有中心角色的架构,而微服务是去中心化的,它认为任何单独的服务都可以独立自治,不受其它系统的制约和调配。

  • 独立数据库:

    服务要保持独立自治性,就应该完全使用自己的数据库,而不是和别的系统共用。当自己的业务模型发生修改时,只修改自己内部的数据结构就可以了,而且也不用关心会对其它服务产生影响,而SOA中并没有对这个原则有明确的要求。

微服务设计

微服务架构的核心目标是把复杂问题简单化,通过服务划分,把一个完整的系统拆分成多个高内聚、低耦合的小的子系统。使每个子系统可以独立的运行、升级和测试。然后再通过一些集成手段将这些子系统组合在一起,对外提供完整功能的过程。所以我们对微服务设计的过程就是对系统如何做拆分和集成的过程。
拆分
对微服务的拆分,从根本上是对业务的拆分,我们可以从以下几个方面来考虑。

  • 限界上下文

    限界上下文是在领域驱动设计的方法论中提出的,它认为每个给定的领域都可以包含多个限界上下文,而每个上下文可以分成内部和外部两部分,内部部分是系统内的,不会受其它子域模型的变化而变化,同样自己内部模型的变化也不会影响到其它子域。而外部部分需要参与和其它上下文的通讯。
    每个上下文都有明确的接口,该接口的定义决定了它会暴露哪些功能给其它上下文。所以我们在定义限界上下文时就是在划分子域边界,而且每个边界内的职责一定是单一的。同时我们在定义上下文的名字的时候一定要在自然语言上保持独立,这个被叫做正交原则。它是为了告诉我们,每个服务子域的定义要清晰独立,不能产生歧义,也不要和其它上下文的名字产生混淆。
    限界上下文的划分面向的是业务领域,而不是技术领域,有的人喜欢通过技术分层的方法把系统进行拆分,最后美其名曰采用的是微服务架构思想。我经历过一次糟糕的项目经历,在本应该是一个独立服务的系统中,按照技术框架将服务分成了上下两层(基于web应用的前端业务层和基于RPC协议的后端业务层),最后整个项目一点没有因为这样的分层而达到解耦的目的,反而是增加了开发和测试的实施难度。限界上下文的划分同样也不是面向数据模型的,之前说过,微服务和其它分布式服务架构最大的一个区别就是看是否采用了领域驱动设计的理论指导,这个理论的主导思想告诉我们系统模型的建立,不应该采用针对数据CRUD的操作,这被定义为“贫血”模式,它提倡的是针对领域模型驱动的“充血”模式。

  • 数据模型

找到限界上下文,我们划分出了各个独立的服务子域,下一步就可以对数据库进行拆分了,将数据库进行必要的拆分能将复杂数据结构进行分解,具体该怎么做呢?下面我通过几个例子来分析一下。
    
a找到缺失领域
笔者之前负责的一个系统中有两个对外的服务,策略分析服务和威胁预警服务,两个系统都要使用到行为特征的资源数据,而且都会在一张表里写入和读取,如图1:

图1

这样显然是不合理的,分析其根本,这里缺失了一个领域概念:特征库。我们再重新拆分一下,如图2:

图2

经过抽象新的业务子域,我们添加了一个特征库的服务,它可以通过API接口的方式向两个系统提供服务。而其自身也可以通过独立的操作界面来维护特征的定义和生命周期了。

b共享静态资源
还有一些静态字典表,我们需要在多个系统中使用,我们之前很常见的做法是将这些静态数据单独存储到一个表或者直接从别的库里拷贝一份出来,在查询的时候直接访问数据库获得字典数据,而微服务提倡的做法是把这些静态数据的查询当做一个单独的服务提供给外部,这样在数据字典维护的时候也只需要维护一份数据就可以了。

c读写分离与不分离
服务的细粒度拆分,导致每个独立的子服务变得很简单,但是也会导致很多复杂的查询业务实现起来变得困难,这时候我们可以把数据资源的维护和查询分离开,比如统计报表。甚至我们可以通过采用不同的存储引擎来提高性能。
还有一些系统,查询的逻辑并不复杂,但是受到的资源约束比较多,这样的系统我们更多建议读和写的操作都放到一起,还是以上面那个特征库系统为例,特征库服务的核心功能是给其它服务提供数据查询能力,但是该系统在存储的时候除了数据本身外还保存了很多属性做约束,比如特征的生命周期和版本(涉及到数据是否可以对外暴露),系统在做数据写入的时候同时需要对这些属性进行维护。可是对外的查询业务完全没有必要关心这些细节,只需要知道特征数据需要以什么样的格式和方式提供给其它系统就可以了,而不应该关心能不能查到。这时候,我们需要把针对特征库读和写的核心操作进行合并,分离出了一个核心数据基础服务子系统,如图3所示

图3
图3 通过把系统从左边的结构演化到右边后,对外提供查询的服务不会直接访问特征数据库,而是通过访问特征数据基础服务来获取特征数据,而这个基础服务应该由一个人或者组进行负责,毕竟只有维护数据如何写入的人才更清楚应该怎样把它查出来。
  • 康威定律

当我们通过上面提到的方法和原则进行服务拆分后,是不是就一定会得到一个可以实施的微服务架构呢?实际上可能还有其它的因素对其进行制约,开发团队的组织结构就是非常重要的一个因素。也就是说微服务的设计要适应于实施它的开发团队的组织结构。这个理论就是康威定律。从软件开发成本角度来看,团队内部成员的沟通成本应该是最低的,所以要把一个独立的系统开发工作交付给一个团队肯定比交付给多个团队更能有利于沟通成本的降低,也最有利于软件交付。限界上下文在划分的时候首先就应该考虑这一点。这是一个团队的多个人负责一个服务的情况,那么从另一个角度想,可不可以由一个人负责多个服务呢,这肯定也是不被建议的,为什么呢?当一个人在开发多个系统时,必然会在测试,部署等环节增加额外的工作量,远没有在一个进程内对系统进行开发方便。如果确实可以拆分出更细粒度的子系统来,建议先通过工程进行拆分,而不是服务,等系统发展起来后,需要加入更多的成员来完成这一个系统时,再拆分服务。所以当一个项目由一个5人的团队开发时,最后划分出10个子服务来肯定是有问题的。

集成
整个系统被拆分成多个独立的子系统后,面临的第一个问题就是如何集成在一起,对外形成一个完整的系统。业内出现了一些成熟的开源的技术框架,比如spring-cloud,dubbo等,都是针对微服务,SOA等面向服务的架构风格的技术实现。但除了这些技术框架,我们还应该掌握哪些集成原则呢?下面我从几个方面给大家介绍一下。

  • 宽进严出

    我们设计的服务可能会作为消费方去调用别人的服务,同时也会作为服务方为别的系统提供服务,但是在一些安全或者参数的规范性校验上,我们却知之甚少,当需要和关联服务交互时,在接口设计上我们应该做到宽进严出,就是说我们对自己要给别人提供的服务严格按照接口规范去输出,尽量不给对方返回没有在接口文档中出现的错误,而对于我们依赖的服务,要做到不信任原则,及要尽量考虑对方接口返回的未知错误,做到最大限度的容错性。

  • 同步还是异步

    当面对系统之间的集成时,我们经常会犹豫到底应该采用同步方式,还是异步方式,同步带来的好处就是交互简单,调用端可以在一个线程或进程中就完成对依赖服务的请求和相应,这也是我们在服务集成时最先考虑的方式。但是由于随着系统规模的不断变大,产生的并发请求量也会急剧增加,对系统的响应延时和吞吐量的要求也愈来愈高,所以一些不需要立即获取响应结果的请求完全可以通过异步的方式来处理。
    不管是同步还是异步都有其存在的道理,我们应该对具体的业务要求进行取舍,不过近年来,一种新的调用机制逐渐被人们应用起来,这就是响应式调用,响应式的调用方式使调用的细节被屏蔽,调用方只需要简单的对调用的结果进行观察,而不用关心调用本身是阻塞的还是非阻塞的,唯一需要做的就是等待结果返回后作出响应就可以了,同时它更棒的地方在于,调用方可以将多个调用组合起来,这样很容易实现了对下游服务并发调用的需求。

  • 编排和协同

    当一个复杂的业务流程需要多个服务的配合来完成时,我们该采用怎样的方式来组合这些服务呢,前面提到过,基于SOA的服务架构是一种把各个服务通过编排的方式集成在一起的,如下图4所示

图4
当用户注册成功后,我们要调用安全中心同步用户信息,还要调用积分中心给新用户增加积分,最后调用消息中心给用户发消息。这种通过编排把各个服务组织在一起的方式导致了用户中心服务承担了太多的责任,它的业务逻辑中耦合了很多无关的业务代码,这是服务中心化的一种体现。我们应该去除它,如下图5
图5
                      

这种方式被定义为协同,它是通过消息事件的订阅和发布来减少系统与系统之间的耦合,从根本上消除了中心服务节点。所以在设计非强依赖系统间调用关系时,建议采用协同方式来代替编排。

相信大家看到这儿,对微服务的设计应该有了一个大概的了解,上面笔者给大家介绍了几个服务拆分和集成的常用方法,但这远远不够,微服务架构面向的更多是业务层面,业务与业务之间是完全不同的,而且一个业务本身也是不断变化的,所以想把某一个微服务架构的设计方法当成银弹来使用,就大错特错了。知道这些原则并不难,关键是如何运用这些原则灵活得设计出一个高内聚、低耦合的系统。

微服拆分带来的问题

微服务给我们带来的好处显而易见,我们通过一定的设计原则也一定能够设计出最适合自己业务的服务来,但是这样做会给我们系统的运行、监控、测试、部署带来什么副作用吗?
答案是肯定的,由于微服务天生是基于分布式系统架构的,所以分布式系统固有的问题它都有,比如数据的一致性、服务的可用性等。另外,由于微服务对系统划分的粒度非常细,本来很多单体的系统被部署到了多个进程中,这给测试、部署、监控等环节所带来的复杂性也是超乎想象的。那因此我们就放弃微服务了吗?当然不会,任何系统架构的设计过程,其实都是在解决系统复杂度的过程。微服务架构的复杂度虽然大,但是并不是不能解决的,下面针对几个常见的问题来分析一下在微服务架构中应该如何应对。

  • 数据的一致性

    我们在一个只拥有一个数据库的系统中处理事务是很简单的事情,很多关系型数据对ACID的实现都是非常严格的,但是到了微服务架构的环境中,保持事务的完整性似乎变的不可能,由于网络传输的不可靠性(虽然这种情况出现的概率很小),被分散在多个进程中的系统在共同完成一个写操作时就会可能出现维护自己的数据状态时不能保持一致性的问题,比如不能够同时提交本地事务,或者不能够同时回滚。对于这个问题,我们应该怎么做呢?
    首先要有重试机制,及当对一个子服务的写操作请求失败后,可以重试几次,直到系统的响应成功为止,这就要求每个有写操作的子服务需要支持幂等,因为有可能上一次已经写成功了,只是由于网络问题,没有正常返回结果。
    由于许多时候网络问题不是瞬间的,可能会持续一段时间,那么我们就一直重试下去吗?肯定也不会,我们会采用补偿机制来修复事务的一致性,比如库存系统在扣减成功后,需要在订单系统上修改客户订单状态,但是尝试多次后,订单系统一直响应失败,那怎么办呢,可以给库存系统发一个增加库存的请求,算是补偿。这样整个系统在一定时间段内还是保证了数据是一致的状态。
    最后可以使用柔性事务(TCC),柔性事务这个词最早是由支付宝的团队提出的,它的核心思想是要求在涉及写操作的业务中支持一个软状态,怎么理解这个“软状态”呢,就是不是一个确定的状态,比如正在处理中的订单。举个例子,系统在从A状态到达B状态时必须要先经过C(软状态),而C不是一个实际的业务状态,这样做不但可以起到资源检查并预占的作用,同时还可以做为回退到A还是前进到B的中间状态节点。

  • 服务的容错性

    在微服务架构中,肯定会有一个系统依赖其它系统来完成一次操作的情况,那么当依赖的下游系统宕机或者由于网络问题导致服务长时间不能响应的时候怎么办,上游系统不可能一直重试吧。这时我们需要采用熔断保护措施才能保证上游系统不被下游系统拖死,具体我们应该怎么做呢?
    超时,首先我们对任何服务的调用都应该有超时机制,当长时间没有应答时就要认为调用失败并终止等待了。
    隔离,即便有超时机制,系统也可能会因为超时时间过长,而处于“漫长”的等待中,更可怕的是当某个下游服务的整个集群全挂了的时候,上游服务不应该被这一个服务拖死。我们应该对资源进行合理分配,把调用下游接口的资源进行隔离,其实有很多时候只是一个不影响主业务流程的下游服务挂了,这不应该成为我们整个服务不能访问的原因,我们可以采用为每个对下游资源的调用分配最大个数的线程池或者连接池来解决这个问题。
    断路保护,在互联网的场景中,高并发、高吞吐的请求是非常普遍的要求,这种情况下,即便我们做了超时机制和资源隔离,也会由于大量的请求使我们的系统处于不必要的繁忙状态,我们应该对那些一直出错误的响应进行处理,把已经尝试过多次的失败请求进行累计,当达到一定值的时候,忽略对它的调用。我们之后定时去监控,看看它恢复了没有就可以了。
    服务降级,当系统出现断路的时候除了不再访问下游资源,还需要做些什么呢?总不能给用户返回一个下游服务有异常的错误,通常情况下我们会通过缓存或者静态资源来给用户返回一些非实时的数据,让用户无感知,这就是服务降级。
    当然系统的容错处理方式远不止这些,很多时候是根据实际的业务场景来处理的,只要我们一直不要忘记网络是不可靠的这个事实,对一切可能出现的异常情况进行捕获和规避,就能做到系统稳定可靠的运行

  • 系统的性能

    在微服务环境中,当用户对系统发起一个请求后,最上游的服务要经过多次网络传输、多次请求/应答数据解析才能完成一个整体的请求交互,所以必将带来更多的资源开销和更大的访问延时,我们应该如何应对呢?
    首先,可以考虑使用RPC进行远程通讯,虽然目前基于restful的http请求很热,它所带来的简单性和规范性是一般RPC框架不具备的,但是不得不承认,基于二进制传输协议的RPC通讯框架在性能上的优势是http协议很难达到的。所以在一些对性能要求比较苛刻的系统中,优先考虑RPC。
    其次,增加缓存使用,缓存虽然不是分布式系统架构中的银弹,但是它无处不在,它的好处在这里我们就不深入讨论了。微服务系统性能瓶颈归根到底还是多次网络请求,解决它最好的办法就是减少调用,我们尽量把每次对下游系统的请求进行缓存,虽然有时这确实是一厢情愿,但在设计微服务系统时,请你首先考虑使用它。
    最后,我们说说扩展,前面聊了几点其实也只是尽量在缩小和单体系统之间的差距而已,并没有从根本上解决性能瓶颈。而对于系统扩展性,是微服务系统与生俱来就有的,通过扩展我们可以减少单个服务的复杂性,使某个服务的请求所要处理的业务逻辑简单很多,最常见的情况是数据库的结构简单了,之前需要各种关联的查询没有了,因为我们每个服务只负责自己的那几张表。这有什么好处呢,首先是垂直扩展简单了,系统的关联性减弱后,我们可以很容易的对系统进行拆分,使之前许多复杂的多表查询变成了简单的单表查询,从而使sql优化这些让我们经常头疼的东西减少了,更重要的是单表查询为我们带来的一个更大的好处就是使数据库的水平扩展也变得十分容易。所以在微服务架构的环境中,我们要始终考虑如何用多个简单的业务逻辑来代替一个复杂的业务逻辑,因为这样更容易实现数据库在水平和垂直方向上的扩展。

  • 实施复杂

    当系统被拆分成多个单独的服务时,无论在部署、测试还是监控环节,都会产生大大超出单个系统的工作量,这在做集成测试的时候会尤为突出。小而多的应用服务必然需要更多的部署节点,应对这样的问题,需要我们必须接受自动化文化,自动化部署和自动化测试方案必须要在微服务实施开始前就规划好。关于自动化部署和测试的方案有很多,而且大多数都已经很成熟。由于篇幅所限,笔者就不在这里展开说了,这也不是本文的重点,希望在后面的文章中有机会跟大家分享。
    总结
    到此,我们从微服务的定义、拆分、集成、带来的问题和解决方案等几个方面全方位的介绍了一遍微服务,相信大家也会通过我的分享对微服务有了进一步的认识,最后,笔者通过一个拓扑图把微服务的设计原则做一个总结。

图6:微服务设计原则
                       

再叮嘱一下,原则不一定要遵守,但遵守了一定有好处。

展开阅读全文

没有更多推荐了,返回首页