复杂调用网新思路
什么是调用网?当通信从一个客户端经过Gateway进入到微服务层时,微服务间互相调用,互相依赖构成一个所谓的调用链。这几个调用链交叉在一起,最后形成调用网。
复杂调用网新思路
起初,许多团队可能采用单一的结构。随着业务的发展和团队的扩大,我们需要逐步拆分服务。因此,随着业务的复杂性,我们的调用链和调用网将变得越来越复杂。当它们复杂到一定程度时,出现了许多困难的问题。
目前,许多团队进行微服务的过程中,可能只看到微服务的优势,没有遇到服务管理上的问题。毕竟不是每个系统都达到了超级复杂的标准,但是提前关注这些问题,也很重要。作为企业的软件架构师或技术负责人,我们应该始终从发展的角度看待问题。软件行业的发展变化很大。如果企业目前的架构不能适应未来一两年的业务发展,将对业务和技术进步造成很大障碍。如果架构师能够吸取其他企业的教训和经验,提前布局,那么业务扩张过程中遇到的技术问题就会少很多。
超复杂调用网带来的问题。
我个人定义了超复杂调用网:
内网非测试微服务超过1000项。
至少有一个微服务,实际例数超过300个。
外部API通常涉及至少10项微服务。
在内部技术实践中,我们发现系统达到这个水平后,超复杂的调用网会产生许多棘手的问题。
首先要点是微服务的数量。如果一个系统中的微服务数量只有几百个,那么绘制一个包含所有微服务的调用图是有利于管理的;但是,如果超过1000个,它们放在一张图片里,整个图片就变得不可读了,意义不大。
第二,如果一个微服务的例子只有几十个,那么例子的管理就比较简单了。如果例子数超过300,团队必然需要使用一些分片策略或长连接策略,这会带来一些特殊的问题。
三是单个API涉及的微服务数量。如果API一般需要10个以上的服务,监控将面临更大的挑战。以字节跳动场景为例,目前字节跳动内网在线微服务数量为1万级,其中最大的微服务约为1-2万个,单个API一般在后端关联几十个甚至上百个微服务。面对这样的复杂性,有三个问题最为突出:
第一,容量估计难度大。微服务已经达到了一定的复杂性,它们的调用关系非常复杂:一个核心服务可能有几百个依赖链,显然很难调查每个依赖方或者仔细跟进每个限流策略。此外,不同的业务将通过不同的活动实现业务增长。对于核心服务来说,追溯每个业务的增长也是一项非常艰巨的任务。
第二,服务治理的难度将大大提高。这里的服务治理包括限流、ACL白名单、加班配置等。由于调用关系变得复杂,每个服务可能会调用几十个甚至几百个依赖服务,一些核心服务会被几百个服务所依赖。这时候如何梳理这些调用关系,配置多少限流,配置什么样的白名单策略,就成了团队需要深入探讨的问题。
第三,容灾复杂度增加。在复杂的调用关系下,每个API都会依赖大量的微服务,每个微服务都有一定的故障概率。为了在不稳定的服务环境中获得尽可能稳定的外部效果,我们需要区分强依赖和弱依赖,并辅以特定的降级策略。
业界尝试
那么行业会对这些复杂的治理问题做出怎样的尝试呢?
首先是鸵鸟心态。完全不做工作,这是业界最广泛的尝试。认为许多企业并非没有受到超大规模调用网的侵扰,也不是没有对其进行一些尝试,而是解决问题造成的成本和损失实在难以量化。
比如一个核心服务有很多依赖者,其中一个依赖者的代码有严重的重试漏洞,瞬间产生大量的重试压垮了核心服务,最终造成系统级灾难。此时我们可以去追溯问题的直接原因——代码质量问题,至于隔离没有做好,超复杂的调用关系没有梳理清楚等等,这些都会归结为间接原因,往往可以不被追究。
二是精细监测和限流。行业内的一些开源组件在功能上确实做得很好。例如,左图是一个著名的开源组件,它将对整个服务链进行精细监控。例如,每一个三角形都是Gateway,中空圆形才是真正的服务。它展示了从流量入口到每个微服务的整个链路。如果链路是绿色的,说明流量是健康的;如果链路是红色的,说明流量异常。有了这么详细的拓扑图,开发者就能看到它的依赖关系。
这个看起来很漂亮,所以大概两年前,我选择了一条中等规模的业务线,整理出所有的依赖关系,得到了上图右侧的图片。每一个代号都是一个服务,每一条线都是这个服务的依赖关系——太复杂了。左边的图片因为只有4个服务,整体比较清晰,但是如果是几百个服务相互交织、相互依赖,用这个图片来计算无疑是不可行的。
三是单元化,或SET化,更具代表性的是蚂蚁和美团。他们使用的主要方法是部署多个服务:set1.set2.set3,通过单个shardkey选择流量。通过这种方式,set可以有效地隔离资源,当单个set出现问题时,可以通过切流来容灾。
但是它也有三个局限性。首先,SET化需要一个合适的分片键,比如用地域或者账号来划分,这需要与业务属性相匹配,并非所有的业务都能找到这个合适的分片键。第二,这种方法需要更多的非全局数据,比如当地的生活订单,用户在北京下单的酒店数据不需要经过深圳。但是在抖音.今日头条这些综合信息服务场景中,非全局数据很少,那些看似本地的数据,比如用户名、用户粉丝数、最近的点赞列表,其实也是全局数据。最后,SET需要冗余和备份成本,大型公司可能无法支持。
四是DOMA。其英文全称为Domain-OrientedMicroserviceArchitecture。Uber在2020年提出了这一架构。下面是一个简单的例子,绿色是publicinterface,红色是privateinterface。若有流量要访问域内的微服务,则必须通过GatewayService转发,然后才能访问。
如果用户想在域外访问这个数据库,我们需要通过左下角的Query.ETL将其转换为离线数据库。整个框架是一个domain,它不同于dddd的domain,它被称为服务域,可以理解为一组服务的集合。字节跳动也参考了这种domain思想,聚合了一些服务,产生特殊的化学反应。
山东济南汉码未来想告诉大家,DOMA架构也存在一些问题,例如,它通过了一层GatewayService。事实上,我们在外层已经有了从外网到内网的Gateway,如果内网再放置过多的Gateway(特别是集中的),肯定会带来额外的性能消耗,并造成一定的延迟上升,这也是字节跳动没有采用这种方式的原因。
探索和实践
对于超复杂的调用网络,字节跳动探索了一些最佳实践,其中第一个核心是服务分层原则。
经过自上而下的调用,服务出现了非常复杂的调用关系,对此,我们可以根据康威定律对其进行一些横向切分,对调用关系进行分层。
马尔文·康威于1967年提出了康威定律,这意味着设计系统的结构受制于产生这些设计组织的沟通结构。比如假设某公司内部有四个团队,如上图所示,左团队与上团队沟通密切,上团队与下团队沟通较少。将这种关系映射到微服务架构后,也是类似的。上微服务与左微服务的通信耦合会更大,与下微服务的联系会更弱。