happy birthday BNTang!

10 Dubbo 配置实战

Dubbo 配置实战

快速入门 dubbo

建议看这篇文章是在学习了快速入门 dubbo 那篇文章的基础上来学习

配置说明

文档地址 https://dubbo.apache.org/zh/index.html

img

关于 dubbo 的配置说明 在文档中都有比较详细的说明,下面举例的都是较为常用的

img

1 启动时检查

  • 启动时会在注册中心检查依赖的服务是否可用,不可用时会抛出异常
  • 在消费方编写初始化容器的 main 方法启动(tomcat 启动方式,必须访问一次 action 才能初始化
    spring)
  • 想想为什么要有这个配置呢?
    • 可以提前发现服务提供方是否可用
示例代码

img

直接启动这个测试类,注意 spring 配置文件的位置

  • 我这里测试,现在是没有启动提供者
  • 因为我们测试的目的就是让他没有提供者,会不会有报错提示
/**
 * @author : look-word
 * 2022-07-19 09:44
 **/
public class TestCheckException {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring.xml");
        // 让程序一直读取, 目的是不让他停止
        System.in.read();
    }
}

当我们启动后会发现,诶,怎么没有错误呢,是下面 log4j 的提示呢?

  • 这里没有错误提示的原因呢,就是说我们没有正确的去配置 log4j,的确我们也没有去配置

img

  • 系统级别日志,需要配合 log4j 才输出,在 resources 下添加 log4j.properties,内容如下:
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} %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=dubbo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n
log4j.rootLogger=error, stdout,file

再次启动,会发现。如我们所愿它出错了。

错误信息

  • 翻译的意思:说在 zookeeper 中没有找到可用的服务

java.lang.IllegalStateException: Failed to check the status of the service service.HelloService. No provider available for the service service.HelloService from the url zookeeper:
img

关闭检查

在 spring.xml 配置文件中加上就不会有异常提示了

  • 可以看到,我这里的这个配置是注释掉的,在实际开发中我们是需要这个异常提示的,不推荐关闭
  • img
<!--默认是true:抛异常;false:不抛异常-->
<dubbo:consumer check="false" />

然后启动测试文件即可,这里不做演示了


2 超时时间

  • 由于网络或服务端不可靠,会导致调用过程中出现不确定的阻塞状态(超时)
  • 为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间
  • 在服务提供者添加如下配置:
<!--设置超时时间为2秒,默认为1秒-->
<dubbo:provider timeout="2000"/>
  • 可以将服务实现 HelloServiceImpl.java 中加入模拟的网络延迟进行测试:
@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }
}
  • 超时设置 2 秒,而模拟的网络延迟有 3 秒,超出时限,报错!

错误代码: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout.

  • 说服务器响应超时。

配置原则:

dubbo 推荐在Provider上尽量多配置Consumer端属性

  1. 服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试
    次数,等等
  2. 在Provider配置后,Consumer不配置则会使用 Provider 的配置值,即 Provider 配置可
    以作消费者的缺省值

3 重试次数

  • 当出现失败,自动切换并重试其它服务器,dubbo 重试的缺省值是 2 次,我们可以自行设置
  • 在 provider 提供方配置:
<!-- 消费方连接第1次不算,再来重试3次,总共重试4次 -->
<dubbo:provider timeout="2000" retries="3"/>

修改实现类代码: 增加次数

@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    int a;
    @Override
    public String sayHello(String name) {
        System.out.println("被调用第"+(++a)+"次");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }
}

可以看到 重试了 3 次 第一次不算

img

引入问题

并不是所有的方法都适合设置重试次数

  • 幂等方法:适合(当参数一样,无论执行多少次,结果是一样的,例如:查询,修改)
  • 非幂等方法:不适合(当参数一样,执行结果不一样,例如:删除,添加)

我们需要单独为某个方法设置重试次数

  • 需要再添加一个方法,作对比
  1. 提供方接口添加 sayNo()方法并实现
public interface HelloService {
    String sayHello(String name);
    String no();
}
@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    int a,b;
    @Override
    public String sayHello(String name) {
        System.out.println("sayHello被调用第"+(++a)+"次");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }

    @Override
    public String no() {
        System.out.println("no被调用第"+(++b)+"次");
        return "no";
    }
}
  1. 消费方接口添加 sayNo()方法声明
public interface HelloService {
    String sayHello(String name);
    String no();
}
  1. 消费方 controller
@RestController
public class HelloAction {
	// Resource 注解 指定名称注入
    @Resource(name = "helloService")
    private HelloService hs;

    @RequestMapping("hello/{name}")
    @ResponseBody
    public String hello(@PathVariable String name) {
        return hs.sayHello(name);
    }

    @RequestMapping("no")
    @ResponseBody
    public String no() {
        return hs.no();
    }
}
  1. 消费方配置方法重试次数
    <dubbo:reference interface="service.HelloService" id="helloService">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

启动项目,访问

可以看到,我们为每种方法配置的重试次数成功了

img


4 多版本

  • 一个接口,多个(版本的)实现类,可以使用定义版本的方式引入
  • 为 HelloService 接口定义两个实现类,提供者修改配置:
配置文件

为 HelloService 定义了两个版本

    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl1" version="1.0.0">
    </dubbo:service>
    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl2" version="2.0.0">
    </dubbo:service>
修改实现类
  • 复制 HelloServiceImpl 重命名为 1 和 2
  • 分别为每个实现类标识版本信息

img

  • 因为提供者定义了版本所以消费者就可以根据 version 的版本,选择具体的服务版本 这里是消费者配置文件

img

注意:消费者的控制层要改为自动注入,因为@Reference 注解和 dubbo:reference在这里冲突

  • Resource 注解默认是根据变量名去 spring 容器中找对应的 bean 的
  • 需要在直接参数中配置 bean 的名称 和 上面图中 id 对应

img

启动测试

注意 每次修改配置文件 都需要重启项目

访问: http://localhost:8002/no
img

  • 当消费者的版本修改为 version="*",那么就会随机调用服务提供者的版本

    这是访问多次 http://localhost:8002/no 控制台输出的信息

    img

5 本地存根

为什么要有本地存根?

  • 目前我们的分布式架构搭建起来有一个严重的问题,就是所有的操作全都是 消费者发起,由服务
    提供者执行
  • 消费者动动嘴皮子却什么活都不干,这样会让提供者很累,例如简单的参数验证,消费者完全能够
    胜任,把合法的参数再发送给提供者执行,效率高了,提供者也没那么累了
  • 例如:去房产局办理房屋过户,请带好自己的证件和资料,如果什么都不带,那么办理过户手续会
    很麻烦,得先调查你有什么贷款,有没有抵押,不动产证是不是你本人,复印资料等操作。一天肯
    定办不完。明天还要来。如果你能提前将这些东西准备好,办理过户,1 个小时足矣,这就是“房产
    中介办事效率高的原因”
  • 话不多说,先在消费者处理一些业务逻辑,再调用提供者的过程,就是“本地存根”
示例代码

代码实现肯定在 消费者,创建一个 HelloServiceStub 类并且实现 HelloService 接口

注意:必须使用构造方法的方式注入

img

public class HelloServiceStub implements HelloService {
    private HelloService helloService;
    // 注入HelloService
    public HelloServiceStub(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public String sayHello(String name) {
        System.out.println("本地存根数据验证。。。");
        if(!StringUtils.isEmpty(name)){
            return helloService.sayHello(name);
        }
        return "i am sorry!";
    }

    @Override
    public String no() {
        return helloService.no();
    }
}
修改消费者配置文件
  • 添加的是红框位置的参数

img

    <dubbo:reference interface="service.HelloService" id="helloService" version="*" stub="service.impl.HelloServiceStub">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

老样子,clean项目 然后打包启动

负载均衡策略

  • 负载均衡(Load Balance), 其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作
    任务。
  • 简单的说,好多台服务器,不能总是让一台服务器干活,应该“雨露均沾”
  • dubbo 一共提供 4 种策略,缺省为 random 随机分配调用

img

示例代码
  • 修改提供者配置并启动 3 个提供者,让消费者对其进行访问
    • tomcat 端口 8001,8002,8003
    • provider 端口 20881,20882,20883
<dubbo:provider timeout="2000" retries="3" port="20881"/>

img

HelloServiceImpl2 类,服务器 1,服务器 2,服务器 3

  • 在每次修改 tomcat 端口号 和 provider 端口是 修改 HelloServiceImpl2 的内容
  • 因为我这里用的是 2.0.0 的版本,所以修改的是 HelloServiceImpl2 的内容

img

启动 consumer 进行测试

启动一个消费者,三个提供者

  • 底下我已经访问了一次,当我们访问多次,去控制台查看输出信息时,会发现他是随机的去调用提供者

img

消费方修改权重

loadbalance 取值文章

    <dubbo:reference loadbalance="roundrobin" interface="service.HelloService" id="helloService" version="2.0.0" stub="service.impl.HelloServiceStub">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

img

  • 最好使用管理端修改权重
    img

然后启动测试即可

高可用

1 zookeeper 宕机

  • zookeeper 注册中心宕机,还可以消费 dubbo 暴露的服务
    • 监控中心宕掉不影响使用,只是丢失部分采样数据
      数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
      注册中心对等集群,任意一台宕掉后,将自动切换到另一台
      注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
      服务提供者无状态,任意一台宕掉后,不影响使用
      服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
  • 测试:
  • 正常发出请求
  • 关闭 zookeeper:./zkServer.sh stop
  • 消费者仍然可以正常消费

服务降级

  • 壁虎遇到危险会自动脱落尾巴,目的是损失不重要的东西,保住重要的
  • 服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释
    放服务器的资源来保证核心业务的正常运行
1 为什么要服务降级
  • 而为什么要使用服务降级,这是防止分布式服务发生雪崩效应
  • 什么是雪崩?就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,
    很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他
    服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪
    崩。
2 服务降级实现方式
  • 在 管理控制台配置服务降级:屏蔽和容错
  • 屏蔽:mock=force:return+null 表示消费方对该服务的方法调用都 直接返回 null 值,不发起远程
    调用。用来屏蔽不重要服务不可用时对调用方的影响。
  • 容错:mock=fail:return+null 表示消费方对该服务的方法调用在 失败后,再返回 null 值,不抛异
    常。用来容忍不重要服务不稳定时对调用方的影响。
    img
posted @ 2022-07-20 10:40  look-word  阅读(450)  评论(0编辑  收藏  举报