一篇文章学会Dubbo的基本使用

535 阅读7分钟

一:dubbo是什么

随着项目的业务越来越复杂,为了提高项目的可维护性,可拓展性,我们需要将一个单体应用拆分为多个服务。而服务之间的通信我们需要使用到rpc技术,什么是rpc呢?rpc即远程过程调用,简单来说就是像调用本地方法一样调用远程服务,而dubbo就是一个当下非常热门而且优秀的rpc框架。

dubbo的总体架构:

architecture.png

1.provider:服务提供者

2.consumer:服务消费者

3.registry: 注册中心

4.container: dubbo服务容器

5.monitor: 监控中心

二:SpringBoot整合dubbo

1、引入依赖,以3.0.2.1版本为例,我们使用zk作为注册中心,所以还需要引入curator的依赖。zk的下载使用这里就省略了。

注:本文都是基于3.0.2.1版本,zk版本是3.7.0

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-x-discovery</artifactId>
            <version>4.2.0</version>
        </dependency>

2.提供一个api接口:

package com.example.api;

/**
 * @author 20136
 */
public interface HelloService {

    String hello(String msg);
}
  1. 服务端:

properties配置:

配置服务名,注册中心地址,协议以及端口号

dubbo.application.name=spring-boot-dubbo
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

api实现类:

@DubboService
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String msg) {
        return "hello " + msg;
    }
}

在启动类上加上EnableDubbo注解,并且指定需要扫描的包,用于扫描@DubboService注解

@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example")
public class DubboProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboProviderApplication.class, args);
    }

}

4.客户端

properties配置:

dubbo.application.name=dubbo-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo

启动类添加@EnableDubbo注解,配置需要扫描的包,用于扫描@DubboReference注解

package com.example.dubbo.consumer;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example")
public class DubboConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboConsumerApplication.class, args);
    }

}

消费端调用api

@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false)
    private HelloService helloService;

    @Test
    void test(){
        System.out.println(helloService.hello("world"));
    }
}

三:dubbo的负载均衡策略

通常为了服务的可用性,同一个服务我们会部署多份,避免单点故障问题。所以在服务调用的时候需要考虑到负载均衡的问题。dubbo提供了5中负载均衡策略。

1.random

随机策略,默认策略,实际上是考虑了权重之后的随机选择,如果每个服务提供者的权重都一致,那么就使用 java 的随机函数去选择一个.

2.roundrobin

轮询策略,轮询负载均衡策略,本质上也是考虑了权重之后的轮循

3.leastactive

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

4.consistenthash

一致性hash策略,根据参数做一致性hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

5.shortestresponse

最短响应时间策略,根据响应时间和当前服务的请求量去获得一个最优解。如果存在多个最优解,则考虑权重,如果仅有一个则权重无效。

指定负载均衡策略很简单,在@DubboReference注解上的loadbalance属性指定自己想要的策略即可。

@DubboReference(check = false,loadbalance = "random")

四:dubbo的集群容错策略

dubbo在调用的时候可能会出现错误,很多时候我们需要根据不同的业务做不同的处理,而dubbo为我们提供了8种策略。

1.failover

失败转移策略,默认的策略,如果调用失败了之后会根据我们配置的重试次数进行重试,默认会重试2次。

2.failfast

快速失败策略,如果调用服务出现错误,会立即抛出异常。

3.failsafe

失败安全策略,如果出现调用出现异常会把异常捕捉。然后继续执行业务代码

4.failback

失败自动恢复策略,调用服务失败时,记录失败请求并安排定期重试

5.forking

并发调用特定数量的服务,只要一个成功就返回,通常用于实时性要求高的操作,但需要浪费更多的服务资源

6.broadcast

广播调用所有provider,一个一个调用,有一个报错就报错

7.available

遍历所有的实例,找到第一个可用实例调用,没有可用实例就报错

8.mergeable

该集群容错模式下,可以合并结果集,一般和group一起使用

指定容错策略也很简单,在@DubboReference注解上的cluster属性指定自己想要的策略即可。

五:dubbo的版本控制

dubbo提供了版本号的概念来帮助我们解决api后续不兼容升级的问题。

使用起来很简单,在@DubboService注解的version属性指定版本号

package com.example.dubbo.provider;

import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String msg) {
        return "hello " + msg;
    }
}

客户端在@DubboReference注解指定自己需要调用的服务的version

@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",version = "1.0.0")
    private HelloService helloService;

    @Test
    void test(){
        System.out.println(helloService.hello("world"));
    }
}

注:所以为了防止api的不向下兼容升级,在定义发布服务的时候都应该要指定version属性。

六:dubbo的分组

如果api接口的实现类有多个,那么消费者要走哪个实现类呢?dubbo提供了group的概念来解决这个问题。

例:HelloService有两个实现类HelloServiceImpl和HelloServiceImpl2

package com.example.dubbo.provider;

import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(group = "serviceImpl")
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String msg) {
        return "hello HelloServiceImpl";
    }
}

package com.example.dubbo.provider;

import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(group = "serviceImpl2")
public class HelloServiceImpl2 implements HelloService {
    @Override
    public String hello(String msg) {
        return "hello HelloServiceImpl2";
    }
}

在消费者可以利用group属性指定走哪个实现类的逻辑

@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",group = "serviceImpl2")
    private HelloService helloService;

    @Test
    void test(){
        System.out.println(helloService.hello("world"));
    }
}

七:dubbo本地存根

有时候我们需要在服务调用之前和之后做一些操作,dubbo为我们提供了本地存根的功能来帮助我们解决这个问题。

我们需要在本地创建一个类,同样也实现api接口,同时必须要有一个注入服务service的构造器。然后在@DubboReference注解的stub属性上指定该类的全类名即可。

例:

package com.example.dubbo.consumer.stub;

import com.example.api.HelloService;

public class HelloServiceStub implements HelloService {

    public HelloService helloService;

    public HelloServiceStub(HelloService helloService){
        this.helloService = helloService;
    }

    @Override
    public String hello(String msg) {
        System.out.println("before....");
        String result = helloService.hello("world");
        System.out.println("after....");
        return result;
    }
}


@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",
            stub = "com.example.dubbo.consumer.stub.HelloServiceStub")
    private HelloService helloService;

    @Test
    void test(){
        System.out.println(helloService.hello("world"));
    }
}

执行结果:

1647763729(1).png

注:聪明的小伙伴可能已经明白了,这不就是一个装饰器模式嘛。

八:dubbo的服务降级

当我们调用服务失败的时候,有时候需要返回一些兜底数据。dubbo提供mock的功能帮助我们解决这个问题。

首先在消费者本地创建一个实现api接口的类,然后在@DubboReference注解的mock属性指定该类的全类名。

例:

package com.example.dubbo.consumer.mock;

import com.example.api.HelloService;

public class HelloServiceMock implements HelloService {
    @Override
    public String hello(String msg) {
        return "mock data";
    }
}

![1647764835(1).png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b501539766804c7ab9b01718fcaa1ec8~tplv-k3u1fbpfcp-watermark.image?)
@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",
            mock = "com.example.dubbo.consumer.mock.HelloServiceMock")
    private HelloService helloService;

    @Test
    void test(){
        System.out.println(helloService.hello("world"));
    }
}    

服务端抛出异常:

package com.example.dubbo.provider;

import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcException;

@DubboService()
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String msg) {
        throw new RpcException("出异常啦。。");
        //return "hello " + msg;
    }
}

执行结果:

1647764835(1).png

注:只有当服务抛出RpcException的时候才会执行mock中的逻辑。

九:dubbo如何做参数校验

dubbo支持在JSR303的基础上进行参数的校验。

例:

1.引入hibernate-validator的依赖

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.2.Final</version>
        </dependency>

2.在参数上加相关的校验注解

package com.example.api;

import javax.validation.constraints.NotBlank;

/**
 * @author 20136
 */
@Data
public class User {

    @NotBlank(message = "name不能为空")
    private String name;

    @NotBlank(message = "密码不能为空")
    private String password;

}

3.设置@DubboReference注解或@DubboService注解上的validation属性为"true"

@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",
            validation = "true")
    private HelloService helloService;

    @Test
    void test(){
        User user = new User();
        System.out.println(helloService.testUser(user));
    }
}

十:如何异步调用服务

dubbo服务调用默认是同步的,但是dubbo也支持异步调用的方式。

例:

1.设置@DubboReference的async属性为true

2.通过future获取调用的结果

@SpringBootTest
class DubboConsumerApplicationTests {

    @DubboReference(check = false,loadbalance = "random",async = true)
    private HelloService helloService;

    @Test
    void test() throws ExecutionException, InterruptedException {
        helloService.hello("world");

        Future<Object> future = RpcContext.getServiceContext().getFuture();
        Object o = future.get();
        System.out.println(o);
    }
}

十一:dubbo配置的优先级

1015156-20160927172338172-1904551545.png

总的来说:

消费端方法级>服务端方法级>消费端接口级别>服务端接口级别>消费端全局配置>服务端全局配置。

总结:

本篇文章只要介绍了dubbo的一些基本使用,如果有任何疑问,欢迎在下方留言,另外如果文章对你有所帮助,那么点个赞再走吧。