关于dubbo多服务环境中的服务隔离的总结

2018-05-31  本文已影响1226人  素墨月羽

写在前面

由于要组内分享,所以顺便记录一下分享的内容,也是给自己提前打个底

问题背景

公司内部是使用dubbo来作为服务与服务之间沟通的主要手段,然后经常因为各种原因,比如多版本并行开发,这是不可避免的;或者是因为开发人员对服务隔离了解不够,这是人为原因,导致服务间的调用经常会串掉,而不同的服务器上部署的代码可能是不一样的,给测试与排查出现的问题带来了很大的麻烦。

分析原因

那既然是并行开发,其实原因很简单,无非是多台服务器在一个注册中心同时注册了服务,导致服务的调用发无法精准的调用到本该调用的服务。当然,这其实是分布式服务带来的好处,那有好处自然也会有坏处。要解决多服务开发过程中的服务隔离的问题,我们首先要来了解一下,为什么会出现服务调用串掉的情况,也就是说要简单的了解一下服务暴露于发现的过程。

当然其实这个问题如果要细讲,本篇幅肯定是不够的,所以这里是大概讲一下关键的地方。

首先我们假设接下来讲的服务暴露与发现都是远程暴露,当然,本地暴露也就不存在服务隔离的问题了(其实本地暴露是解决测试环境中服务隔离最有效的办法,后面有会细讲)

既然是远程暴露,那么自然是会向相应的注册中心去注册服务,由于我们目前使用的都是zookeeper注册中心,后文我也就以此注册中心为例(其实是我真的没用过其他的注册中心)。这个过程其实是比较复杂的,但是我们关心的其实是一点,就是服务暴露的时候,是以什么作为和其他服务区分的标识,也就是说服务的调用方在找服务的时候,是通过哪些因素去检索到想要调用的服务的。理解了这一点,我们也就能找到解决服务隔离的思路了。

那其实在通过查看源码的过程中我们可以得知(这步就省略了,网上分析相关源码的文章还蛮多的),一般来说,可以区分的标识常用的也就是那几个:URL地址,接口名称,方法名称,组号,版本号。前面三个都很好理解了,就算是通过rest调用,我们也得确认这三个因素才能找到正确的方法。那后面两个也就是通常我们用来解决服务隔离采取的直接手段,可以说是非常常用了,但其实我们通过阅读官方文档可以得知,这两个并不是用来做服务隔离使用的,这里我就直接引用dubbo官方文档中对这两个属性的说明来介绍了。

服务分组
当一个接口有多种实现时,可以用 group 区分。

服务
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
引用
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />
任意组 1:

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

在低压力时间段,先升级一半提供者为新版本
再将所有消费者升级为新版本
然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
如果不需要区分版本,可以按照以下的方式配置 1:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

所以可以看见,组号的作用是接口有多个实现的使用用于区分的,而版本号,则是用于同一个服务的版本升级的。但是由于他们是作为服务发现者去检索服务时候的条件了,所以这两个参数可以完成服务隔离的功能(其实多版本已经算是服务隔离了)。但是我们要明确的是,这两个参数,不是为服务隔离而用的,就比如如果想通过group来实现服务隔离,很简单的做法就是给service与reference都加上相同的group。但是如果这个时候,该service又加了一种实现,那group就应该起到它本来的作用,而这个时候又要服务隔离,那不就很凉,当然可以选择再加版本号,emmmmmmmm,那就当我啥也没说吧。

解决方案

既然我们大概知道了服务暴露的过程,也就明白了为什么服务调用会串,那我们就沿着这个思路,来想一下如何解决这个服务隔离的问题。

错误的方法

有效的方案

<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
    <!-- 向中文站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="chinaRegistry" />
    <!-- 向国际站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" registry="intlRegistry" />
</beans>

以上代码也是摘抄自dubbo的官方文档中对于多注册中心的介绍,这里我们作为服务提供者,其实这样添加就可以了。但是服务的调用者,也还是需要进行一些修改,也就是多注册中心的引用,下面也摘抄一下官方文档中对这一块的说法。

如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
   <dubbo:application name="world"  />
   <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 -->
   <dubbo:registry address="10.20.141.150:9090|10.20.154.177:9010" />
   <!-- 引用服务 -->
   <dubbo:reference id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" />
</beans>```

这里也是有一个大坑,因为文档中这样写,其实比较简单,通过文档我们只知道如果是多个注册中心,使用|区分,如果是注册中心的集群,使用,区分,但在实际使用场景中,也遇见了一些坑,比如下面这种配置

<dubbo:registry address="zookeeper://10.20.141.150:9090?backup=10.20.141.151,10.20.141.152"/>

这个是dubbo集群配置的方式,那文档中说用逗号区分,我一开始还以为就是把文档中的竖号直接换成逗号呢。那如果要在这个集群上,增加一个注册中心,就是下面这种配置

<dubbo:registry address="zookeeper://10.20.141.150:9090?backup=10.20.141.151,10.20.141.152|zookeeper://10.20.141.153:9090"/>

看上去增加的这个地址好像跟上面的也差不多,其实是的,只不过这个注册中心没有配置到前面的集群中而已,那关于zookeeper的集群,这里也不展开讲了(其实是我还没有细看过)

还有一点,文档中配置的方式,并没有zookeeper,其实是可以不配置的,如果不配置的话,dubbo默认会使用dubbo注册中心。那是种啥注册中心?没用过,并不了解,有兴趣的可以去研究一下。

这里也是简单的摘抄一下dubbo的官网上关于路由规则的一些简单介绍

condition:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填
0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填
com.foo.BarService 表示只对指定服务生效,必填
group=foo 对指定服务的指定group生效,不填表示对未配置group的指定服务生效
version=1.0对指定服务的指定version生效,不填表示对未配置version的指定服务生效
category=routers 表示该数据为动态配置类型,必填
dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填
enabled=true 覆盖规则是否生效,可不填,缺省生效。
force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 flase。
runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 flase。
priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0。
rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11") 表示路由规则的内容,必填
这里介绍的这些规则是添加代码的时候用得上的,看着上面有很多的必填,看起来蛮复杂的,但其实在网站上添加,还是蛮简单的,我们要知道的也就是几条:1.给哪个接口添加路由;2.路由限制的IP;3.路由指向的IP。由第一条我们可以知道,如果要添加路由,就必须的一个接口一个接口的添加,所以如果要给整体实现服务隔离,工作量会比较大;第二条和第三条是实际的condition,对于这个condition,也有一些具体的规则

=> 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
=> 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
如果匹配条件为空,表示对所有消费方应用,如:=> host != 10.20.153.11
如果过滤条件为空,表示禁止访问,如:host = 10.20.153.10 =>

根据上面的介绍,我们可以直接,比如要设置白名单,也就是我只想指定的服务器能访问到我这台机子,可以这样配置

 host != 10.20.153.10,10.20.153.11 =>
image.png

如果我们不想要某台机子访问我们的服务器,可以指定黑名单

 host = 10.20.153.10,10.20.153.11 =>
image.png

我们想直接排除掉一台服务提供者,也就是说希望所有调用者都不会调用到那台机子上,可以这样配置

 => host != 172.22.3.91
image.png

根据以上的规则,我们可以根据具体使用场景来配置,以达到我们服务隔离的目的。再次要强调的是,由于dubbo目前只支持单服务的路由,所以如果要做整体项目的服务隔离,得要一个接口一个接口的设置,工作量还是比较大的,但效果也是肥肠显著的,而且不会对调用方有什么影响,并且可以比较方便的对路由规则进行修改。

象征性的总结

其实以上的内容,很多都可以在dubbo的官网上找到,而我也多次提到,是因为这里面确实讲了很多关于dubbo很实用的使用手段。当然,光看使用手册是不够的,还是要结合源码,使用手册只会告诉我们怎么用,如果没有遇见实际的问题,我们可能永远用不上,很快就会忘了;但是如果再看了源码,我们就能知道为什么这样可以,那自然当出现相关的问题的时候,我们就能直接想到该如何解决,而如何解决是不对的,不好的。

长路漫漫~~

上一篇下一篇

猜你喜欢

热点阅读