一起来学SpringCloud之 - 断路器Hystrix(Feign)

文章目录

  1. 1. - Hystrix

  2. 2. - 准备工作

    1. 2.2.1. - pom.xml

    2. 2.2.2. - ConsumerApplication

    3. 2.2.3. - HiClient

    4. 2.2.4. - HiController

    5. 2.2.5. - bootstrap.yml

    6. 2.1.1. - pom.xml

    7. 2.1.2. - ProviderApplication.java(有变化)

    8. 2.1.3. - bootstrap.yml

    9. 2.1. - battcn-provider

    10. 2.2. - battcn-consumer

    11. 2.3. - 测试

  3. 3. - 解锁新姿势

    1. 3.2.1. - FeignRequest(consumer)

    2. 3.2.2. - HelloController(provider)

    3. 3.2.3. - 测试

    4. 3.1.1. - FeignServiceErrorDecoder

    5. 3.1.2. - 测试

    6. 3.1. - 异常处理

    7. 3.2. - Request 参数传递

  4. 4. - 说点什么

上一篇中我们讲了 断路器Hystrix(Ribbon) 本章讲解Feign+Hystrix已经Request请求传递,各种奇淫技巧….

- Hystrix

Hystrix支持回退概念:当 断路器 打开或运行错误时,执行默认的代码,给@FeignClient定义一个fallback属性,设置它实现回退的,还需要将您的实现类声明为Spring Bean。

官方文档:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign-hystrix-fallback

- 准备工作

1.启动Consul
2.创建 battcn-providerbattcn-consumer 如果看了上一章的,可以直接copy代码复用

- battcn-provider

- pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

- ProviderApplication.java(有变化)

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class ProviderApplication {

    @Value("${spring.application.name}")
    String applicationName;

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

    @GetMapping("/test1")
    public String test1() {
        return "My Name's :" + applicationName + " Email:1837307557@qq.com";
    }

    @GetMapping("/test2")
    public String test2() {
        System.out.println(1/0);
        return "hello error";
    }
}

 

- bootstrap.yml

server:
  port: 8765

spring:
  application:
    name: battcn-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      enabled: true
      discovery:
        enabled: true
        prefer-ip-address: true

 

- battcn-consumer

- pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

- ConsumerApplication

@SpringCloudApplication
@EnableFeignClients//开启 FeignClient支持
public class ConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

}

 

- HiClient

package com.battcn.client;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author Levin
 * @date 2017-08-07.
 */
@FeignClient(value = "battcn-provider",fallback = HiClient.HiClientFallback.class)
public interface HiClient {

    @GetMapping("/test1")
    String test1();

    @GetMapping("/test2")
    String test2();

    @Component
    class HiClientFallback implements HiClient{
        @Override
        public String test1() {
            return "fallback....";
        }

        @Override
        public String test2() {
            return "fallback...";
        }
    }
}

 

- HiController

package com.battcn.controller;

import com.battcn.client.HiClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HiController {
    @Autowired
    HiClient hiClient;
    @GetMapping("/h1")
    public String hi() {
        return hiClient.test1();
    }
    @GetMapping("/h2")
    public String say() {
        return hiClient.test2();
    }
}

 

- bootstrap.yml

server:
  port: 8766

feign:
  hystrix:
    enabled: true   #开启Feign Hystrix 支持

spring:
  application:
    name: battcn-consumer
  cloud:
    consul:
      host: localhost
      port: 8500
      enabled: true
      discovery:
        enabled: true
        prefer-ip-address: true

 

- 测试

启动:battcn-provider

启动:battcn-consumer

访问:http://localhost:8500/ 显示如下代表服务注册成功

一起来学SpringCloud之 - 断路器Hystrix(Feign)-图片-1查看注册中心

访问:http://localhost:8766/h1

My Name's :battcn-provider Email:1837307557@qq.com	#正确情况

 

访问:http://localhost:8766/h2

fallback...  	#错误情况,阻断输出fallback...

 

- 解锁新姿势

- 异常处理

画图工具:https://www.processon.com/

一起来学SpringCloud之 - 断路器Hystrix(Feign)-图片-2异常处理

如果我们FeignClient服务都是内部的,在客户端抛出异常直接往最外层抛出,就不需要在消费者通过硬编码处理了,关键代码(完整代码看GIT)…

battcn-provider 中异常处理

@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponseEntity jsonErrorHandler(Exception e, HttpServletResponse rep) throws Exception {
    if (e instanceof BattcnException) {
        BattcnException exception = (BattcnException) e;
        return exception.toErrorResponseEntity();
    }
    logger.error("服务器未知异常", e);
    rep.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    return new ErrorResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器未知异常");
}

 


battcn-consumer 中异常处理

@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponseEntity jsonErrorHandler(Exception e, HttpServletResponse rep) throws Exception {
    if (e instanceof HystrixBadRequestException) {
        HystrixBadRequestException exception = (HystrixBadRequestException) e;
        rep.setStatus(HttpStatus.BAD_REQUEST.value());
        logger.info("[HystrixBadRequestException] - [" + exception.getMessage() + "]");
        JSONObject obj = JSON.parseObject(exception.getMessage());
        return new ErrorResponseEntity(obj.getInteger("customCode"), obj.getString("message"));
    }
    logger.error("服务器未知异常", e);
    rep.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    return new ErrorResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器未知异常");
}

 

Hystrix中只有,HystrixBadRequestException 是不会被计数,也不会进入阻断器,所以我们定义一个自己的错误解码器

- FeignServiceErrorDecoder

package com.battcn.config;

import com.netflix.hystrix.exception.HystrixBadRequestException;
import feign.Response;
import feign.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component
public class FeignServiceErrorDecoder implements feign.codec.ErrorDecoder {
    static Logger LOGGER = LoggerFactory.getLogger(FeignServiceErrorDecoder.class);
    @Override
    public Exception decode(String methodKey, Response response) {
        try {
            if (response.status() >= 400 && response.status() <= 499) {
                String error = Util.toString(response.body().asReader());
                return new HystrixBadRequestException(error);
            }
        } catch (IOException e) {
            LOGGER.error("[Feign解析异常] - [{}]", e);
        }
        return feign.FeignException.errorStatus(methodKey, response);
    }
}

 

- 测试

访问:http://localhost:8766/h1

My Name's :battcn-provider Email:1837307557@qq.com	#正确情况

 

访问:http://localhost:8766/h2

{"customCode":400,"message":"请求错误"} 	#抛出异常,而不是进入阻断器

 

关闭battcn-provider:http://localhost:8766/h1

fallback...  	#服务down机,阻断输出fallback...

 

- Request 参数传递

在开发中难免会有 服务之间 请求头传递比如Token,ID,因为我们使用的是FeignClient 的方式,那么我们无法获得HttpServletRequest的上下文,这个时候怎么办呢?通过硬编码是比较low的一种,接下来为各位看官带来简单粗暴的(也就知道这种,还有其它简单的方式欢迎交流….)

一起来学SpringCloud之 - 断路器Hystrix(Feign)-图片-3参数传递

- FeignRequest(consumer)

package com.battcn.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.UUID;

@Component
public class FeignRequest implements RequestInterceptor {

    final Logger LOGGER = LoggerFactory.getLogger(this.getClass().getSimpleName());

    @Override
    public void apply(RequestTemplate requestTemplate) {
        //1.模拟获取request.header参数
        String token = UUID.randomUUID().toString().replace("-","").toUpperCase();
        LOGGER.info("传递的Token - [{}]",token);
        requestTemplate.header("token",token);//模拟将Token放入在 feign.Request对象中

    }
}

 

- HelloController(provider)

@Value("${spring.application.name}")
String applicationName;

@Autowired
HttpServletRequest request;

@Override
@GetMapping("/test1")
public String test1() {
    return "My Name's :" + applicationName + " Token:"+request.getHeader("token");
}

 

- 测试

一起来学SpringCloud之 - 断路器Hystrix(Feign)-图片-4日志结果

结果:My Name's :battcn-provider Token:5588551D64C8478BA681A35892A03437 代表我们Token(HttpServletRequest)传递成功…

- 说点什么

画图工具:https://www.processon.com/

本章代码(battcn-provider/consumer):https://git.oschina.net/battcn/battcn-cloud/tree/master/battcn-cloud-hystrix-feign


推荐教程

Java设计模式

创建模式、结构模式、行为模式

Maven教程

Maven是一个项目管理和构建工具

Lucene教程

Lucene是一个开源的基于Java的搜索库

JUnit教程

JUnit是Java编程语言的单元测试框架

Java算法

排序、二叉树、红黑树

Log4j教程

log4j是一种用Java编写的可靠,快速和灵活的日志框架(API)

StringBoot教程

StringBoot实战操作演练

mybatis文档手册

Mybatis基础语法、实例讲解

SpringCloud教程

服务发现、断路器、智能路由

Guava教程

Guava是开源的Java工具库

Elasticsearch教程

一款强大的搜索引擎