Bootstrap

分布式专题-分布式服务治理01-揭开Dubbo的神秘面纱

前言

在传统的远程调用,比如RMI、HTTP协议、WebService等确实能够满足远程调用关系,但是随着用户量的倍增以及系统的复杂性增加,传统的远程调用却满足不了服务治理的需求:

  • 地址维护
  • 负载均衡
  • 限流/容错/降级
  • 监控

所以在这一部分,我们引入Dubbo来实现服务治理,我们从三方面讲Dubbo这个RPC远程调用框架。

本节我们讲第一个部分:解开Dubbo的神秘面纱

架构的发展

传统互联网架构
还记得阿里最初的项目是什么样的结构么?就是LAMP(即,Linux+Apache+MySQL+PHP),Tomcat作为web容器,放置的单体项目包括整套业务的所有逻辑,用户服务,订单服务,支付服务等~
在这里插入图片描述
分布式架构的演进
如今互联网比较完善的体系就是将业务分离,服务分离,数据库主从设计,读写分离。
在这里插入图片描述

带来哪些问题

(1)当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。

此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。

并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

(2)当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。

这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。

(3)服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?

为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。
其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。

Dubbo的架构

dubbo架构图
老生常谈,随手掏来一个经典的dubbo架构图,图示上已经写的很清楚了,一共有0~5六个部分:

  1. start

provider服务提供者:服务启动

  1. register

provider服务提供者然后注册register服务

  1. subscribe

Consumer服务消费者:消息订阅subscribe、

  1. notify

注册中心会将这些服务通过notify到消费者

  1. invoke

invoke这条实线按照图上的说明当然同步的意思了
服务消费者随机调用一个服务地址,失败重试另一个地址

  1. count

这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor。Monitor在整个架构中是可选的,Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用。

Dubbo 案例演示

接下来,我们简单的使用一下dubbo~

  • 服务端

我这里使用server-api和server-provider作为服务端两个模块
在这里插入图片描述

  • server-api:服务调用接口
  • server-provider:服务提供者

在server-api中,就是空实现,用来调用server-provider:
接口1:

public interface IGpHello {

    String sayHello(String msg);
}

接口2:

public interface IDemoService {

    String protocolDemo(String msg);
}

真正的实现就是在server-provider中实现:
接口1实现:

public class GpHelloImpl implements IGpHello{

    @Override
    public String sayHello(String msg) {
        return "Hello:"+msg;
    }
}

接口2实现:

public class GpHelloImpl2 implements IGpHello{

    @Override
    public String sayHello(String msg) {
        return "Hello,i'm server 2:"+msg;
    }
}

在dubbo-server的pom文件引用dubbo的依赖,zookeeper等依赖,然后创建配置文件dubbo-server.xml,用来声明服务地址:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!--提供方信息-->
    <dubbo:application name="dubbo-server" owner="mic"/>

    <!--注册中心-->
    <dubbo:registry id="zk1" address="zookeeper://192.168.200.111:2181"/>

    <dubbo:registry id="zk2" address="zookeeper://192.168.200.112:2181"/>

    <dubbo:protocol port="20880" name="dubbo"/>

    <dubbo:protocol port="8080" name="hessian"/>

    <dubbo:service interface="com.test.dubbo.IGpHello"
                   ref="gpHelloService" protocol="dubbo,hessian" registry="zk1"/>

    <dubbo:service interface="com.test.dubbo.IDemoService"
                   ref="demoService" protocol="hessian"/>

    <bean id="gpHelloService" class="com.test.dubbo.GpHelloImpl"/>

    <bean id="demoService" class="com.test.dubbo.DemoService"/>
</beans>

服务端调用就加载这个配置文件:

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context=
                new ClassPathXmlApplicationContext
                        ("META-INF/spring/dubbo-server.xml");
        context.start();

        System.in.read(); //阻塞当前进程
    }
  • 客户端

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!--提供方信息-->
    <dubbo:application name="dubbo-client" owner="mic"/>

    <!--注册中心-->
    <dubbo:registry address="zookeeper://192.168.200.111:2181?register=false" check="false" file="d:/dubbo-server"/>


    <dubbo:reference id="gpHelloService"
                     interface="com.test.dubbo.IGpHello"
                     protocol="dubbo"/>


</beans>

启动:

    public static void main( String[] args ) throws IOException, InterruptedException {
        ClassPathXmlApplicationContext context=new
                ClassPathXmlApplicationContext
                ("dubbo-client.xml");

      /*  Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).
                getExtension("defineProtocol");
        System.out.println(protocol.getDefaultPort());*/

           //得到IGpHello的远程代理对象
            IGpHello iGpHello = (IGpHello) context.getBean("gpHelloService");

            System.out.println(iGpHello.sayHello("Mic"));
            Thread.sleep(4000);

        System.in.read();
    }

在这里插入图片描述
dubbo节点分析
上面的demo中,使用dubbo通信成功,我们注意到,此时,dubbo在注册中心创建了“dubbo”的节点
在这里插入图片描述
在这里插入图片描述
关于dubbo的缓存
注意到:在客户端其实是有缓存的概念的,这样不一定就让client每次都请求到zookeeper
在这里插入图片描述
运行后,我们在本地磁盘找到了的缓存文件
在这里插入图片描述
实际上,在dubbo的源码里,通过定时任务去跑数据,更新缓存,这点我们在后面的dubbo源码分析章节会讲到

服务的启动过程
我们也可以通过dubbo提供main方法启动容器

    public static void main(String[] args) throws IOException {

        //默认情况下会使用spring容器来启动服务
        com.alibaba.dubbo.container.Main.main(
                new String[]{"spring","log4j"});
    }

看看源码:

    public static void main(String[] args) {
        try {
            if (args == null || args.length == 0) {
                String config = ConfigUtils.getProperty("dubbo.container", loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }

            final List<Container> containers = new ArrayList();

            for(int i = 0; i < args.length; ++i) {
                containers.add(loader.getExtension(args[i]));
            }

            logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
            if ("true".equals(System.getProperty("dubbo.shutdown.hook"))) {
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        Iterator i$ = containers.iterator();

                        while(i$.hasNext()) {
                            Container container = (Container)i$.next();

                            try {
                                container.stop();
                                Main.logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
                            } catch (Throwable var6) {
                                Main.logger.error(var6.getMessage(), var6);
                            }

                            Class var3 = Main.class;
                            synchronized(Main.class) {
                                Main.running = false;
                                Main.class.notify();
                            }
                        }

                    }
                });
            }

            Iterator i$ = containers.iterator();

            while(i$.hasNext()) {
                Container container = (Container)i$.next();
                container.start();
                logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
            }

            System.out.println((new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]")).format(new Date()) + " Dubbo service server started!");
        } catch (RuntimeException var7) {
            var7.printStackTrace();
            logger.error(var7.getMessage(), var7);
            System.exit(1);
        }

        Class var9 = Main.class;
        synchronized(Main.class) {
            while(running) {
                try {
                    Main.class.wait();
                } catch (Throwable var5) {
                    ;
                }
            }

        }
    }

说明dubbo支持多容器启动

dubbo支持多协议

  • RMI
  • hessian
  • webservice
  • http
  • thirft
  • Dubbo(默认)

优势:
1、不需要修改原本的服务的情况下,方便协议的迁移
2、通过增加相应协议的jar包,快速发布

dubbo支持多注册中心

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!--提供方信息-->
    <dubbo:application name="dubbo-server" owner="mic"/>

    <!--注册中心-->
    <dubbo:registry id="zk1" address="zookeeper://192.168.200.111:2181"/>

    <dubbo:registry id="zk2" address="zookeeper://192.168.200.112:2181"/>

    <dubbo:protocol port="20880" name="dubbo"/>

    <dubbo:protocol port="8080" name="hessian"/>

    <dubbo:service interface="com.test.dubbo.IGpHello"
                   ref="gpHelloService" protocol="dubbo,hessian" registry="zk1"/>

    <dubbo:service interface="com.test.dubbo.IDemoService"
                   ref="demoService" protocol="hessian"/>

    <bean id="gpHelloService" class="com.test.dubbo.GpHelloImpl"/>

    <bean id="demoService" class="com.test.dubbo.DemoService"/>
</beans>

dubbo基于集群访问
对dubbo-server做负载均衡
在这里插入图片描述

后记

本节具体代码详见:
dubbo-demo

;