聊一聊Dubbo的一些特性和原理

Dubbo的基础知识详见我上一篇文章--《掀起Dubbo的盖头来》

Dubbo多版本支持

  如果我们想要对之前某个服务进行升级,那么久要考虑一个版本支持的问题。就拿我之前写过的LusheHelloImpl进行一个升级,类名为LusheHelloImpl2,代码如下

public class LusheHelloImpl2 implements ILusheHello {
    @Override
    public String sayHello(String msg) {
        return "hello version2 " + msg;
    }
}

  然后再dubbo-server.xml文件中进行版本号的署名

    <dubbo:service interface="lushe.study.dubbo.ILusheHello"
                   ref="lusheHelloService" protocol="dubbo,hessian" version="0.0.1"/>

    <dubbo:service interface="lushe.study.dubbo.ILusheHello"
                   ref="lusheHelloService2" protocol="dubbo,hessian" version="0.0.2"/>

    <bean id="lusheHelloService" class="lushe.study.dubbo.LusheHelloImpl"/>

    <bean id="lusheHelloService2" class="lushe.study.dubbo.LusheHelloImpl2"/>

  启动dubbo之后,就需要在客户端相关配置中注明版本号

<!--配置Dubbo注册中心-->
<dubbo:registry id="zk1" address="zookeeper://10.10.101.104:2181"/>

<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:reference id="lusheHelloService" interface="lushe.study.dubbo.ILusheHello"
registry="zk1" version="0.0.1"/>

  然后这是版本为0.0.1的结果

2018-07-20 17:11:40,204 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Refer dubbo service lushe.study.dubbo.ILusheHello from url zookeeper://10.10.101.104:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=dubbo-client&check=false&dubbo=2.5.3&interface=lushe.study.dubbo.ILusheHello&methods=sayHello&owner=Lushe&pid=16111&revision=1.0-SNAPSHOT&side=consumer&timestamp=1532077897660&version=0.0.1, dubbo version: 2.5.3, current host: 192.168.43.238
hellolushe
2018-07-20 17:11:40,394 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Run shutdown hook now., dubbo version: 2.5.3, current host: 192.168.43.238
2018-07-20 17:11:40,394 INFO [com.alibaba.dubbo.registry.support.AbstractRegistryFactory] - [DUBBO] Close all registries [zookeeper://10.10.101.104:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-client&dubbo=2.5.3&interface=com.alibaba.dubbo.registry.RegistryService&owner=Lushe&pid=16111&timestamp=1532077897729], dubbo version: 2.5.3, current host: 192.168.43.238

  这是版本为0.0.2的结果

2018-07-20 17:13:05,653 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Refer dubbo service lushe.study.dubbo.ILusheHello from url zookeeper://10.10.101.104:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=dubbo-client&check=false&dubbo=2.5.3&interface=lushe.study.dubbo.ILusheHello&methods=sayHello&owner=Lushe&pid=16115&revision=1.0-SNAPSHOT&side=consumer&timestamp=1532077982958&version=0.0.2, dubbo version: 2.5.3, current host: 192.168.43.238
hello version2 lushe
2018-07-20 17:13:05,770 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Run shutdown hook now., dubbo version: 2.5.3, current host: 192.168.43.238
2018-07-20 17:13:05,771 INFO [com.alibaba.dubbo.registry.support.AbstractRegistryFactory] - [DUBBO] Close all registries [zookeeper://10.10.101.104:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-client&dubbo=2.5.3&interface=com.alibaba.dubbo.registry.RegistryService&owner=Lushe&pid=16115&timestamp=1532077983136], dubbo version: 2.5.3, current host: 192.168.43.238

  我们去Zookeeper的服务器上,可以看到它发布的服务的信息也发生了变化

[zk: localhost:2181(CONNECTED) 6] ls /dubbo/lushe.study.dubbo.ILusheHello/providers
[dubbo%3A%2F%2F192.168.43.238%3A20880%2Flushe.study.dubbo.ILusheHello%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dlushe.study.dubbo.ILusheHello%26methods%3DsayHello%26owner%3DLushe%26pid%3D16100%26revision%3D0.0.1%26side%3Dprovider%26timestamp%3D1532077715180%26version%3D0.0.1, 
dubbo%3A%2F%2F192.168.43.238%3A20880%2Flushe.study.dubbo.ILusheHello2%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dlushe.study.dubbo.ILusheHello%26methods%3DsayHello%26owner%3DLushe%26pid%3D16100%26revision%3D0.0.2%26side%3Dprovider%26timestamp%3D1532077719569%26version%3D0.0.2, 
hessian%3A%2F%2F192.168.43.238%3A8080%2Flushe.study.dubbo.ILusheHello2%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dlushe.study.dubbo.ILusheHello%26methods%3DsayHello%26owner%3DLushe%26pid%3D16100%26revision%3D0.0.2%26side%3Dprovider%26timestamp%3D1532077721126%26version%3D0.0.2,
 hessian%3A%2F%2F192.168.43.238%3A8080%2Flushe.study.dubbo.ILusheHello%3Fanyhost%3Dtrue%26application%3Ddubbo-server%26dubbo%3D2.5.3%26interface%3Dlushe.study.dubbo.ILusheHello%26methods%3DsayHello%26owner%3DLushe%26pid%3D16100%26revision%3D0.0.1%26side%3Dprovider%26timestamp%3D1532077717787%26version%3D0.0.1]

主机绑定

  在发布一个Dubbo服务的时候,会生成一个dubbo://ip:port的协议地址,那么这个IP是根据什么生成的呢?我们可以在Dubbo源码下Config包中ServiceConfig.java代码中找到如下代码;可以发现,在生成绑定的主机的时候,会通过一层一层的判断,直到获取到合法的ip地址。

  (1)首先,Dubbo会从配置文件中尝试去获得host

NetUtils.isInvalidLocalHost(host)

  (2)如果host为空或者为非法,那么就从它本地地址网卡中去拿到

host = InetAddress.getLocalHost().getHostAddress();

  (3)如果还是没有拿到,那么就会取判断注册中心的地址是否为空,不为空则循环拿到每个地址去循环遍历,简历Socket去连接,然后根据Socket去获得本地地址。

if (NetUtils.isInvalidLocalHost(host)) {
                if (registryURLs != null && registryURLs.size() > 0) {
                    Iterator i$ = registryURLs.iterator();

                    while(i$.hasNext()) {
                        URL registryURL = (URL)i$.next();

                        try {
                            Socket socket = new Socket();

                            try {
                                SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                socket.connect(addr, 1000);
                                host = socket.getLocalAddress().getHostAddress();
                                break;
                            } finally {
                                try {
                                    socket.close();
                                } catch (Throwable var26) {
                                    ;
                                }

                            }
                        } catch (Exception var29) {
                            logger.warn(var29.getMessage(), var29);
                        }
                    }
                }

  (4)如果还是拿不到。。那么就会遍历本地网卡,返回一个合理的IP地址

if (NetUtils.isInvalidLocalHost(host)) {
                    host = NetUtils.getLocalHost();
                }

public static String getLocalHost() {
        InetAddress address = getLocalAddress();
        return address == null ? "127.0.0.1" : address.getHostAddress();
    }

集群容错

  什么是容错机制? 容错机制指的是某种系统控制在一定范围内的一种允许或包容犯错情况的发生,举个简单例子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结束还是继续等待,然后根据我们的选择执行对应的操作,这就是“容错”。
  在分布式架构下,网络、硬件、应用都可能发生故障,由于各个服务之间可能存在依赖关系,如果一条链路中的其中一个节点出现故障,将会导致雪崩效应。为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果

  Dubbo提供了6种容错机制,分别如下
  (1)failsafe 失败安全,可以认为是把错误吞掉(记录日志)
  (2)failover(默认) 重试其他服务器; retries(2)
  (3)failfast 快速失败, 失败以后立马报错
  (4)failback 失败后自动恢复。
  (5)forking forks. 设置并行数
  (6)broadcast 广播,任意一台报错,则执行的方法报错

  我们可以在配置文件中对某个服务进行容错配置

<dubbo:service interface="lushe.study.dubbo.ILusheHello"
                   ref="lusheHelloService2" protocol="dubbo,hessian" version="0.0.2" cluster="failsafe"/>

服务降级

  降级的目的是为了保证核心服务可用。
  降级可以有几个层面的分类: 自动降级和人工降级; 按照功能可以分为:读服务降级和写服务降级;
  (1)对一些非核心服务进行人工降级,在大促之前通过降级开关关闭哪些推荐内容、评价等对主流程没有影响的功能
  (2)故障降级,比如调用的远程服务挂了,网络故障、或者RPC服务返回异常。 那么可以直接降级,降级的方案比如设置默认值、采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
  (3)限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能会导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阀值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页(活动太火爆,稍后重试等)

  dubbo的降级方式: Mock
实现步骤
  (1)在client端创建一个TestMock类,实现对应IGpHello的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾
  (2)在client端的xml配置文件中,添加如下配置,增加一个mock属性指向创建的TestMock
  (3)模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到TestMock这个类。当服务端故障解除以后,调用过程将恢复正常

配置优先级别

  以timeout为例,显示了配置的查找顺序,其它retries, loadbalance等类似。
  (1)方法级优先,接口级次之,全局配置再次之。
  (2)如果级别一样,则消费方优先,提供方次之。
  其中,服务提供方配置,通过URL经由注册中心传递给消费方。
  建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置。

SPI

  SPI(service provider interface)是贯穿整个Dubbo的核心机制。简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用方来说,只需要集成对应厂商的插件,既可以完成对应规范的实现机制。形成一种插拔式的扩展手段。

  首先我们先来了解一下Java的SPI

SPI规范总结
  实现SPI,就需要按照SPI本身定义的规范来进行配置,SPI规范如下
  1. 需要在classpath下创建一个目录,该目录命名必须是:META-INF/services
  2. 在该目录下创建一个properties文件,该文件需要满足以下几个条件
    a) 文件名必须是扩展的接口的全路径名称
    b) 文件内部描述的是该扩展接口的所有实现类
    c) 文件的编码格式是UTF-8
  3. 通过java.util.ServiceLoader的加载机制来发现
SPI的实际应用
  SPI在很多地方有应用,大家可以看看最常用的java.sql.Driver驱动。JDK官方提供了java.sql.Driver这个驱动扩展点,但是你们并没有看到JDK中有对应的Driver实现。 那在哪里实现呢?
  以连接Mysql为例,我们需要添加mysql-connector-java依赖。然后,你们可以在这个jar包中找到SPI的配置信息。所以java.sql.Driver由各个数据库厂商自行实现。这就是SPI的实际应用。当然除了这个意外,大家在spring的包中也可以看到相应的痕迹
SPI的缺点
  1. JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源
  2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因

Dubbo的SPI机制规范
  大部分的思想都是和SPI是一样,只是下面两个地方有差异。
  1. 需要在resource目录下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,并基于SPI接口去创建一个文件
  2. 文件名称和接口名称保持一致,文件内容和SPI有差异,内容是KEY对应Value

 

posted @ 2018-07-22 16:39  jolivan  阅读(723)  评论(0编辑  收藏  举报