Microservice Anti-patterns

  在最近的一次Microservices Practitioner Summit中,原Netflix工程师介绍了一种越来越常见的对Microservice的误用。简单地说,大家在搭建一个基于Microservice的服务时常常依赖同一套类库,进而使得Microservice中的各个子服务无法选择最适合的技术。

  如果您不知道Microservice是什么,请首先阅读我的另一篇文章《Microservice简介》。

  在本文中,我们就将以该演讲的内容作为引子,介绍一下当前业界对于Microservice的一系列误用方式。

Distributed Monolith

  不知道大家是否喜欢看看各公司所办的各种会议。现在不像10年前只有微软,Intel等几家公司办的TechED,IDF等会议了。越来越多的公司喜欢举办会议、论坛等活动,以吸引越来越多的优秀开发人员一起讨论最新技术和业界发展趋势。因此我先说点题外话,希望如果大家感兴趣请自己寻找一下自己觉得有趣的会议。我相信大家都是喜欢技术的人。这样一方面能提高自己的能力,另一方面也对国内的技术环境有帮助,利人利己。

  好,让我们回到原题。在该工程师的演讲中,他提出了近期较为常见的一种对Microservice的误用:很多公司或个人都认为将一个Monolith的服务拆分成一系列子服务,就能够被称为是基于Microservice的服务了。的确,这种变化引入了Microservice的一系列优点:由于各个子服务之间拥有了确切的边界,因此它们将能够更容易地独立发展,而且我们也可以更容易地对该子服务进行扩容:

  但是这种子服务组织方式并不能完全地称为Microservice:其丢掉了Microservice的技术灵活性。在Microservice中创建一个子服务的方式主要分为两种:从原有的服务上剥离,以及新建服务。

  在从原有服务上剥离时,我们常常会选择让新创建的子服务使用原有的技术,以减少创建子服务的工作量。这其实并没有太多的问题,毕竟这种剥离在原有服务的基础上提高了各子服务的独立性。虽然说此时我们所使用的技术并不是最合适的,但是的确是最有效率的创建子服务的方式。

  而新建一个Microservice子服务就不一样了。在创建一个新的子服务时,软件开发人员常常需要自行编写该子服务所包含的所有代码。因此此时我们应该尽量选择最合适的技术,以获取最高的开发效率。

  但是误用就出现在这里。在创建一个新的Microservice子服务时,软件开发人员常常偏向于使用之前所使用的各种技术。这是软件开发人员对已使用技术所保留的一种惯性:软件开发人员在之前对这些类库的使用中知晓了其可以用来完成某些功能,而并没有过多地关注实现这些功能的复杂程度。就像我在《Cassandra简介》一文中说的那样,技术选型是一个非常严谨的过程。该过程常常需要进行大量的研究,并根据开发的难易程度,用户基数,活跃程度以及成功案例来决定是否使用该技术。而软件开发人员的这种技术惯性则常常是导致项目最终失去控制的一个原因。

  在这些共有的技术越积越多的情况下,我们的Microservice服务就越来越容易引入刚刚Netflix工程师所提到的错误:在Microservice中广泛使用类库特有技术。我们知道,某些类库会提供一系列特有的功能,以用来提高执行效率,提供更高的安全性等。但是这些技术常常并不是行业规范,因此也会导致其无法与其它类库兼容。这会导致Microservice中的各个子服务之间存在着一种隐式的契约:为了有效地在各个子服务之间进行通讯,Microservice中的各个子服务需要使用特定的技术。

  如果我们在Microservice中引入这种特定的技术,那么该技术将会逐渐扩展到Microservice的所有子服务中:

  这样Microservice中的所有子服务最终将使用同一个技术集,进而限制了其它子服务所能够选择的技术。如果我们任由这种情况发展,那么到最后Microservice中的所有子服务都将绑定于一整套固定的技术集之上,并无法再选用最为合适的技术。而最终的结果就是,我们的Monolith服务的确转化为基于Microservice的服务,但是其使用的技术集还是原来的技术集。这种服务组织方式并没有提高开发效率,反而增加了在各个Microservice子服务之间相互沟通的成本,降低了整个服务对单一请求的响应速度。总体来说,得不偿失。

  而真正应该在Microservice子服务之间存在的,是明确指定的契约和协议,而不应该是如何实现这些子服务。

  其实一个决策常常是在权衡各方面优劣才做出的。Microservice的优势无非就是具有更好的横向扩展能力(Scalability),更灵活的技术选择,更简单的子服务实现逻辑,更清晰的子服务边界以及更好的子服务复用性。但是缺点也一样明显:子服务之间相互通讯所导致的单一请求执行效率降低,子服务边界所带来的代码组织灵活性降低等。如果我们丢掉了灵活的技术选择,那么更简单的子服务实现逻辑所带来的高效开发及更好的维护性就将无从说起。最终所导致的结果就是:使用Microservice组织子服务不会为我们带来额外的好处。

  这里还有一个悬而未决的问题,那就是从Monolith剥离出来的各个子服务常常使用同一个技术集。是的。在这种情况下,我们要尽量控制这些技术集的扩散,并在需要时逐渐移除对这些技术集的依赖。

功能边界不清

  我相信有些读者已经对Microservice有些研究了,甚至尝试过在服务实现中使用它。最容易遇到麻烦的一点便是对单个请求进行处理时的性能问题。

  无论是浏览器还是JS类库,都拥有一个请求超时的概念。在发送一个请求之后,如果服务长时间没有对该请求进行响应,那么浏览器或JS类库都会将该请求识别为超时。而且从用户使用的角度来讲,他也并不希望一个请求长时间没有响应。因此在一个基于Microservice的服务中,我们常常需要让整个服务能够快速有效地响应用户的请求。

  一个提升性能的方法就是合并请求。如果说用户对一个服务的使用需要固定顺序的一系列调用,那么我们就可以通过将这些调用合并到一起,从而减少反复通讯所造成的损耗:

  上图展示了这种通过合并调用提高性能的方法。在用户尝试找到特定种类下的所有商品时,我们需要通过查找所有的商品种类以确定该商品种类的ID,进而通过该商品种类的ID得到该商品种类下的各种商品。为了能够合并调用,我们需要提供一个新的API,以允许软件开发人员通过商品种类名称直接完成调用,而对商品ID的查询等执行逻辑都放在服务端去执行。这样用户就只需要发送一个请求,而不是三个请求,进而缩短了整个流程的运行时间。

  但是这样还是有问题,那就是我们所新引入的API已经将商品种类以及商品这两个概念包含在同一个子服务中了。这种包含了过多概念的子服务常常成长为一个包含了过多功能的子服务,也即是Microservice中的Monolith。

  而一个较为正确的解决方案则是:就像我在《Microservice简介》一文中已经提到过的那样,我们在必要时应该提供一个Gateway,并在Gateway中添加合并在一起的API。这样由于Gateway常常和与其关联的各个子服务处于同一个数据中心中,因此其内部通讯效率常常比用户通过浏览器访问快很多。这样我们既保证了各个子服务可以独立地发展,又能提高用户的访问效率:

  而且在该Gateway中,我们也常常可以通过缓存等一系列技术手段来提高运行效率。

  反过来啊,我们也别走到另外一个误区里面,那就是建立太多的层次。我能理解一个数据库作为一个独立的层次的必要性,毕竟数据库常常与其它服务实例处于不同的服务实例上。但是如果在纵向上添加太多的层次,那么在处理用户所发出的一个请求时就需要经过过多的消息转发,从而使得对消息的处理时间变长:

  当然,Gateway也是其中的一个层次,因此不要为每个子服务添加一个Gateway,而是要有一个整体的规划:

  总之,在如何切割子服务时我们常常需要在脑中保持是否用户的请求能被快速响应这一个问题。在Microservice中,API的粒度过细,以及内部调用过多是最具有杀伤力,也最容易出现的问题。

过于强调Microservice

  的确Microservice这个词在国外很火。很多公司都将自己的产品是基于Microservice来组织的作为一个卖点。这的确能够帮助很多销售人员解释公司产品所具有的一系列优势。但是作为一个开发人员,我们不要被这个观点所迷惑。原因就是因为,直接开发基于Microservice的服务所需要的初始开发成本较Monolith的开发成本高很多。直到产品的规模达到一定程度,Microservice的优势才能够发挥出来:

  而一个产品常常需要经由PoC才能变成真正的版本,而且在产品的初期,我们所需要解决的常常不是这个产品需要具有多大的扩展性,而是我们需要有足够的钱来支撑这个产品的持续研发。因此在产品演变的过程中,功能性需求常常在开发的初期占首要地位,而像横向扩展能力等非功能性需求在后期才会变得越来越重要。所以在开发一个全新产品时,如果我们过于注重通过Microservice来组织各个子服务,那么软件开发人员在整个项目的初始阶段的开发效率将非常低下。相反地,我们需要在开始实现时尽量注意各个组成之间的隔离。这样一旦需要从Monolith转化为Microservice,我们只需要将这些代码根据其所在的包进行剥离即可。

  当然,如果我们已经开发过按照Microservice组织的服务,而且有一系列子服务可以被重用,那么我们完全可以通过重用之前Microservice服务所使用的框架来开始这个服务的开发。此时Microservice服务的开发效率并不会低多少,甚至还可能在非常短的时间之内拥有比Monolith开发更高的效率:

  所以说,我们的第一要务并不是使用Microservice,而是通过它来为业务目标服务。将Microservice置于业务目标之前,反而常常在项目初期成为一块绊脚石。

没有使用Continuous Delivery

  因为我一直在外企,所以对Continuous Delivery平台的使用还是相对较多的。但是从国内某些厂商,甚至是较大的厂商使用Microservice搭建服务时,常常会出现不使用Continuous Delivery平台的情况出现。这样就会导致一个问题,搭建测试环境及生产环境会非常麻烦。

  Microservice的一个重要优势就是提高了开发效率。反过来,如果软件开发人员和测试人员每天需要为如何搭建一个开发及测试环境发愁,那么使用Microservcie开发又得到了什么好处呢?

  当然啊,工作上的事情有些说不清,所以咱们也不多说什么。只能说,Continuous Delivery能够提高我们的开发效率。如果您是个决策者,而且刚好看到这篇文章,那么请您一定认真地考虑这一点。其实搭建一个Jenkins环境也并不是非常困难,所需要的物理机也并不是很多。而且您还能够在灾难恢复等众多非功能性需求上获得一系列好处。

  对,最后提一句,这篇文章和InfoQ上最近的一篇文章有关:http://www.infoq.com/news/2016/03/microservices-anti-patterns。熟悉我的读者可能知道我喜欢攒文章,隔几个月再发布,因为这样可以根据一段时间的经验对自己所写的内容进行一次校验。只不过我看这篇文章同样提到了这个演讲,所以为了避免不必要的解释,就提前发了

转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/5315860.html

商业转载请事先与我联系:silverfox715@sina.com

公众号一定帮忙别标成原创,因为协调起来太麻烦了。。。

原文地址:https://www.cnblogs.com/loveis715/p/5315860.html