目录
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
来源:https://www.cnblogs.com/my-ordinary/p/12505424.html