java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

4. 综合实战

4.1 配置说明

4.1.1 启动时检查

java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

  • 启动时会在注册中心检查依赖的服务是否可用,不可用时会抛出异常
  • 在消费方编写初始化容器的main方法启动(tomcat启动方式,必须访问一次action才能初始化spring)
/*
* 启动时检查
* */
public class testCheckException {
    public static void main(String[] args) throws IOException {
        // 初始化spring
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring.xml");
        System.in.read();
    }
}
<!--默认是true:抛异常;false:不抛异常--> 
<dubbo:consumer check="false" />
  • 系统级别日志,需要配合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

4.1.2 超时时间

  • 由于网络或服务端不可靠,会导致调用过程中出现不确定的阻塞状态(超时)
  • 为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间
  • 在服务提供者添加如下配置:
<!--设置超时时间为2秒,默认为1秒--> 
<dubbo:provider timeout="2000"/>
  • 可以将服务实现HelloServiceImpl.java中加入模拟的网络延迟进行测试:
import com.alibaba.dubbo.config.annotation.Service;
import service.HelloService;
/*
* 服务实现类
* */
@Service
public class HelloServiceImpl01 implements HelloService {
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "Hello,"+name+"!!!!";
    }
}
  • 超时设置2秒,而模拟的网络延迟有3秒,超出时限,报错!
  • 配置原则
    • dubbo推荐在Provider上尽量多配置Consumer端属性:
      • 1. 作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
      • 2. 在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作消费者的缺省值

4.1.3 重试次数

  • 当出现失败,自动切换并重试其它服务器,dubbo重试的缺省值是2次,我们可以自行设置
  • 在provider提供方配置:
<!-- 消费方连接第1次不算,再来重试3次,总共重试4次 --> 
<dubbo:provider timeout="2000" retries="3"/>
import com.alibaba.dubbo.config.annotation.Service;
import service.HelloService;
/*
* 服务实现类
* */
@Service
public class HelloServiceImpl01 implements HelloService {
    public String sayHello(String name) {
        System.out.println("被调用了一次");
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "Hello,"+name+"!!!!";
    }
}
  • 并不是所有的方法都适合设置重试次数
    • 幂等方法:适合(当参数一样,无论执行多少次,结果是一样的,例如:查询,修改)
    • 非幂等方法:不适合(当参数一样,执行结果不一样,例如:删除,添加)
  • 单独设置某个方法

1. 提供方接口添加sayNo()方法并实现

/*
* 服务方接口
* */
public interface HelloService {
    String sayHello(String name);
    String sayNo();
}
import com.alibaba.dubbo.config.annotation.Service;
import service.HelloService;
/*
* 服务实现类
* */
@Service
public class HelloServiceImpl01 implements HelloService {
    public String sayHello(String name) {
        System.out.println("被调用了一次");
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "Hello,"+name+"!!!!";
    }

    public String sayNo() {
        System.out.println("no被调用了1次");
        return "no";
    }
}

2. 消费方接口添加sayNo()方法声明

/*
* 服务方接口(声明而已,具体实现会远程调用dubb-service的实现类)
* */
public interface HelloService {
    String sayHello(String name);
    String sayNo();
}

3. 消费方controller

package controller;

import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import service.HelloService;

/*
* 控制层
* */
@Controller
public class HelloAction {

    //@Reference // 此注解已经在xml文件中被<dubbo:reference>顶替,所以自动注入 即可
    @Autowired
    private HelloService helloService;


    @GetMapping("hello")
    @ResponseBody
    public String sayHi(String name){
        return helloService.sayHello(name);
    }

    @GetMapping("no")
    @ResponseBody
    public String no(){
        return helloService.sayNo();
    }
}

4. 消费方配置方法重试次数

    <dubbo:reference interface="service.HelloService" id="HelloService">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="sayNo" retries="0"/> <!-- 不重试 -->
    </dubbo:reference>

4.1.4 多版本

  • 一个接口,多个(版本的)实现类,可以使用定义版本的方式引入
  • 为HelloService接口定义两个实现类,提供者修改配置:
<dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl01" version="1.0.0" />
<dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl02" version="2.0.0" />
  • 消费者就可以根据version的版本,选择具体的服务版本
<dubbo:reference interface="service.HelloService" id="HelloService" version="2.0.0">
    <dubbo:method name="sayHello" retries="3"/>
    <dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>
  • 注意:消费者的控制层要改为自动注入,因为@Reference注解和 <dubbo:reference>在这里冲突
/*
* 控制层
* */
@Controller
public class HelloAction {

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

4.1.5 本地存根

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

import org.springframework.util.StringUtils;
import service.HelloService;

/*
* 本地存根
* */
public class HelloServiceStub implements HelloService {

    // helloService的代理对象
    private HelloService helloService;

    /*
    * 本地存根必须以构造方法的形式注入
    * */
    public HelloServiceStub(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public String sayHello(String name) {
        if (!StringUtils.isEmpty(name)){
            return helloService.sayHello(name);
        }
        return "i am sorry!";
    }

    @Override
    public String sayNo() {
        return helloService.sayNo();
    }
}
  • 修改消费者配置:
<dubbo:reference interface="service.HelloService" id="HelloService" version="2.0.0" stub="stub.HelloServiceStub">
    <dubbo:method name="sayHello" retries="3"/>
    <dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>

4.2 负载均衡策略

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

java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

修改提供者配置并启动3个提供者,让消费者对其进行访问

  • tomcat端口8001,8002,8003
  • provider端口20881,20882,20883
<dubbo:provider timeout="2000" retries="3" port="20881"/>
  • HelloServiceImpl01类,服务器1,服务器2,服务器3
public String sayNo() {
    System.out.println("1.0no被调用了1次");
    return "no";
}
public String sayNo() {
    System.out.println("2.0no被调用了1次");
    return "no";
}

java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

  • 启动consumer进行测试
  • 消费方修改权重
 <!--loadbalance="roundrobin" 轮循-->
<dubbo:reference loadbalance="roundrobin" interface="service.HelloService" id="HelloService" version="2.0.0" stub="stub.HelloServiceStub">
    <dubbo:method name="sayHello" retries="3"/>
    <dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>
  • 最好使用管理端修改权重

java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

4.3 高可用

4.3.1 zookeeper宕机

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

4.4 服务降级

  • 壁虎遇到危险会自动脱落尾巴,目的是损失不重要的东西,保住重要的
  • 服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释放服务器的资源来保证核心业务的正常运行

4.4.1 为什么要服务降级

  • 而为什么要使用服务降级,这是防止分布式服务发生雪崩效应
  • 什么是雪崩?就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。

4.4.2 服务降级实现方式

  • 管理控制台配置服务降级:屏蔽容错
  • 屏蔽:mock=force:return+null 表示消费方对该服务的方法调用都 直接返回 null ,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
  • 容错:mock=fail:return+null 表示消费方对该服务的方法调用在 失败后,再返回 null ,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

java之学习记录 7 - 1 - 分布式系统架构解决方案Dubbo 实战(中)

上一篇:linux基础20——僵尸进程和孤儿进程


下一篇:java eventhandler