hystrix服务容错处理笔记

余生颓废 提交于 2020-03-16 18:28:29

0 环境

系统环境: win10

编辑器:idea

springcloud:H版

1 前言

hystrix叫断路器/熔断器。相当于保险丝

  • 微服务中存在多个服务可直接调用的服务 调用时突然出现故障(常在河边走 哪有不湿鞋) 可能整个系统凉了(服务雪崩效应 --> 【 | 类似前段时间 都缺人其实 假设而已 可能比喻不恰当 超市A -> 食品厂B --> 原料厂C | A催货 --> B需要原料 催货 --> C这边没有人手 B只能催C 等这边有货 而A不断催B B只能继续催C 就这样凉了】) 通过hystrix解决这个问题 某一个模
    块故障了 通过我们之前配置好的东西 使的整个系统能运转

2 基本用法

  • 创建一个springboot项目 配置依赖 进入项目 | 配置yml --> 端口设置 应用名 eureka连接 | 开启断路器。。。
  • 项目用到的eureka server和provider以及hystrix

2.1 创建项目

2.1 创建项目

2.2 yml配置

spring:
  application:
    name: hystrix
server:
  port: 3000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1234/eureka

2.3 开启断路器和提供RestTemplate实例

// 开启断路器
@SpringCloudApplication
public class HystrixBaseApplication {

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

    // 提供RestTemplate实例
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

在这里插入图片描述

2.4 提供hystrix接口

2.4.1 consumer与hystrix的区别

  • 注解式
// hystrix
@Service
public class HelloService {
    @Autowired
    RestTemplate restTemplate;
    /**
        * @Description:  在这个方法中 我们会发起远程调用 调用provider中提供的hello接口
         *               但是这个调用可能会失败
         *               我们需要在方法上添加@HystrixCommand注解 配置fallbackMethod属性
         *               该属性表示当你调用方法失败 可以用临时方法替代
         *               (服务降级 越往下降 -> 获取数据越容易 但数据的准确性也在降级)
         *               ignoreExceptions属性作为了解 忽略某个异常
        * @Param: []
        * @return: java.lang.String
        * @Author: 
        * @Date: 2020xx/xx
    */
//    @HystrixCommand(fallbackMethod = "error")
    @HystrixCommand(fallbackMethod = "error", ignoreExceptions = ArithmeticException.class)
    public String hello(){
        // 异常处理 若不是提供方的错误 而是consumer本身的异常
//        int i = 1/0;
        return restTemplate.getForObject("http://provider/hello", String.class);
    }
/**
    * @Description: 方法名需要和 fallbackMethod属性中的名字一致 还有返回类型得一致 不然返回类型不一致 玩个啥
    * @Param:
    * @return:
    * @Author: 
    * @Date: 2020/xx/xx
    */
    public String error(){
        return "error";
    }
}
// hystrix
@RestController
public class HelloController {
    @Autowired
    HelloService helloService;
    /**
         * @Description: 为啥要用到hystrix 首先eureka中 provider某个实例关闭了 server获取了 在
         *              在告诉consumer 这中间肯定会耗时(另一个场景就是请求延时) 那么会出现一个错误界面
         *              等consumer收到通知了 才会知道
         *              那么hystrix呢 会跳出eroor字符串 而不是一个错误界面 展示一个界面给用户(是不是好多了)
         * @Param:
         * @return:
         * @Author: 
         * @Date: 2020/xx/xx
    */
    @GetMapping("/hello")
    public String hello(){
        return helloService.hello();
    }
}    
// provider
@RestController
public class HelloController {

    @Value("${server.port}")
    Integer port;

    @GetMapping("/hello")
    public String hello(){
        return "hello>>>" + port;
    }
}
  • 打包provider 找到target位置 java -jar xxxx --server.port=xxx 打开2窗口 设置2个不同的prot 并且开启eureka server
  • 体验一下consumer调用和hystrix调用的差别(若是调用失败 先到server上看看 是否注册上来了)

    • 启动consumer(之前的代码就行) 调用接口 ctrl+c关闭一个provider 在调用会出现一个错误页面提示 需要等待一会 才会跳出未关闭的provider端口 关闭端口有个传递时间 等consumer接收到 就会正常显示了
    • 重启关闭的provider端口 启动hystrix 调用hello接口 多次刷新url 2个接口均衡显示 关闭其中一个provider(速度要快 不然看不到效果) 再到页面刷新 会出现erro 自定义返回值体验是不是更好点

3 请求命令

  • 继承方式实现

    基本使用

// hystrix
// 默认使用线程隔离策略(可以配置线程池的一些参数) 还可以信号量策略配置
public class HelloCommand extends HystrixCommand<String> {

    @Autowired
    RestTemplate restTemplate;
    
    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }
    
    @Override
    protected String run() throws Exception {
//        int i = 1 / 0;
// 获取当前线程的名称
//        System.out.println(Thread.currentThread().getName());
        return restTemplate.getForObject("http://provider/hello", String.class);
//        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }
}
// hystrix controller
/** 
    * @Description: 一个实例只能执行一次 可以直接执行 也可以先入队后执行
    * @Param:  
    * @return:  
    * @Author: 
    * @Date: 2020/xx/xx
    */
    @GetMapping("/hello1")
    public void hello1(){
        HelloCommand learn = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("learn")), restTemplate);
        // 直接执行
        String execute = learn.execute();
        System.out.println("直接执行:" + execute);

        HelloCommand helloCommand = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("learn")), restTemplate);
        Future<String> queue = helloCommand.queue();

        try {
            // 先入队 后执行
            String s = queue.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }

启动eureka server和provider以及hystrix 访问hello1接口 在控制台查看结果

hystrix继承方式 实现降级

public class HelloCommand extends HystrixCommand<String> {

    @Autowired
    RestTemplate restTemplate;

    String name;

    public HelloCommand(Setter setter, RestTemplate restTemplate, String name) {
        super(setter);
        this.name = name;
        this.restTemplate = restTemplate;
    }

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    // 缓存需要重写该方法
    @Override
    protected String getCacheKey() {
        return name;
    }

    /*
    *  请求失败的回调
    *
    * */
    @Override
    protected String getFallback() {
        return "error_extends";
    }

    @Override
    protected String run() throws Exception {
//        int i = 1 / 0;
        return restTemplate.getForObject("http://provider/hello", String.class);
        // 获取当前线程的名称
//        System.out.println(Thread.currentThread().getName());
    }
}

重启hystrix项目 访问hello1(启动2provider 都注册了 在关闭一个 刷新才能看到效果 与一开始的注解式方式相似)

  • 注解实现请求异步调用
// hystrix controller层
@GetMapping("/hello2")
    public void hello2(){
        Future<String> stringFuture = helloService.hello1();

        try {
            String s = stringFuture.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
// hystrix service
/**
    * @Description: 通过注解实现请求异步调用
    * @Param:
    * @return:
    * @Author: 
    * @Date: 2020/xx/xx
    */
    @HystrixCommand(fallbackMethod = "error")
    public Future<String> hello1(){
        return new AsyncResult<String>(){

            @Override
            public String invoke() {
                return restTemplate.getForObject("http://provider/hello", String.class);
            }
        };
    }

重启hystrix项目 访问hello2

4 异常处理

  • 注解式实现
    // hystrix controller
    @GetMapping("/hello")
    public String hello(){
         return helloService.hello();
    }
// hystrix service
@Service
public class HelloService {
    @Autowired
    RestTemplate restTemplate;

    /**
    * @Description:  在这个方法中 我们会发起远程调用 调用provider中提供的hello接口
     *               但是这个调用可能会失败
     *               我们需要在方法上添加@HystrixCommand注解 配置fallbackMethod属性
     *               该属性表示当你调用方法失败 可以用临时方法替代
     *               (服务降级 越往下降 -> 获取数据越容易 但数据的准确性也在降级)
     *               ignoreExceptions属性作为了解 忽略某个异常
    * @Param: []
    * @return: java.lang.String
    * @Author: 
    * @Date: 2020/xx/xx
    */
//    @HystrixCommand(fallbackMethod = "error")
    @HystrixCommand(fallbackMethod = "error", ignoreExceptions = ArithmeticException.class)
    public String hello(){
        // 异常处理 若不是提供方的错误 而是consumer本身的异常
        int i = 1/0;
        return restTemplate.getForObject("http://provider/hello", String.class);
    }
     /**
    * @Description: 方法名需要和 fallbackMethod属性中的名字一致 还有返回类型得一致 不然返回类型不一致 玩个啥
    * @Param:
    * @return:
    * @Author:
    * @Date: 2020/xx/xx
    */
    public String error(Throwable throwable){
        return "error: " + throwable.getMessage();
    }
}
  • 继承式实现
public class HelloCommand extends HystrixCommand<String> {

    @Autowired
    RestTemplate restTemplate;
    
    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    /*
    *  请求失败的回调
    *
    * */
    @Override
    protected String getFallback() {
        // 在继承方式中出现异常  因为是重写方法 那么我们不可能在参数上添加Throwable
        // 用getExecutionException调用
        return "error_extends: " + getExecutionException().getMessage();
    }

    @Override
    protected String run() throws Exception {
        int i = 1 / 0;
        return restTemplate.getForObject("http://provider/hello", String.class);
        // 获取当前线程的名称
//        System.out.println(Thread.currentThread().getName());
    }
}

重启hystrix 分别访问hello hello1接口

5 请求缓存

调用同一个接口 若参数一致 将之缓存

5.1 加缓存

    // 在provider中添加hello2接口
    @GetMapping("/hello2")
    public String hello2(String name){
        System.out.println(new Date() + "--->" + name);
        return "hello " + name;
    }
  • 注解式
// hystrix service中实现
    @HystrixCommand(fallbackMethod = "error1")
    // 这个注解表示该方法的请求结果会被缓存起来
    // 默认情况下 缓存key就是方法的n个参数的组合 缓存的value就是方法的返回值
    // key(n个param组合) : value
    @CacheResult
    public String hello2(String name){
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }

    @HystrixCommand(fallbackMethod = "error1")
    // 这个注解表示该方法的请求结果会被缓存起来
    // 默认情况下 缓存key就是方法的n个参数的组合 缓存的value就是方法的返回值
    // key(n个param组合) : value
    // 若是只是需要一个参数作为key 在该参数上添加@CacheKey即可
    // 多个请求中 只要name一样 哪怕id不同 二次请求也会使用第一次请求结果(name一样 id不同 --> 使用缓存)
    @CacheResult
    public String hello3(@CacheKey String name, Integer id){
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }
    
    public String error1(String name){
        return "error1" + name;
    }
// hystrix controller
/**
    * @Description: 注解式
    * @Param:
    * @return:
    * @Author: 水面行走
    * @Date: 2020/3/15
    */
    @GetMapping("/hello3")
    public void hello3(){
        // 需要初始化 不然会报错
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        // 缓存数据
        String learn = helloService.hello2("learn");
        // 使用缓存
        learn = helloService.hello2("learn");
        // 关闭
        context.close();
    }
  • 继承式
public class HelloCommand extends HystrixCommand<String> {

    @Autowired
    RestTemplate restTemplate;

    String name;

    public HelloCommand(Setter setter, RestTemplate restTemplate, String name) {
        super(setter);
        this.name = name;
        this.restTemplate = restTemplate;
    }

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }
    
    // 清除缓存 需要定义一方法 HystrixRequestCache用来执行清除操作 根据getCacheKey的返回的key来清除 在controller调用这个方法进行清除
    
    // 缓存需要重写该方法
    @Override
    protected String getCacheKey() {
        return name;
    }

    /*
    *  请求失败的回调
    *
    * */
    @Override
    protected String getFallback() {
        // 在继承方式中出现异常  因为是重写方法 那么我们不可能在参数上添加Throwable
        // 用getExecutionException调用
        return "error_extends: " + getExecutionException().getMessage();
    }

    @Override
    protected String run() throws Exception {
//        int i = 1 / 0;
//        return restTemplate.getForObject("http://provider/hello", String.class);
        // 获取当前线程的名称
//        System.out.println(Thread.currentThread().getName());
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }
}
// hystrix controller
@GetMapping("/hello5")
    public void hello5(){
        // 需要初始化 不然会报错
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        HelloCommand learn = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("learn")), restTemplate, "learn");
        // 直接执行
        String execute = learn.execute();
        System.out.println("直接执行:" + execute);

        HelloCommand helloCommand = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("learn")), restTemplate, "learn");
        Future<String> queue = helloCommand.queue();

        try {
            // 先入队 后执行
            String s = queue.get();
            System.out.println("流程化:" + s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 关闭
        context.close();
    }

重启hystrix和provider 访问hello3端口 在控制台查看provider接口输出 确实只显示一次 缓存有效

5.2 移除缓存

// hystrix controller
@GetMapping("/hello4")
    public void hello4(){
        // 需要初始化 不然会报错
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        // 缓存数据
        String learn = helloService.hello2("learn");
        // 删除缓存
        helloService.delUserByName("learn");
        // 因为缓存数据被删除了 需要向provider发起请求
        learn = helloService.hello2("learn");
        // 关闭
        context.close();
    }
// hystrix service
@HystrixCommand(fallbackMethod = "error1")
    // 这个注解表示该方法的请求结果会被缓存起来
    // 默认情况下 缓存key就是方法的n个参数的组合 缓存的value就是方法的返回值
    // key(n个param组合) : value
    @CacheResult
    public String hello2(String name){
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }

    @HystrixCommand(fallbackMethod = "error1")
    // 这个注解表示该方法的请求结果会被缓存起来
    // 默认情况下 缓存key就是方法的n个参数的组合 缓存的value就是方法的返回值
    // key(n个param组合) : value
    // 若是只是需要一个参数作为key 在该参数上添加@CacheKey即可
    // 多个请求中 只要name一样 哪怕id不同 二次请求也会使用第一次请求结果(name一样 id不同 --> 使用缓存)
    @CacheResult
    public String hello3(@CacheKey String name, Integer id){
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class, name);
    }

    public String error1(String name){
        return "error1" + name;
    }

    /**
     * @Description: 当我们删除了数据库数据 还会删除缓存数据 --> @CacheRemove登场
     *                删除缓存 哪里的缓存(指定) 比如删除某个指定的方法
     *               使用@CacheRemove配合commandKey属性 --> 指定缓存 对其诛之
     *               commandKey属性指定删除的某个方法
     * @Param: [name]
     * @return: java.lang.String
     * @Author: 水面行走
     * @Date: 2020/3/15
     */
    @HystrixCommand
    // 删除缓存
    @CacheRemove(commandKey = "hello2")
    public String delUserByName(String name){
        return null;
    }

重启hystrix和provider 访问hello3端口 在控制台查看provider接口输出 显示两次次 缓存被移除

6 请求合并

频繁的调用provider接口 太浪费了 就有了将多个请求合并为一个请求的方式

// 在provider中提供请求合并接口
@RestController
public class UserController {
    /**
    * @Description:  若consumer传过来过个id(1,2,3,4,5....这样的格式) 需要格式转换
     *               该接口处理单个请求/合并(多个)后请求
    * @Param:
    * @return:
    * @Author: 水面行走
    * @Date: 2020/3/15
    */
    @GetMapping("/user/{ids}")
    public List<User> getUserByIds(@PathVariable String ids){
        System.out.println("ids: " + ids);
        // string切割为string数组
        String[] split = ids.split(",");

        List<User> list = new ArrayList<>();

        // 将string数组值遍历封装为user中的属性 添加到list集合中
        for (String s : split) {
            User user = new User();
            // string转换int
            user.setId(Integer.parseInt(s));
            list.add(user);

        }

        return list;

    }
}
    // hystrix pom.xml添加commons模块
    <dependency>
      <groupId>xxx</groupId>
      <artifactId>commons</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  • 注解式
// hystrix service
@Service
public class UserService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "200")})
    public Future<User> getUsersByIds(Integer id){
        return null;
    }

    @HystrixCommand
    public List<User> getUserByIds(List<Integer> ids){
        // 数组.class 因为List.class 结果是个map类型 --> 类属性:value 处理很麻烦
        // 并且需要数组转换为string
        User[] users = restTemplate.getForObject("http://provider/user/{1}", User[].class, StringUtils.join(ids, ","));

        return Arrays.asList(users);

    }

}
// hystrix controller
@GetMapping("/hello7")
    public void hello7() throws ExecutionException, InterruptedException {
        // 需要初始化 不然会报错
        HystrixRequestContext context = HystrixRequestContext.initializeContext();

        Future<User> queue = userService.getUsersByIds(74);
        Future<User> queue1 = userService.getUsersByIds(64);
        Future<User> queue2 = userService.getUsersByIds(54);
        Future<User> queue3 = userService.getUsersByIds(44);

        User user = queue.get();
        User user1 = queue1.get();
        User user2 = queue2.get();
        User user3 = queue3.get();
        System.out.println(user);
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user3);

        // 关闭
        context.close();
    }
  • 继承式(了解即可)
// hystrix
public class UserBatchCommand extends HystrixCommand<List<User>> {

    private List<Integer> ids;
    private UserService userService;

    public UserBatchCommand(List<Integer> ids, UserService userService) {
        // 写死
        super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("batchCmd")).andCommandKey(HystrixCommandKey.Factory.asKey("batchKey")));
        this.ids = ids;
        this.userService = userService;
    }

    @Override
    protected List<User> run() throws Exception {
        return userService.getUserByIds(ids);
    }
}
// hystrix
// 请求合并
public class UserCollapseCommand extends HystrixCollapser<List<User>, User, Integer> {

    private Integer id;
    private UserService userService;

    public UserCollapseCommand(UserService userService, Integer id) {
        // 写死
        super(HystrixCollapser.Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(200)));
        this.id = id;
        this.userService = userService;
    }

    // 请求参数
    @Override
    public Integer getRequestArgument() {
        return id;
    }

    // 请求合并方法
    @Override
    protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Integer>> collection) {
        List<Integer> list = new ArrayList<>(collection.size());
        for (CollapsedRequest<User, Integer> integerCollapsedRequest : collection) {
            list.add(integerCollapsedRequest.getArgument());
        }
        return new UserBatchCommand(list, userService);
    }

    // 请求结果分发
    @Override
    protected void mapResponseToRequests(List<User> users, Collection<CollapsedRequest<User, Integer>> collection) {
        int count = 0;
        for (CollapsedRequest<User, Integer> userIntegerCollapsedRequest : collection) {

            userIntegerCollapsedRequest.setResponse(users.get(count++));
        }
    }
}
// service 实现
@Service
public class UserService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand
    public List<User> getUserByIds(List<Integer> ids){
        // 数组.class 因为List.class 结果是个map类型 --> 类属性:value 处理很麻烦
        // 并且需要数组转换为string
        User[] users = restTemplate.getForObject("http://provider/user/{1}", User[].class, StringUtils.join(ids, ","));

        return Arrays.asList(users);

    }

}
// hystrix controller
@GetMapping("/hello6")
    public void hello6() throws ExecutionException, InterruptedException {
        // 需要初始化 不然会报错
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        UserCollapseCommand cmd = new UserCollapseCommand(userService, 99);
        UserCollapseCommand cmd1 = new UserCollapseCommand(userService, 88);
        UserCollapseCommand cmd2 = new UserCollapseCommand(userService, 77);
        UserCollapseCommand cmd3 = new UserCollapseCommand(userService, 66);

        // 加入队列
        Future<User> queue = cmd.queue();
        Future<User> queue1 = cmd1.queue();
        Future<User> queue2 = cmd2.queue();
        Future<User> queue3 = cmd3.queue();

        User user = queue.get();
        User user1 = queue1.get();
        User user2 = queue2.get();
        User user3 = queue3.get();
        System.out.println(user);
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user3);


        // 关闭
        context.close();
    }

7 总结

降级处理 注解(@HystrixCommand(fallbackMethod = "xxx"))和继承式(重写getFallback())
缓存 注解@CacheResult和重写getCacheKey()
合并@HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "200")}) --> 请求合并 延时 和@HystrixCommand

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!