月泉的博客

Dubbo(一)入门

月泉 Dubbo

不多说,大纲走一波

  • Dubbo 是什么?
  • 如何使用?
  • 注册中心
  • 多协议支持
  • 多版本支持
  • 启动检查
  • 集群容错
  • 降级

Dubbo 是什么?

我要直接说《分布式服务治理框架》讲道理没了解过的小伙伴应该是直接一脸懵逼,那么就从其主要想解决什么问题开始吧,首先说说历史 从经典的单体架构说起,通常这样是没有什么问题的,直到现在都很常用,然而随着用户量的增加,扩展性能也很方便,加服务器可以了,但随着用户越来越多,增加服务器的成本也大大的提高了,而且很多地方也容易造成性能的浪费,随着越加越多,渐渐的带来的提升也不是那么的明显了,开发维护起来也非常臃肿,于是就想把它拆分,例如这里我将它水平拆分,将所有的系统都拆分成单个一体的服务,这样利于开发维护和做技术积累,在扩展性能的时候也可以有针对的扩展

在我们水平拆分后,例如调用下订单这个服务,服务之间的交互是这样的,如上图,那么在这个通信的过程会遇见什么问题?

  1. 地址的维护,随着机器越来越多,各个服务部署在不同的机器上,他们地址也不一样
  2. 负载均衡
  3. 容错
  4. 降级
  5. 服务之间存在不同协议的通信
  6. 服务之间调用的监控
  7. 服务之间依赖关系,如那些服务依赖于那些服务,比如那些服务必须要等待依赖的服务先启动自己才能够启动
  8. ….

所谓的治理我个人理解起来就是:解决与管理

在 Dubbo 的架构中有 4 中角色

该图借鉴至 Dubbo 官方文档

Provider

服务提供者,提供服务给予消费者消费

Registry

注册中心,提供者将服务注册至注册中心,消费者向注册中心订阅,一旦服务发生变动都会通知消费者,其实就是服务的发现与注册

** Consumer **

服务消费者,没什么好说的

** Monitor **

监视者,监控服务的调用次数与调用时间

如何使用?

首先我创建 2 个项目,一个分别叫做dubbo-server-demo一个叫做dubbo-client-demo,分别都使用Maven来构建

接着在server-api模块里创建一个对外提供服务的接口TestService

package org.yuequan.dubbo.demo;

public interface TestService {
    String sayHello(String msg);
}

接着在server-provider中对该接口实现,当然也要添加对server-api的依赖

<dependency>
    <groupId>org.yuequan.dubbo.demo</groupId>
    <artifactId>server-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>org.yuequan.dubbo.demo</groupId>
    <artifactId>server-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

随后对该接口进行实现

package org.yuequan.dubbo.demo;

public class TestServiceImpl implements TestService {
    @Override
    public String sayHello(String msg) {
        return "Hi,Customer:" + msg;
    }
}

首先引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.10</version>
</dependency>

接着在/META-INF/dubbo/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-4.3.xsd        http://code.alibabatech.com/schema/dubbo      http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="dubbo-server" owner="yuequan" />
    <dubbo:registry address="N/A" />
    <dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" />
    <bean class="org.yuequan.dubbo.demo.TestServiceImpl" name="testService" ></bean>
</beans>

接着我们来写一个main函数用于启动它,有 2 种方式一种是使用Spring容器启动方式,还有一种是使用 Dubbo 的Main.main启动,这里我暂时先用Spring容器启动

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

接着启动应用,可以看到日志的输出 同时也看到服务发布成功了

接着来dubbo-client-demo,首先引入dubbo依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>dubbo</artifactId>
  <version>2.5.10</version>
</dependency>

接着写配置文件META-INF/dubbo/dubbo-client.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-4.3.xsd        http://code.alibabatech.com/schema/dubbo      http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="dubbo-client" owner="yuequan" />
    <dubbo:registry address="N/A" />
	  <dubbo:reference interface="org.yuequan.dubbo.demo.TestService" url="dubbo://192.168.10.102:20880/org.yuequan.dubbo.demo.TestService" />
</beans>

还记得控制台日志输出的地址吗,这里就是了,现在暂时不用注册中心,先来一个最简单的点对点通信,接着我们来写客户端的启动类,通信协议默认是使用的dubbo其默认端口就是 20880

public class App
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
        TestService service = (TestService) context.getBean("testService");
        System.out.println(service.sayHello("YueQuan"));
    }
}

接着启动它 至此,一个简单的使用例子就写好了,接着进入下一个小节

注册中心

上一个小节的例子,我们使用点对点通信的例子,接着我们来配置注册中心,dubbo默认支持能够用来做注册中心的有

  • Multicast
  • Zookeeper
  • Redis
  • Simple 这里官方推荐使用 Zookeeper,不会 Zookeeper 可以看我之前写的文章,首先来修改dubbo-server.xml注册中心配置
<!-- N/A 代表着没有,不使用的意思 -->
<dubbo:registry address="N/A" />
<!-- 修改成,当然你要启动ZooKeeper -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

接着引入 ZooKeeper 依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.12</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

接着启动 发现日志什么都没有输出,接着配置下日志log4j.properties

log4j.rootLogger=INFO, stdout

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

接着重启,可以看到控制台有日志输出了

接着看看来看看 Zookeeper 这边

// 运行客户端
./zkCli.sh

输入ls /

可以看到下面多了一个 dubbo 节点,接着来看下 dubbo 节点下的内容:ls /dubbo

可以看到接口已经被注册上来了,接着我们看看这个接口下的内容:

ls /dubbo/org.yuequan.dubbo.demo.TestService

可以看到下面现在就 2 个节点,我们来看看现在有多少个 providers:

ls /dubbo/org.yuequan.dubbo.demo.TestService/providers

可以看到已经注册上来了

接着来配置下dubbo-client-demo,首先引入依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.12</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

然后修改配置dubbo-client.xml

<!-- 修改前 -->
<dubbo:registry address="N/A" />
<!-- 修改后 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" url="dubbo://192.168.10.102:20880/org.yuequan.dubbo.demo.TestService" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" />

接着启动,可以看到日志输出

接着我们再来看下 Zookeeper:

ls /dubbo/org.yuequan.dubbo.demo.TestService

可以看见多了 2 个节点分别为routersconsumers这时候输入

ls /dubbo/org.yuequan.dubbo.demo.TestService/comsumers

会发现返回的是一个空数组,这是因为客户端的消费已经结束了,所以该下的节点也会被清除,这一特性可以很好的利用 Zookeeper 的临时节点来实现,实际上它也是这样做的,接着我们来修改下客户端的代码让他不那么快结束

public class App
{
    public static void main( String[] args ) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
        TestService service = (TestService) context.getBean("testService");
        System.out.println(service.sayHello("YueQuan"));
        Thread.sleep(10000);
    }
}

启动后,再来看看

ls /dubbo/org.yuequan.dubbo.demo.TestService/comsumers

可以看到消费者信息,这是单个配置注册中心,那如果有多个呢?配置多注册中心也非常简单,如下:

<dubbo:registry protocol="zookeeper" address="192.168.1.1:2181,192.16.1.2:2181,192.168.1.3:2181" />
<!-- OR -->
<dubbo:registry address="zookeeper://192.168.1.1:2181?backup=192.16.1.2:2181,192.168.1.3:2181" />
<!-- 更多可以看官方文档~~~ -->

多协议

我们一个服务可能之前是使用的dubbo协议但后面又改成了hessian协议,也有可能一个服务提供了dubbo协议也提供了hessian协议,但也有的客户端它只使用一种协议的通信方式,在多协议的情况下,Dubbo框架也做了很好的实现接着修改dubbo-server.xml文件,这里我将会使用hessiandubbo2 个协议,所以需要先引入hessian的依赖,这里需要使用到容器所以还需要引入servlet-api和一个servlet容器,那这里我就使用jetty,添加的依赖如下

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty</artifactId>
    <version>6.1.26</version>
</dependency>
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.51</version>
</dependency>

接着修改配置

	<dubbo:protocol name="dubbo" port="20880" />
	<dubbo:protocol name="hessian" port="8011" />
	<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo,hessian" />

接着启动,这样就配置好了,但是这样看不到明显的效果啊?我怎么知道我客户端再未指定协议的情况下消费的是hessian协议还是dubbo协议?于是为了演示可以这样做将TestServiceImpl在复制一份,修改下其中内容

public class TestService2Impl implements TestService {
    @Override
    public String sayHello(String msg) {
        return "Hi,Customer:" + msg + ",through Hessian protocol";
    }
}

然后再调整下配置

<bean class="org.yuequan.dubbo.demo.TestServiceImpl" name="testService" ></bean>
<bean class="org.yuequan.dubbo.demo.TestService2Impl" name="testService2"></bean>
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian" />

接着启动,然后为了明显,再修改下客户端的调用代码并引入依赖,让它调用十次,可以看出这十次分别调用的那个

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.1</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>jetty</artifactId>
  <version>6.1.26</version>
</dependency>
<dependency>
  <groupId>com.caucho</groupId>
  <artifactId>hessian</artifactId>
  <version>4.0.51</version>
</dependency>

客户端代码修改如下

public class App
{
    public static void main( String[] args ) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/dubbo/dubbo-client.xml");
        TestService service = (TestService) context.getBean("testService");
        for (int i = 0; i < 10; i++) {
            System./out/.println(service.sayHello("YueQuan"));
        }

    }
}

接着启动查看控制台输出

Dubbo 默认的负载均衡机制是随机的,具体的会在后续文章细写

多版本支持

服务提供对外的接口可能会采取某种策略逐步平滑升级,例如原先使用 1.0.0 的接口,又对外提供了 2.0.0 处于刚刚开放,存在某些不稳定因素,可能想逐步开发,某些客户端继续使用 1.0.0,让一部分客户端先使用 2.0.0

接着我们修改dubbo-server.xml

<!-- 修改前 -->
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian"/>
<!-- 修改后 -->
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService" protocol="dubbo" version="1.0.0" />
<dubbo:service interface="org.yuequan.dubbo.demo.TestService" ref="testService2" protocol="hessian" version="2.0.0" />

对于 1.0.0 的接口访问使用dubbo协议,2.0.0 的接口访问使用hessian 然后修改客户端配置dubbo-client.xml

<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" />

然后运行,你会发现只会调用定义的1.0.0版本的服务,2.0.0同理

启动检查

在服务的启动过程中可能有依赖于其它服务,但其它服务并没有运行起来,这时候你先起起来了,必然会报错,更有意思的是如果是互相依赖,那互相都起不来怎么办呢?所以这里就要关闭启动检查,例如

<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" version="1.0.0" check="false"/>

集群容错

在集群调用中出现调用失败时,比如所需要调用的服务它一直未返回数据阻塞住了,直到超时等情况,对于调用失败 Dubbo 提供了几种容错方案,分别是

  1. Failover
  2. Failfast
  3. Failsafe
  4. Failback
  5. Forking
  6. Broadcast

** Failover **

默认就是它,失败后自动切换,当出现失败后,自动切换其它服务器,当然可以设置重试次数

** Failfast **

快速失败,失败后就立马报错

** Failfast **

安全失败,发生异常,立马吞掉,也就是直接忽略

** Failback **

失败后自动恢复

** Forking **

同时并行调用几个相同的服务,只要一个成功即返回,但可以设置最大并行数

** Broadcast **

广播调用所有提供者,挨个调用,只要有一个报错就报错

通常可以通过配置可以对容错的手段进行更改,如下:

<!-- 修改前 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference cluster="failsafe" id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />

这里我配置的failsafe所以消费者调用该服务的提供者就算出现异常也会被忽略掉,为了演示效果,我这里将超时时间修改为 50 毫秒

因为我这里是本机所以 50 毫秒也还是有执行成功的现象不过无所谓,看第一个null执行失败的异常并没有输出而是直接返回了 null

降级

降级我用一个比较笼统的说法来简单的阐述一下,降级实际上就是关闭或者减少其它服务所占用的资源来保证核心服务可用,就比如小米的饥饿营销或者在双 11 下单的时候经常会出现服务器繁忙,请稍后再试,再比如说在双 11 下单高峰的时候,为了集中资源,将平时首页给推荐的数据分析并推送给我的相关服务全部关掉,仅保留基本核心服务,等高峰期一过在把这些附加服务开放,实际上降级有很多种,比如:页面降级、读降级、写降级、超时降级、开关降级等等等,反正这里 get 到点就可以了,以后应该是有机会来单独说降级这个概念的。 对于服务降级 Dubbo 也是提供了相应的机制来让我们处理的。 就比如说一个接口超过了 50 毫秒的响应时间我们就对它进行降级,来保证它基本可用,来写个 demo 修改配置文件dubbo-client.xml

<!-- 修改前 -->
<dubbo:reference cluster="failsafe" id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" />
<!-- 修改后 -->
<dubbo:reference id="testService" interface="org.yuequan.dubbo.demo.TestService" check="false" version="1.0.0" mock="org.yuequan.dubbo.demo.TimeoutMock" timeout="50"/>

这里值得一提的是,可以发现我将cluster因为这里如果是使用的failsafe的话是不会降级的,而是直接走failsafe的机制的,接着运行,我们来看下控制台

可以看到调用超过了 50 毫秒的会直接降级,本节就先到这里。

如果你觉得作者写的这篇文章对你有帮助或者收获很大的话,你可以尝试着请我喝一瓶《东方树叶》或者吃一块《鸡胸》来激励我!!!

微信请客

支付宝请客

月泉
伪文艺中二青年,热爱技术,热爱生活。