3.SpringCloud 熔断器-Hystrix
Hystrix 基本介绍与Hystrix服务搭建
Hystrix 叫做断路器/熔断器。微服务系统中,整个系统出错的概率非常高,因为涉及到的模块太多了。当某一个模块无法正常工作时,希望通过配置一些服务熔断的措施,来保证整个系统正常运行。
1. 添加相关依赖 Web、Eureka Client、Hystrix
<dependencies>
<!-- SpringBoot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka-Client 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Hystrix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- SpringCloud 版本控制依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. 将 Hystrix服务 注册到 Eureka 上
spring.application.name=hystrix
server.port=3000
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
3. 在项目启动类上添加 @EnableCircuitBreaker 注解,开启断路器,同时提供一个 RestTemplate 实例
@SpringBootApplication
@EnableCircuitBreaker
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
启动类上的两个注解,也可以使用 @SpringCloudApplication 代替,这是一个组合注解。
Hystrix 服务熔断/服务降级
服务熔断:当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
服务降级:当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务或页面不进行处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
Hystrix 服务熔断/降级示例:
HelloService中 在方法上 添加 @HystrixCommand 注解,配置 fallbackMethod 属性即可:
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
/**
* 在这个方法中,远程调用 provider 中提供的 /hello 接口
* 添加 @HystrixCommand 注解,配置 fallbackMethod 属性,表示该方法调用失败时的临时替代方法
* @return
*/
@HystrixCommand(fallbackMethod = "error")
public String hello() {
return restTemplate.getForObject("http://provider/hello", String.class);
}
/**
* 注意,临时替代方法的 方法名称和返回值 要与 fallbackMethod 配置中保持一致
* @return
*/
public String error() {
return "error";
}
}
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello() {
return helloService.hello();
}
}
Hystrix 请求异步调用
定义方法的返回值类型为 Future<T>,返回AsyncResult对象,重写invoke()方法
@HystrixCommand(fallbackMethod = "error")
public Future<String> hello2() {
return new AsyncResult<String>() {
@Override
public String invoke() {
return restTemplate.getForObject("http://provider/hello", String.class);
}
};
}
@GetMapping("/hello2")
public void hello2() {
Future<String> hello2 = helloService.hello2();
try {
String s = hello2.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Hystrix 异常处理
当发起服务调用时,consumer 中抛出了异常,也会自动进行服务降级,我们还可以获取异常的原因。
上述/hello接口中,定义一个异常情况,通过Throwable 参数获取异常的原因:
@HystrixCommand(fallbackMethod = "error")
public String hello() {
int i = 1 / 0;
return restTemplate.getForObject("http://provider/hello", String.class);
}
public String error(Throwable t) {
return "error:" + t.getMessage();
}
另一种情形:如果抛异常了,我们希望异常直接抛出,不要进行服务降级,那么只需要配置忽略某一个异常即可,配置ignoreExceptions属性
@HystrixCommand(fallbackMethod = "error",ignoreExceptions=ArithmeticException.class)
public String hello3() {
int i = 1 / 0;
return restTemplate.getForObject("http://provider/hello", String.class);
}
Hystrix 请求缓存
- @CacheResult,表示该方法的请求结果会被缓存起来
请求缓存就是在 consumer 中调用同一个接口,如果参数相同,则可以使用之前缓存下来的数据。
1.首先在 provider 中添加 /testCache 接口,一会用来检测缓存配置是否生效:
@GetMapping("/testCache")
public String testCache(String name) {
System.out.println(new Date() + ">>>" + name);
return "hello " + name;
}
2.在 hystrix 的请求方法中,添加 @CacheResult 注解
@HystrixCommand(fallbackMethod = "error")
@CacheResult
// @CacheResult表示该方法的请求结果会被缓存起来,默认情况下,缓存的 key 就是方法的参数,
// 缓存的 value 就是方法的返回值。
public String testCache(String name) {
return restTemplate.getForObject("http://provider/testCache?name={1}", String.class, name);
}
3.初始化 HystrixRequestContext,初始化完成后,缓存开始生效,HystrixRequestContext 关闭之后,缓存失效。
@GetMapping("/testCache")
public void testCache() {
// 缓存开始生效
HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
String s= helloService.testCache("zhangsan");
s= helloService.testCache("zhangsan");
// 缓存失效
ctx.close();
}
这里调用了两遍testCache方法,但是provider中只会输出一次信息,因为第二次是缓存数据。
默认情况下,缓存的 key 就是所调用方法的参数,如果参数有多个,就是多个参数组合起来作为key
@HystrixCommand(fallbackMethod = "error")
@CacheResult
public String testCache2(String name, Integer age) {
return restTemplate.getForObject("http://provider/testCache?name={1}&age={2}",
String.class, name, age);
}
此时缓存的 key 就是 name+age,但是,如果有多个参数,又只想使用其中一个作为缓存的 key,那么可以通过 @CacheKey 注解来指定缓存的key
@HystrixCommand(fallbackMethod = "error")
@CacheResult
public String testCache2(@CacheKey String name, Integer age) {
return restTemplate.getForObject("http://provider/testCache?name={1}&age={2}",
String.class, name, age);
}
此时虽然有两个参数,但是缓存时以 name 作为缓存的key。也就是说,两次请求中,只要 name 一
样,即使 age 不一样,第二次请求也会使用第一次请求缓存的结果。
- @CacheRemove,表示删除缓存的数据
@CacheRemove在使用时,必须指定 commandKey 属性:缓存方法的名字
例如如下方法定义缓存与删除缓存:
@HystrixCommand(fallbackMethod = "error")
@CacheResult
// @CacheResult表示该方法的请求结果会被缓存起来,默认情况下,缓存的 key 就是方法的参数,
// 缓存的 value 就是方法的返回值。
public String testCache(String name) {
return restTemplate.getForObject("http://provider/testCache?name={1}", String.class, name);
}
@HystrixCommand
@CacheRemove(commandKey = "testCache")
// 删除缓存,必须指定 commandKey 属性:缓存方法的名字
public String deleteUserByName(String name) {
return null;
}
@GetMapping("/testCache2")
public void testCache2() {
// 缓存开始生效
HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
//第一请求完,数据已经缓存下来了
String javaboy = helloService.testCache("zhangsan");
//删除数据,同时缓存中的数据也会被删除
helloService.deleteUserByName("zhangsan");
//第二次请求时,虽然参数还是 zhangsan,但是缓存数据已经没了,所以这一次,provider 还是会收到请求
javaboy = helloService.testCache("zhangsan");
// 缓存失效
ctx.close();
}
Hystrix 请求合并
如果 consumer 中,频繁的调用 provider 中的同一个接口,在调用时,只是参数不一样,那么这样情况下,就可以将多个请求合并成一个,这样可以有效提高请求发送的效率。
添加 @HystrixCollapser 注解,batchMethod指定批处理的方法,collapserProperties指定请求合并的条件属性
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
@HystrixCollapser(batchMethod = "getUsersByIds",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "200")})
public Future<User> getUserById(Integer id) {
return null;
}
@HystrixCommand
public List<User> getUsersByIds(List<Integer> ids) {
User[] users = restTemplate.getForObject("http://provider/user/{1}", User[].class, StringUtils.join(ids, ","));
return Arrays.asList(users);
}
}
测试示例:
@GetMapping("/testRequestMerge")
public void testRequestMerge() throws ExecutionException, InterruptedException {
HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
Future<User> q1 = userService.getUserById(99);
Future<User> q2 = userService.getUserById(98);
Future<User> q3 = userService.getUserById(97);
User u1 = q1.get();
User u2 = q2.get();
User u3 = q3.get();
System.out.println(u1);
System.out.println(u2);
System.out.println(u3);
Thread.sleep(2000);
Future<User> q4 = userService.getUserById(96);
User u4 = q4.get();
System.out.println(u4);
ctx.close();
}
参考文章:http://itboyhub.com/2021/01/31/spring-cloud-hystrix/