上一篇中我们讲了 断路器Hystrix(Ribbon) 本章讲解Feign+Hystrix已经Request请求传递,各种奇淫技巧….
Hystrix支持回退概念:当 断路器
打开或运行错误时,执行默认的代码,给@FeignClient
定义一个fallback
属性,设置它实现回退的,还需要将您的实现类声明为Spring Bean。
官方文档:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign-hystrix-fallback
1.启动Consul
2.创建 battcn-provider
和 battcn-consumer
如果看了上一章的,可以直接copy代码复用
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
@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:[email protected]";
}
@GetMapping("/test2")
public String test2() {
System.out.println(1/0);
return "hello error";
}
}
server:
port: 8765
spring:
application:
name: battcn-provider
cloud:
consul:
host: localhost
port: 8500
enabled: true
discovery:
enabled: true
prefer-ip-address: true
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
@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);
}
}
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...";
}
}
}
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();
}
}
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/ 显示如下代表服务注册成功
访问:http://localhost:8766/h1
My Name's :battcn-provider Email:[email protected] #正确情况
访问:http://localhost:8766/h2
fallback... #错误情况,阻断输出fallback...
画图工具:https://www.processon.com/
如果我们
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
是不会被计数,也不会进入阻断器,所以我们定义一个自己的错误解码器
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:[email protected] #正确情况
访问:http://localhost:8766/h2
{"customCode":400,"message":"请求错误"} #抛出异常,而不是进入阻断器
关闭battcn-provider:http://localhost:8766/h1
fallback... #服务down机,阻断输出fallback...
在开发中难免会有 服务之间 请求头传递比如Token,ID,因为我们使用的是
FeignClient
的方式,那么我们无法获得HttpServletRequest
的上下文,这个时候怎么办呢?通过硬编码是比较low
的一种,接下来为各位看官带来简单粗暴的(也就知道这种,还有其它简单的方式欢迎交流….)
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对象中
}
}
@Value("${spring.application.name}")
String applicationName;
@Autowired
HttpServletRequest request;
@Override
@GetMapping("/test1")
public String test1() {
return "My Name's :" + applicationName + " Token:"+request.getHeader("token");
}
结果: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
如有问题请及时与我联系