美文网首页
关于dubbo多服务环境中的服务隔离的总结

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

作者: 素墨月羽 | 来源:发表于2018-05-31 21:23 被阅读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服务集群中的负载均衡的方案的一种。


      image.png

      在我们的dubbo admin管理平台上,方法的提供者IP上都会有这个数值,一般默认是100,而我们在dubbo的配置中,如果没有选择负载均衡的方案的话,dubbo会默认给我们选择按权平均,所以我们可以通过修改dubbo admin上的权值,来控制各个服务提供实现者被调用的概率,这里注意,只能修改被调用的概率,是完全实现服务隔离的。理由也很简单了,既然是按权平均,也就是说无论怎么设置,每个服务都有被调用到的可能性,尤其有些同学可能不是很懂这块,把权值的概念理解为优先级,以为值越高,优先级越高,所以当出现服务调用错误的时候,会修改其中一个的权值,来保证服务能被正确调用,当正巧再次测试的时候,真的就调用正确了,就理所当然的认为这是一种解决方案。

    • 本地暴露
      这一个就是我自己的错误认知了,在不了解本地暴露之前,我以为本地暴露是指在本机暴露一个服务,然后可以由其他的项目调用。后来在研究源码的时候,发现其实本地服务的意思是在同一个项目内,起一个服务,同时可以调用这个服务,也难怪dubbo官方文档中对本地描述中也有讲说如果不设置调用是远程服务还是本地服务,那么这两者都会暴露出来,而且调用者会优先调用本地服务。

    有效的方案

    • 直接提供者
      直连提供者是dubbo提供的一种方案,在dubbo的官网上有相关介绍,这里并不想过多展开,是因为官网并不支持这样做,也明确提出只允许在测试环境这样做(其实也比较好理解,真的要直连提供者了,还搞什么分布式,直接一台服务器跑好了)
      这里也就放一下XML配置的方式,还可以通过JVM和文件映射的方式,我觉得也是吃了控,就不要去尝试使用了
    <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
    
    • 多注册中心
      多注册中心是我们目前使用的一种解决方案,选择这种方式的出发点是为了避免友军调用服务时,过多的增加组号和版本号,增加麻烦。是的,目前我们通常仍然是选择使用组号和版本号来实现服务隔离,毕竟这样实现简单,易于理解。但是问题也很大,如果是一个依赖方较多的服务,一旦修改了版本号,将是毁灭性的,其他依赖方都得跟着改。我们也就是出于这种目的,并不想麻烦友军,于是麻烦麻烦自己,把我们对外得服务都暴露在一台指定的注册机上,这台注册机是由我们组内维护的,我们可以根据需要每个服务器都加一个注册机,这样通过不同的注册机来实现服务隔离(其实也就是上面提到的几个检索条件中,最直接的条件,URL)。而这样实现的技术依赖,也是因为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 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 admin管理网站上是提供了增加路由的手段的,当然,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很实用的使用手段。当然,光看使用手册是不够的,还是要结合源码,使用手册只会告诉我们怎么用,如果没有遇见实际的问题,我们可能永远用不上,很快就会忘了;但是如果再看了源码,我们就能知道为什么这样可以,那自然当出现相关的问题的时候,我们就能直接想到该如何解决,而如何解决是不对的,不好的。

    长路漫漫~~

    相关文章

      网友评论

          本文标题:关于dubbo多服务环境中的服务隔离的总结

          本文链接:https://www.haomeiwen.com/subject/bgrcsftx.html