文章目录
上一篇中我们讲了 断路器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-provider
和 battcn-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/ 显示如下代表服务注册成功
My Name's :battcn-provider Email:1837307557@qq.com #正确情况
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
是不会被计数,也不会进入阻断器,所以我们定义一个自己的错误解码器
- 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); } }
- 测试
My Name's :battcn-provider Email:1837307557@qq.com #正确情况
{"customCode":400,"message":"请求错误"} #抛出异常,而不是进入阻断器
关闭battcn-provider:http://localhost:8766/h1
fallback... #服务down机,阻断输出fallback...
- Request 参数传递
在开发中难免会有 服务之间 请求头传递比如Token,ID,因为我们使用的是
FeignClient
的方式,那么我们无法获得HttpServletRequest
的上下文,这个时候怎么办呢?通过硬编码是比较low
的一种,接下来为各位看官带来简单粗暴的(也就知道这种,还有其它简单的方式欢迎交流….)
- 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"); }
- 测试
结果: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
本文作者: 唐亚峰
本文链接: https://blog.battcn.com/2017/08/07/springcloud/dalston/spring-cloud-feign-hystrix/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。