hystrix服务容错处理笔记
目录
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.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
作者:以罗伊
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。