4

10 Dubbo 配置实战 - look-word

 2 years ago
source link: https://www.cnblogs.com/look-word/p/16496995.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

  • 一个接口,多个(版本的)实现类,可以使用定义版本的方式引入
  • 为 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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK