Dubbo是一款高性能,轻量级的Java RPC框架。虽然它是以Java语言来出名的,但是现在我们生态里面已经有Go、Python、PHP、Node.JS等等语言。在GitHub上,https://github.com/dubbo 下面已经有很多生态相关的东西。
Dubbo是一个RPC框架,它和所有的RPC一样,有一个最小运行子集,它需要Provider、Consumer,以及一个服务注册发现相关的东西,在Spring Cloud里面是叫服务注册发现,在Dubbo里面我们叫它注册中心(后面讲到东西我们都以注册中心来进行说明)。
简单介绍一下Dubbo的整个启动过程:
Provider导出一个服务,这个服务就是可被调用的;
第二步,往注册中心注册这个服务;
Consumer这端会来订阅相关的服务,如果注册中心里面,Provider列表有变化的话,它也会得到通知;
Consumer会根据一定的路由规则从注册中心拿到Provider列表,再根据一定的负载均衡策略,精确地调用到某台Provider上去。
这就是一个简单的一个RPC的调优过程。
Dubbo在2011年就在GitHub上进行了开源,经历了很多年的发展,整个社区一直非常活跃,现在GitHub上Star数已经高达23K+,Fork数16K+。
在2018年2月份的时候,阿里巴巴已经把Dubbo的项目捐献给了Apache社区,希望更多人能够参与到Dubbo开发中来,希望依靠集体的智慧让Dubbo变得越来越好。现在Dubbo的committer,外部开发者的人数已经多于阿里巴巴开发者,包括微店,网易云音乐,考拉,韩都衣舍等等。
Dubbo因为开源这么多年,积累了较多的用户,包括很多互联网的企业,包括阿里巴巴,考拉,滴滴等互联网企业;还有很多中字头企业,中国电信,中国人寿,中国工商银行;还有一些比较传统的企业。
在RPC整个链路中,需要的元素有Provider、Consumer,以及注册中心(中间Zookeeper是作为注册中心来使用的)。整个注册过程如下:
Provider会把一长串URL(dubbo://xxx的字符串)写入到Zookeeper里面某个节点里面去。
Consumer的注册也是类似,会写到Zookeeper里面某个节点(Consumer写入的原因,是因为OPS服务治理的时候需要实时的消费者数据)。
Consumer发起一个订阅,订阅相关的服务。
当某个服务的Provider列表有变化的时候,Zookeeper会将对应的变化通知到订阅过这个服务的Consumer列表。
从图中我们可以看到Provider端的URL非常长,特别是当一个服务有大量方法的时候。Provider端的URL会先从Provider到Zookeeper,再往Consumer传递,这样导致了单次传输的网络开销比较大。
那么再来看一下集群的情形,图中左边有N个Provider,右边有M个Consumer,那么Provider发布的时候,会遇到什么情形呢?Provider每次发布它会先下线再上线,所以每个Provider发布的时候,Provider会发送两次通知,也就是发送2N次;接收数据方有M个Consumer,最后算出在整个网络里面的推送数据的次数是2N×M。
来看一个真实的案例,在杭州有一家中等规模的电商公司,公司内部有4000+个服务,以Zookeeper作为注册中心,Zookeeper有100w个节点,在发布日的时候,公司内部网络的网卡被打爆了,进而导致服务变更的推送失败,新的服务注册也失败。整个集群基本上处于不可用状态。同样的也收到了一些中小公司的反馈,每次在发布的时候,网络也会有个抖动。
分析一下为什么会出现这种情形。
Zookeeper的100万节点中,大约有10万个Provider节点和50万个Consumer节点。按照前面的算法,在所有Provider同时发布的极端情况下,有2×10万×50万次推送,也就是说会产生1000亿条的数据推送。针对每次推送的数据进行了一个统计,每条URL大小大概有1KB,那么计算出来的极端的推送数据量是1KB再乘以1000亿,已经是100TB的级别了。
上面说的是极端情形,就算是发布日也不可能同时进行发布:有的应用发布日不发版本,不同应用不可能同时发布,同一个应用也需要分批发布。假设同一时刻发布的量在千分之一,那么推送的数据量也在100GB,所以出现发布日的时候间断性地网卡爆掉的现象就不足为奇了。每次发布的时候,都会想着要跟别的应用发布时间错开,争取单独发布,作为程序员还要纠结这个事情真是一个悲剧。
来分析下现在的问题和需求:
首先,根据上述案例中的数据分析得知,性能出现了问题。推送的数据量非常大,存储的数据量大,网络传输量大,服务推送延迟,网卡堵塞,服务注册不可用。
接着对Provider端那个很长的URL进行分析之后发现,不需要把整个URL写到注册中心里,只需要把IP的端口写进去就可以了,因为只有IP的端口需要实时变化。把其他信息放到一个类似的KEY-VALUE结构的持久化存储里去,而且这个KEY-VALUE结构只要是应用级别就行了,节省了大量的存储空间。
社区中对服务测试的需求非常强烈。要支持服务测试需求,就需要知道调用的服务方法名,入参出参的详细信息。所以这部分信息也是需要存储下来的。但是这部分信息非常大,每个服务中可能有10多个方法,每个方法可能有三四个方法入参,入参和出参的完整数据结构往往非常复杂。这部分数据信息也叫做服务的元数据信息。
首先来看一下怎么解决性能的问题。主要有两种方式可以解决:
怎么减少当次的注册量,就像前面分析的,只存储IP的端口到注册中心;
是否可以减少推送的次数,现在推送次数太大了。
查看上图可知,Provider端URL还是很长,期望简化往注册中心注册的信息;同时服务测试需求,又同时期望能将更丰富的元数据信息进行持久化的存储。
Provider端写入的改造。Provider往注册中心写的时候,将整个数据的写入分成两部分:
写入注册中心;
写入元数据中心。
注册中心作为服务的注册和发现,更加关注数据的实时性和有效性(watch机制),整个URL中IP和端口就能判断某个服务是否可用,其他信息都是相对固定不变的。所以注册中心中,只需要存储IP和端口。元数据中心中存储URL中除IP和端口外的其他信息,加上服务测试需要的服务方法名,服务方法的出入参信息。元数据是一个KEY-VALUES的持久化存储,是独立于注册中心的存储,它不需要有watch的机制,而只需要提供持久化存储。图中使用的的KEY VALUE存储是Redis,但是元数据中心定义了一套SPI,开发者可以去扩展,可以自己实现DB存储,或者其他持久化存储的方式。
Consumer端获取Provider列表信息的改造。Dubbo之前的版本中,直接从注册中心里面获取Provider端的服务信息,获取到的信息已经是一个完整的可调用的服务信息。但是Provider端写入改造之后,原有Consumer端获取的Provider服务信息的方式不可用了。除了从注册中心获取到的数据之外,还需要从元数据中心里拿到元数据信息,然后对这两部分数据做一个Merge之后才能构建出完整的可调用的服务信息。
当前Dubbo2.7版本还没有完全去除所有参数,而是采用先去除部分参数的方式来验证;后续会逐渐迭代完善,同时在2.6.x版本中也会进行一些兼容方案的支持。
上面的改造针对的是怎么减少单次的推送数据量,针对的还是服务维度。期望中最理想地给注册中心减负的方式是应用维度的服务注册和发现,可以参考Spring Cloud体系下的Eureka实现。一旦实现这种方案,服务注册中心就再也不会成为RPC领域的瓶颈,而且可以认为这种方案是服务注册的终极方案。
当然这种实现方式做的改动相对比较大,不仅需要将服务执行和运维完全分开,而且需要一定的架构体系改造来支撑具体服务的发现。到目前为止还没有形成成熟可靠的方案,团队内部也只是在探讨阶段。
所谓服务变更推送开关,就是针对任何的服务信息的变更,不进行推送。
到底哪种情形需要这种开关呢?阿里巴巴整个集群的机器数非常大,所以宿主挂掉或者虚拟机挂掉出现的概率比较高。在每年双十一的时候,大部分消费者都会去淘宝天猫上购物。在11月10号11点50几分开始,大量买家在拼命地刷新购物车或者商品详情页面,这时候阿里巴巴内部的系统负载是非常高的,网络负载也非常高。如果这时候,有一台机器因为宿主机挂了的原因而导致部分服务下线,这时候需要推送相关应用服务下线的变更给对应的服务Consumer。这时候就需要占用网络带宽,可能对服务调用产生影响,进而还会对双十一造成很大的压力。所以这时候就希望有一个开关,能够把整个服务推送关掉。
但是这时候也会带来一些问题,当服务Provider不可用的时候,注册中心没有向服务Consumer推送变更通知,服务Consumer请求的时候可能会报错,这时候的小部分服务出错可以允许的;保证整个集群上万台机器,特别是整个双十一核心链路的稳定性才是双十一最重要的使命。
在一个大的集群环境中,在没有路由规则的情况下,Consumer集群会调用整个Provider集群中的任何机器。服务分组,就是对Consumer集群和Provovider集群进行分组,将大的服务级分成几个子集。
举个例子,集群中有8个Consumer实例,有8个Provider实例,按照正常流程Consumer这8个实例会调用Provider任何一台,任何一个Provider的变更通知也会通知到这8个Consumer实例。但是如果对它进行分组呢,Consumer实例集群分成A和B两个组,Provider集群也分成A和B两个组。Consumer中A的组只能调到Provider中A组的服务;Provider的A组中的实例在发布过程中,也只会推送到Consumer的A组中,而不会推动Consumer的B组。最终通过推送的范围,来减少了推送的数据总量。
对于服务分组的的实现,这里不做展开,本文后面再次讲到服务分组的时候,会稍微展开陈述。
前面陈述的服务注册相关的改造和方案,都是围绕Dubbo等RPC进行的。接着来看一下在互联网环境下,理想的注册中心是什么样子的。(以下阐述只代表个人观点)
CAP理论:现在大部分主流而且在使用中的注册中心都是满足CP的,但是在互联网大集群环境下,期望的结果是满足AP的同时,能够满足最终一致性。在大集群环境下,可用性往往比强一致性的优先级更高。以Zookeeper为例,Zookeeper能够为分布式系统提供协调功能的服务,默认提供强一致性的数据服务,但是它在某些情况下是允许Zookeeper是不可用的。列举一个场景,Zookeeper Leader失效了,这时需要重新选举Leader,而这个选举过程需要30秒以上(数据来自于网上的文章),这段时间内Zookeeper对外是不可用的。
去中心化:Zookeeper是有Leader机制,往Zookeeper里写数据都是往Leader里面写,这个Leader其实就是一个单点。所以整个写的过程是中心化的。而且Zookeeper对跨城跨机房的方案上,支持非常有限。
数据推送的强控制:期望对推送的有更加强的灵活性。还是以Zookeeper为例,Zookeeper中有watch机制,每个数据节点发生变更的时候,就会往外推送变更的通知。但是作为注册中心,我们期望能够控制它的推送频率,针对新增节点只需要一分钟里面推送6次就可以了,每十秒推送一次,这样可以合并一些变更通知,减少网络数据请求的数据量。
容量:Dubbo是单进程多服务的方式来注册服务的。这也就意味着注册中心中需要存储的数据量较大,所以要有足够的容量来支撑这种场景。
那些注册中心产品:Zookeeper作为服务注册中心的公司在减少,那么现在有哪些方案,可以来替代呢?
Eureka是一个AP的应用,而且它是去中心化的。但是它有几点不足:
在我们的内部的性能测试中,它性能表现非常一般,性能大概只有Zookeeper的60%左右。
Eureka内有一种契约机制,它每隔30秒会发起一个续约的请求,如果3次没有接收到,它才会过期失效;如果一个服务非正常退出(没有发起解约请求),那么就存在这个超时的间隙期,服务是不可用的。所以在生产环境,对服务敏感的相关应用方是无法满足需求的。
Eureka是应用维度的服务注册,当前的dubbo是服务维度的注册,如果要匹配的话,需要大范围改造。
Netflix宣布了停止更新Eureka 2.0。
Etcd是Zookeeper的升级版,它参考了Zookeeper的很多实现,同时进行了较多优化。Etcd的强一致性协议和代码实现更加简单,它的部署方式也更加简单,它支持了Rest的方式进行相关访问,它的性能相对Zookeeper来说也有了一定的提升。但是它还是一个CP的系统,它也是要求数据的强一致性,而牺牲部分的可用性。
Consul相对前面几个产品来说,更加专注服务注册发现本身,它是一个比较专业的服务注册中心。Consul有了后台管理页面,它有了健康检查,Consul原生支持多数据中心。但它的性能上有瓶颈的,它和Zookeeper和ETCD进行对比,它性能是稍微差一点的;同时Consul也要求数据的强一致性而牺牲部分可用性。
Nacos是阿里巴巴开源的一个产品,内部系统也在使用,它已经经受了一定流量和用户的考验。现在阿里巴巴集团内部的Provider和Consumer数量已经到达了亿的级别,它现在能够支撑上亿级别的订阅量,整体经受了一定的实践检验。Nacos整体设计是去中心化的,而且设计上满足AP和最终一致性,性能上和Zookeeper比较接近。
前段时间和网易考拉在沟通过程中也发现,他们也在做一个自己的注册中心;新浪也有一个自己的服务注册中心。所以许多大的互联网公司,因为定制或者差异化的需求,都在自研注册中心。
Dubbo内部有一个配置文件叫dubbo.properties,这种配置方式和Spring Boot的application.properties是比较像的。每次新开发一个Dubbo应用的时候,应用开发者都需要去写一大堆的配置到dubbo.properties中,包括注册中心的地址,元数据中心的地址,应用级别的超时时间等等。当所在公司只有两三个应用的时候,一个个应用单独设置的方式是没问题的;当业务快速发展的时候,应用数从2个变成20个应用的时候,那么所在的技术团队可能需要整理一份快速构建应用的文档,供应用开发者参考使用,而且这个文档需要及时维护更新。如果这时候还需要更改注册中心地址(原来2个应用的时候,Zookeeper的地址用IP来快速实现,现在想换成域名了),意味着要去推动这20个应用的开发者,让他们修改对应的配置,然后测试并且发布。整个过程非常痛苦!需要有一种类似于Spring Cloud Config的配置方式来满足集中式的配置方式,相当于一个远程集中式的dubbo.properties。
Dubbo 2.7以前的版本中,服务路由规则,服务治理规则的数据都是存储在注册中心中的。之前大部分用户都选用Zookeeper作为注册中心,Zookeeper兼具了Key-Value的功能,所以之前的版本中运行起来是没有问题的。 但是如果选用的注册中心,不具有持久化的功能,这时候的路由规则和服务治理规则就没地方存储了,整个系统就玩不转了。作为Dubbo开发者,期望将服务治理和服务路由规则分开存储到一个集中式的Key-Value存储中。
要解决上述两个痛点,需要在Dubbo中引入一个远程的集中式的配置中心,这个配置中心存储了远程的dubbo.properties,路由规则,服务这里规则等。
举个场景,一个公司的所有应用已经引入了ETCD作为自己的应用的动态配置管理,但是在引入Dubbo以后,Dubbo中又有几套对应的动态配置可供选择,如Nacos、阿波罗,Zookeeper。这就要求公司层面需要维护两套动态配置的Server,这个成本比较高,而且增加了系统的稳定性的风险。对于架构师来说,需要Dubbo能支持ETCD的动态配置。
图中分成上下两部分,下面黄色部分就是它的一个存储;上面的整个部分是Dubbo内部实现。上文中所讲的动态配置,在Dubbo中定义为配置中心。
在实现层面,在Dubbo中定义了一层SPI,默认实现的Zookeeper,Apollo,Nacos。应用架构师去扩展这个SPI就可以完成ETCD的定制,这样就能达到和原有的应用中使用ETCD方式兼容。
对于运维工程师来说,原来的一个注册中心的地址变更要推动每个应用开发者去做变动,应用开发者非常烦,运维工程师很吃力。现在只需要在远程的dubbo.properties里进行统一去升级,然后通知相关的应用开发者进行一些适当的验证。
因为已经定义了配置中心的API,Dubbo开发者直接调用对应的API来实现服务规则和路由规则的存储。
三个中心就是前面讲到的注册中心,配置中心,元数据中心。
期望的使用方式:Provider先去配置中心里获取注册中心的地址和元数据中心地址,再根据拿到的注册中心地址去对应的注册中心注册服务,根据拿到的元数据中心地址写入元数据信息到对应的元数据中心Server。Consumer和OPS也是类似的。
通过配置中心这种集中式的配置方式,可以让开发者从原来烦琐的配置中解脱出来,让它更聚焦于业务的开发,而不需要关注框架层面的东西。
Dubbo路由规则,按照覆盖范围可分为应用级别,服务级别,方法级别路由规则;按照功能纬度,可以分为黑名单,条件路由 ,TAG路由规则。大部分路由需求都是可以通过组合来实现的,如应用级的黑名单可以通过应用级别+黑名单路由规则方式组合。
某产品新开发了一个新特性,想进行A/B Test,让部分用户开放新功能体验;或者产品在迭代过程中,想进行下改造功能的灰度验证。在当前微服务的架构体系下,一个新功能往往依赖整个调用链路的上下游一起完成。所以这两种新功能的验证,基本不可能在单机上完成,往往需要服务链路的上下游一起隔离出一部分机器进行验证。在这里,称为全链路灰度发布验证。
来看一种最简单的场景。客户在浏览器端发起一个HTTP请求,这个请求会根据一个负载均衡策略访问到一台web服务器(30.5.127.44),这台机器会调用服务A集群,服务A集群会调用服务B集群。业务开发者开发了一个新功能,想在线上验证功能的正确性,但是又不想原有的功能受影响。也就是说想从服务集群里拿出少部分实例去验证线上的功能。假设下需求,希望总用户的千分之五的用户能够走到新功能的流程,帮助验证新功能的正确性。
从服务A集群中选出一台机器(30.5.120.16)作为灰度验证的机器,从服务B集群中选出一台机器(30.5.128.66)作为灰度机器。对选出的这两台机器打上标canary(金丝雀)。
需要在Web服务器运行的代码逻辑中,增加逻辑:获取到用户的UserId,UserId对1000求模之后小于5的,在Dubbo对服务A集群发起请求之前带上Tag=canary(在Dubbo中是通过设置Attachment来完成)。
Dubbo自带的Tag路由规则会做以下事情:Dubbo会先拿到要调用服务所在的应用名;再根据应用名+Tag路由规则的名称canary,去获取到对应的机器列表,Web服务就拿到了30.5.120.16这台机器;Dubbo根据拿到的机器列表依据负载均衡策略发起请求。相应的Web服务器中没有打上标的机器,会访问到其他机器(30.5.120.26),而不会访问到已经被打上标的机器。Tag路由规则,完成了对相应Provider和相应Consumer端的隔离。
通过Tag路由规则已经解决了Web集群到服务A集群这里面的链路,但是怎么解决服务A集群到服务B集群的Tag的传递呢?现在比较流行的一些全链路跟踪的产品可以帮我们做到,如Open Tracing,Zipkin。我们以Zipkin为例,通过Zipkin实现Dubbo的Filter可以把这个标从Web集群传到服务A集群再传到服务B集群,整个链路都可以传递下去。
整体调用链路总结。满足灰度验证的用户链路:web服务 -\u0026gt; 30.5.120.16 -\u0026gt; 30.5.128.66; 不满足灰度验证的用户(不打标)链路:web服务 -\u0026gt; 集群中30.5.120.16之外的机器 -\u0026gt; 集群中30.5.128.66之外的机器。
通过上述步骤已经完成了全链路的灰度环境搭建,这种方式的应用范围非常广,可以按照自己的业务场景和需求进行一些调整来达到自己期望的效果。下面集群中也有案例进行说明。
从集群角度来看下,服务实例之间的隔离。假设一个Provider集群有8个实例,Consumer-A集群有4个实例,Consumer-B集群有4个实例; Consumer-A集群和Consumer-B都会调用Provider集群。
服务分组:以Provider和Consumer-A为例。将Provider分成左(1、2、3、4)和右(5、6、7、8)两个组,将Consumer-A分成左(1、2)和右(3、4)两个组。Consumer-A中左组只能调用到Provider中的左组,Consumer-A中右组只能调用到Provider中的右组,而不会调用到整个集群的其他实例。服务分组除了能让注册中心减少推送的数据量外,还能减少Consumer和Provider长连接的数量,从而带来一定的性能提升。
服务分组最简单的实现方式,在Dubbo的Service和reference中配置中配置多个group,进行手动匹配。
更高级的服务分组实现方式,通过路由规则的方式进行动态匹配,路由规则中根据一定的规则(如根据Ip的最后位的奇偶数)将Provider和Consumer进行分组,然后路由规则里去完成隔离。这种方式暂时还没有发现较好的实现。
业务隔离:来看个现实的场景,阿里巴巴交易平台承接的业务非常多,在新零售出来之前,所有的业务都是线上交易,在出现2分钟的不可下单的时候,体验糟糕但是还不会引起大范围的骚乱。但是在盒马鲜生线下购物的时候,如果出现2分钟不能下单,那在排队的消费者意味着要干等两分钟,这是非常糟糕的体验。
抽象下上面的场景,用更加产品化和技术化的方式来分析。以Provider和Consumer-A,Consumer-B为例。Provider集群的消费者非常多,包括Consumer-A,Consumer-B或其他Consumer集群。因为Consumer-B的业务非常重要,不想让其他机器的故障,影响到Consumer-B的稳定性,所以Provider中来自于Consumer-B的流量需要进行隔离。业务隔离的实现方式,可以采用Tag路由规则的方式来实现。对Provider集群的7,8机器打上标-BTag(即路由规则,可以通过OPS打标),然后在Consumer-B集群中调用Provider之前setTag=BTag (在Dubbo中在attachment里设置tag值)。
灰度环境:
单机灰度的话,只要在发布的时候,可以单台机器发布就可以。
全链路灰度,需要在整个集群中圈出一批机器,如从Provider,Consumer-A,Consumer-B集群中选出3号机器作为灰度环境。全链路灰度的实现已经在前面说明,用Tag路由实现。
Dubbo在启动时,会导出服务,让服务是可被调用的;同时它还会向元数据中心写入元数据信息(包括服务方法的方法名,路参,出参等信息)。
服务测试是在Dubbo OPS后台中的功能。服务测试业务流程:先去元数据中心获取元数据信息;在页面中展示服务的结构体,等待用户输入;用户输入参数信息;Dubbo OPS后台根据元数据信息和用户输入的参数构造出服务调用的入参,向相应的Provider发起泛化调用。泛化调用被较多的网关系统使用。
除了服务测试功能外,还需要:
文档功能。自动生成文档,生成的文档是可视化的,而不是JSON等文件格式。
自动化测试。
Swagger可以满足以上需求。但是Swagger和SpringMVC结合紧密,SpringMVC Rest接口加上注解之后,可以快速整合Swagger。
要让Dubbo能够通过Swagger方式生成文档和进行自动化测试,需要做两个事情:
将Dubbo接口形式转换成Swagger文档。Swagger是有文档规范的,只要将接口形式转换成Swagger约定的格式,就可以满足Swagger文档的形式进行输出。
Swagger服务测试功能。外部HTTP请求到一个Controller,Controller需要将HTTP请求转换成Dubbo请求,包括参数的映射和服务的映射。
Hystrix停更了,在GitHub官网上推荐了Resillence4j,阿里巴巴也开源了Sentinel。这里进行一个简单的比较。
Hystrix的两块功能,隔离和熔断,能满足大部分需求但是停止更新了。
Resillence4j要求JDK8及以上,对function编程更加友好。
Sentinel 在阿里内部使用的范围非常广,已经经受住了一定的考验。每年双十一在买东西的时候,如果出现一个页面上显示系统繁忙请稍候重试,或者显示人太多了请排队,这时候其实就是Sentinel在后台发挥作用了。Sentinel OPS这块做得非常好,达到了开箱即用的标准。
在Consumer端设置重试,必须保证Provider端多次调用是幂等的。
设置应用级别的重试,会让已经压力倍增的系统雪上加霜。举个例子,Provider系统的线程数是200,当Provider某个服务出现问题,导致RT从200ms变为了3500ms;Consumer因为设置了默认重试且重试次数为3,这就会导致Provider线程池很快会被耗尽。
所以,默认不要设置应用级别的重试,对有业务需求的服务单独设置重试的规则,并且保证Provider端服务的幂等性。
设置超时时间,能够防止系统雪崩和资源耗尽。一个Consumer调用Provider的时候,Provider 需要3秒处理完业务逻辑之后返回结果,Consumer默认是同步调用,需要某个线程同步阻塞等待3秒,也就是说如果没有超时机制,很容易将Provider的问题传递给Consumer并且不断往上传递。如果Consumer设置了超时时间200ms,Provider加入需要3秒处理完结果,但是不会将问题传递给Consumer。
建议的超时时间是一百毫秒到两百毫秒左右,对于特殊业务进行特殊设置。
隔离包括实例隔离和线程隔离。实例隔离,就是前面讲到的服务分组和业务分组,通过实例隔离达到问题和影响面的隔离。
线程隔离,可以分为静态隔离和动态隔离。静态隔离,首先通过一定的分析方法找出一些比较慢的方法,或者甄选出业务上比较重要的服务方法;再对这些服务或者方法单独设置线程池以及对应的线程个数。动态隔离是在Dubbo应用运行态的时候,自发地调整线程池的隔离。Dubbo应用里默认的线程数是200个,200个消耗完之后,新的请求进来就会返回线程池满的异常。这时候Dubbo内部会对运行中的服务方法进行统计,统计出并发线程数\u0026gt;8,RT\u0026gt;300ms(假设的一个指标)的服务,把这些服务扔到独立的线程池里去完成,后续这些服务的执行都会由这些新建的线程池的线程执行,其他服务仍然在这200个线程池里面执行。当这些隔离的服务方法运行一段时间后,它的RT变得正常了,那这部分独立出去的线程池可以被销毁,这些服务方法可以重新由原来的200个线程执行。整个过程的调整是动态的。整个动态隔离的机制和Hystrix的线程隔离有些相同的含义。
每个微服务实践的时候都会讲到。微服务最佳实践里面,强链路跟踪,限流,降级、熔断等,都可以在系统里引入,进而保证系统的稳定性。
系统可验证主要包含两方面,第一个是环境是可被验证的;第二个是通过一定的测试手段和方法来保证系统是可测试和可验证的。
环境可被验证,有些场景需要支持单机的灰度,有些场景需要全链路的灰度。
测试方面:在系统上构建接口级别的服务测试,自动化测试,支持全链路压测。通过服务测试和自动化地执行来增强系统的正确性测试;通过全链路压测,来判断全链路的瓶颈在哪里。
Service Mesh在2018持续火热,阿里巴巴和蚂蚁都在这一块有布局。现在阿里内部主要在两个场景中去做尝试,第一个是跨语言。阿里大部分语言是Java,但是有少部分是C++,还有部分NodeJS,需要Service Mesh去解决跨语言之间的调用。第二个是异构系统。阿里巴巴收购了一些公司,但收购进来公司的技术栈和集团内部不一致,它可能是Java语言,但是使用的Spring Cloud的微服务体系,还有一些公司没有使用Java语言,需要通过Service Mesh来解决这种异构系统之间的调用问题。
从实现方式上来说,阿里没有重复的造轮子,控制层这一块(也被称为sidecar),是在Envoy上进行了一些扩展,实现了一个Dubbo Filter,现在这部分代码已经被捐献到了Envoy的社区。数据层实现方式是对Istio 做了一些扩展和定制。因为Istio有一套默认的服务发现,服务治理的产品,但阿里巴巴有自己的服务发现,动态配置管理,服务治理的产品方案。在数据层,会将Istio和阿里巴巴中间件的产品做一些整合,来满足需求。
从部署结构上看,Dubbo应用和对应的Envoy实现是部署在一台机器上(虚拟机)的,只是以两个进程的形式存在。Dubbo应用需要调用Spring MVC应用中的服务,每一次发起调用的时候,Dubbo应用会首先调用到同一台机器上的Envoy进程,再调用到另外一台机器上的Envoy进程,另外一台机器的Envoy进程再调用SpringMVC应用。整个调用过程多了两跳。
阿里巴巴已经在咸鱼的一些场景上在做Service Mesh的验证,同时程序和架构也在持续优化过程中。
针对2.7之前存在的若干异步问题进行了优化。在Dubbo2.7之后,开始全力拥抱JDK8。所以2.7版本将基于JDK8 中的CompletableFuture做出一些针对性的增强来支持异步,类似于Promise方式。
优化服务治理参数配置,升级服务路由规则,同时对应的OPS也进行了相应的升级。Dubbo2.7之前的路由规则对应关系一个服务可以对应多条规则,优化之后一个服务只能对应到一条规则;新增了前文讲到的Tag路由;服务治理参数,路由规则的数据存储都将使用配置中心。
社区里面分支演进分成2.6版本和2.7版本。2.7版本主要有一些新功能的迭代,2.6以维护为主。如果2.7里有些功能可以被2.6借鉴的话,也会往2.6里面叠加。
新增了一个工具页面,用于快速构建Dubbo的Spring Boot的应用。这个参考了Spring Boot的模式。
在Dubbo体系下,有一个Dubbo Spring Boot工程来支持Dubbo+Spring Boot的整合。到目前为止支持Dubbo2.6.X版本,支持Spring Boot 1.5.x和2.0.x。因为Spring Boot的1.5和2.0的分支差距比较大,所以Dubbo Spring Boot也维护了两个版本。后续Spring Boot 1.X会在2019年停止更新。
Dubbo OPS进行了全新的升级,有了一个新的UI,合并了Dubbo Admin和Dubbo monitor两个应用,而且整个OPS用了Spring Boot工程结构,从而真正达到了开箱即用。Dubbo OPS能够同时支持2.6和2.7的版本的运行,对新功能进行了相应的配置支持。
Dubbo捐献给了社区之后,重新搭建了新的官网,新的中英文文档,有了中英文博客,整个社区现在非常活跃。在这半年多依赖,和一些深度使用Dubbo的客户做了一些交流,如工商银行,考拉等等。今年举办了5场Meetup,包括杭州,成都,深圳,北京,上海,吸引了大量的开发者参与。
正如前面所说,在多语言方面,社区引入了PHP,Go,NodeJS,Python等。
对现有的一些生态组件进行持续升级和维护。
引入了一些新的扩展组件,包括SkyWalking,ETCD等等。
社区持续投入,包括PR,Issue跟进,优秀博客和文档的持续输出。当然整个运作形式是根据Apache的方式进行运作,而Apache沟通的最主要形式就是邮件,所以各开发者可以订阅Dubbo的邮件列表进行持续跟踪。
持续推进Dubbo Meetup和客户交流。
持续功能增强和优化,包括前面的注册中心的优化,路由规则的持续增强和优化。现在Dubbo的committer中,阿里只占了小部分,其中来自于网易音乐和考拉,微店,韩都衣舍,有赞等公司的开发者也非常活跃。最近有一位印度的开发者,也正式成为了Dubbo的Committer。
因为现在生态中,部分实现已经有了重叠,包括后续进来到我们生态体系里的扩展也会造成同样的扩展或者实现出现重叠,这时候会进行一些优胜劣汰。
Dubbo和SpringCloud关系,部分同学在选型的时候觉得要么选Dubbo,要么选SpringCloud,两者互相排斥的。其实不是这样的,这块的详细描述,可以关注公众号:阿里巴巴中间件。里面会有一些文章的描述和分析。
Dubbo 3.0的进展,比较受到关注。因为涉及到Dubbo和HSF整合,以及一些新特性的开放,如reactive的引入。但是整个过程是非常漫长的,因为两个产品不可能完全融合,需要有一些特性存在。而且在完成之后,整个迁移的过程非常巨大。今年上半年,会有一个预览版本,现在很大一部分人力已经投入到Dubbo 3.0的研发中。
Service Mesh,现在还在测试阶段,整体还未达到生产环境的级别。还需要不断优化性能和不断在各个场景中试用。
作者简介
曹胜利,阿里巴巴中间件技术部技术专家。2010年加入阿里B2B中国网站技术部,先后在1688技术部,菜鸟技术部负责业务产品;曾负责阿里采购平台的架构和服务化改造工作;目前在阿里巴巴从事应用容器和微服务框架的开发、实施以及效率提升相关的工作。关注分布式架构、微服务,特别是Spring,Spring boot生态的发展。