持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
13、在SpringCloud中使用Hystrix
Hystrix 主要用于保护调用服务的一方,如果被调用的服务发生故障,符合一定条件, 就开启断路器,对调用的程序进行隔离。
本节中的例子所使用的项目如下所述。(结构相似,沿用以前的项目)
- spring-hystrix-server :Eureka 服务器
- spring-hystrix-provider :服务提供者,提供/person/ {personid }服务,/hello 服务
- spring-hystrix-invoker :服务调用者
1、整合 Hystrix
为服务调用者 invoker 项目添加依赖:
<!--Hystrix 整合SpringCloud 的依赖 start-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframewore.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.12</version>
</dependency>
<!--Hystrix 整合SpringCloud 的依赖 end-->
在服务调用者的启动类中,加入启用断路器的注解:
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker // 启用断路器
public class SrlInvokerServerApplication {
public static void main(String[] args) {
SpringApplication.run(SrlInvokerServerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
新建服务类,在服务方法中调用服务。
@Component
public class PersonService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getPersonFallback")
public Person getPerson(Integer id) {
// 使用RestTemplate调用Eureka服务
Person person = restTemplate.getForObject("http://provider-server/person/{personId}", Person.class, id);
return person;
}
/**
* 回退方法,返回一个默认的Person
*/
public Person getPersonFallback(Integer id) {
Person p = new Person();
p.setId(0);
p.setName("crazy");
p.setAge("-1");
p.setMessage("request error");
return p;
}
}
服务类中注入了 RestTemplate ,服务方法使用@HystrixCommand 注解进行修饰,并且配置了回退方法。@HystrixCommand 注解由 Hystrix的 javanica 项目提供,该项目主要是为了 简化 Hystrix 的使用。被@HystrixCommand 修饰的方法, Hystrix(javanica )会使用AspectJ对其进行代理, Spring 会将相关的类转换为 Bean 放到容器中,在 Spring Cloud中,我们无须过多关心 Hystrix 的命令管理
编写控制器,调用服务类的方法:
@RestController
@Configuration
public class HystrixInvokerController {
@Autowired
private PersonService personService;
@RequestMapping(value = "/router/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Person router(@PathVariable Integer personId) {
Person person = personService.getPerson(personId);
return person;
}
}
启动各个项目: 访问该控制器结果为: {"id":1,"age":"张三","name":"克罗斯","message":"http://localhost:8888/person/1"} 停止服务提供者,调用失败,结果为:触发了回退的方法 {"id":0,"age":"-1","name":"crazy","message":"request error"}
2、命令配置
在Spring Cloud 中使用@HystrixCommand 来声明一个命令,命令的相关配置也可以 该注解中进行,以下的代码片断配置了几个属性:
下面代码的配置,个人认为只对一个命令起作用,不是全局配置
/**
* 测试配置,对3 个key进行命名
* 设置命令执行超时时间为 1000 毫秒
* 设置命令执行的线程池大小为 1
*/
@HystrixCommand(
fallbackMethod = "getPersonFallback", groupKey = "MyGroup",
commandKey = "MyCommandKey", threadPoolKey = "MyCommandPool",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")},
threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "1")}
)
public Person getPerson() {
// 测试配置
return null;
}
/**
* 声明忽略MyException,如果方法抛出MyException,则不会触发回退
*/
@HystrixCommand(ignoreExceptions = {MyException.class},fallbackMethod = "testExceptionFallBack")
public String testException(){
throw new MyException();
}
除了以上的几个配置外,@HystrixComman 注解还可以使用 ignoreExceptions 来处理 异常的传播。
Hystrix 的命令、线程配置较多 ,由于篇幅所限,本小节仅简单地列举几个,读者可举一反三,按需要进行配置。。
3、默认配置
对于一些默认的配置,例如命令组的 key 等,可以使用 @DefaultProperties 注解,这样就 减少了@HystrixCommand 注解的代码量。 以下代码片断展示了如何使用@DefaultProperties:
@DefaultProperties(groupKey = "GroupPersonKey")
public class PersonService {
/**
* 测试默认配置
*/
@HystrixCommand // groupkey 将使用 GroupPersonKey
public Person getPersonTest() {
// 测试默认配置
return null;
}
}
除了定义 GroupKey 外, 还支持 @HystrixCornmand 的其余配置,例如线程属性、命令属性等
4、缓存注解
Hystrix 的缓存功能,在 Spring Cloud 中,同样支持使用缓存, 并且可以通过注解来实现。根据前面章节的介绍可知 ,缓存与合并请求功能需要先初始化请求上下文才能实现。新建一个 javax.servlet. Filter ,用于创建与销毁 Hystrix 的请求上下文。
/**
* Hystrix的缓存功能和合并请求需要初始化请求上下文才能实现。
* 此过滤器用于拦截所有请求,并创建和销毁Hystrix的请求上下文。
* springBoot 添加过滤器,只用WebFilter不行,需要加Configuration
*/
@WebFilter(urlPatterns = "/*", filterName = "hystrixFilter")
@Configuration
public class HystrixFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
// 过滤器执行了
System.out.println("过滤器执行了");
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
@Override
public void destroy() {
}
}
编写服务方法,使用@CacheResult注解进行修饰
@Component
public class CacheService {
@CacheResult
@HystrixCommand
public Person getPerson(Integer id) {
System.out.println("执行getPerson方法");
Person p = new Person();
p.setId(id);
p.setName("angus");
return p;
}
}
注意 ,在服务方法中,被调用一次就会进行一次控制台输出 。在控制器的方法中,调 用多次 getPerson 方法。
@RequestMapping(value = "/cache1/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Person testCacheResult(@PathVariable Integer personId) {
//调用多次服务
for (int i = 0; i < 3; i++) {
Person person = cacheService.getPerson(personId);
System.out.println("控制器调用服务:" + i);
}
return new Person();
}
根据输出结果可知 在一次用户请求的过程中 ,服务方法只执行了一次 缓存生效。
过滤器执行了
执行getPerson方法
控制器调用服务:0
控制器调用服务:1
控制器调用服务:2
缓存的注解主要有以下 3 个。
- @CacheResult :该注解修饰方法,表示被修饰的方法返回结果将会被缓存,需要与@HystrixCommand 一起使用。
- @CacheRemove :用于修饰方法让缓存失效,需要与@CacheResult 的缓存 key 关联
- @CacheKey :用于修饰方法参数,表示该参数作为缓存的 key
前面的例子使用了@CacheResult 注解,下面的代码片断,@CacheResult与@CacheRemove一起使用:
// 使用的缓存 key 为 removeKey
@CacheResult
@HystrixCommand(commandKey = "removeKey")
public String cacheMethod(){
return "hello";
}
// 此方法被调用之后,将会删除key为 removeKey 的缓存
@CacheRemove(commandKey = "removeKey")
@HystrixCommand
public String updateMethod(String name){
return "update";
}
以上代码片断中的 cacheMethod 方法,使用的缓存 key为removeKey 方法 updateMethod 被调用后,将会删除 key为 removeKey 的缓存。关于3个缓存注解更深入的使用,本小 节不进行讲述,读者可以自行测试。
5、合并请求注解
在 Spring Cloud 中同样支持合并请求,在一次 HTTP 请求的过程中,收集一段时间内的相同请求,放到一个批处理命令中执行。 实现合并请求,同样需要先初始化请求上下文,
/**
* 测试Spring Cloud与Hystrix整合之后,合并请求
*/
@Component
public class CollapseService {
// 配置收集1秒内的请求
@HystrixCollapser(batchMethod = "getPersons", collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "1000")
})
public Future<Person> getSinglePerson(Integer id) {
// 此方法不会被执行
System.out.println("执行单个获取的方法");
return null;
}
@HystrixCommand
public List<Person> getPersons(List<Integer> ids) {
System.out.println("收集请求,参数数量:" + ids.size());
List<Person> ps = new ArrayList<Person>();
for (Integer id : ids) {
Person p = new Person();
p.setId(id);
p.setName(" crazyit");
ps.add(p);
}
return ps;
}
}
在代码清单中,最后真实执行的方法为 getPersons, getSinglePerson 方法使用了 @Hystrix Collapser 注解来修饰,会收集1秒内调用 getSinglePerson的请求,放到 getPersons方法中进行批处理。
编写控制器:异步执行了3次getSinglePerson方法
@RequestMapping(value = "/" +
"", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String testCollapse() throws Exception {
// 连续执行三次请求
Future<Person> f1 = collapseService.getSinglePerson(1);
Future<Person> f2 = collapseService.getSinglePerson(2);
Future<Person> f3 = collapseService.getSinglePerson(3);
Person p1 = f1.get();
Person p2 = f2.get();
Person p3 = f3.get();
System.out.println(p1.getId() + "----" + p1.getName());
System.out.println(p2.getId() + "----" + p2.getName());
System.out.println(p3.getId() + "----" + p3.getName());
return "";
}
根据输出结果可知,最终只执行了 getPersons 方法。相对于直接使用 Hystrix ,在 Spring Cloud 中合并请求较为简单,合并处理器已经由@HystrixCollapser 注解帮我们实现, 我们仅关心真正命令的执行即可。
过滤器执行了
收集请求,参数数量:3
1---- crazyit
2---- crazyit
3---- crazyit
6、Feign与Hystrix整合
Feign对Hystrix 提供了支持,为“服务调用者”加入以下 Feign 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
在application.yml中打开Feign的Hystrix开关,请见一下配置:
#打开Feign的Hystrix的开关,默认false
feign.hystrix.enabled=true
在启动类中加入Feign的开关,本小节的"服务调用者"的启动类所使用的注解如下:
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker // 启用断路器
新建Feign接口:
/**
* 测试 feign 和 Hystrix 整合
* 默认会生成一个"provider-server"为ID的JavaBean在Spring容器里,如果多个这样的接口,@FeignClient值是一样的会报错
* 解决方案:spring.main.allow-bean-definition-overriding=true #当遇到同样名字的时候,是否允许覆盖注册,默认false
* 还有就是 一个服务 例如,provider,只写一个接口
*/
@FeignClient(name = "provider-server",fallback = HelloClient.HelloClientFallback.class)
public interface HelloClient {
@RequestMapping(method = RequestMethod.GET,value = "/errorHello")
public String hello();
@Component
static class HelloClientFallback implements HelloClient{
@Override
public String hello() {
System.out.println("hello 方法的回退");
return "error hello";
}
}
}
与普通 Feign 客户端无异,仅仅设置了处理回退的类,回退类实现了客户端接口。 为了能测试效果,修改服务器端的/he llo 服务,让其有 800 毫秒的延时。根据前面章节的介绍可知,默认情况下, Hystrix 的超时时间为1 秒,因此,还需要修改超时设置。
#修改超时设置 -- HelloClient#hello() 接口的配置
hystrix.command.HelloClient#hello().execution.isolation.thread.timeoutInMilliseconds=500
#断路器10秒内接受超过3次请求,并且错误率超过50% -- HelloClient#hello() 接口的配置
hystrix.command.HelloClient#hello().circuitBreaker.requestVolumeThreshold=3
##默认时间段内发生的请求数 -- 全局配置 default
#hystrix.command.default.circuitBreaker.requestVolumeThreshold=2
##超时时间 -- 全局配置 default
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=800
## 针对某个客户端,使用下面的配置片段 主要控制 CommandKey 变化
#hystrix.command.CommandKey.circuitBreaker.requestVolumeThreshold=2
Feign 与Hystrix 整合使用时, 会自动帮我们生成 CommandKey,格式为: Feign 客户端接口名#方法名()。例如本例中的客户端为 HelloClient 方法为 hello, 生成的 CommandKey为HelloClient#hello() 。而默认情况下 生成的 GroupKey为 @FeignClient 注解的 name属性。 在以上的配置中,我们针对 hello 方法设置了超时时间为 500 毫秒,而/hello 服务超时时间为 800 毫秒,换言之, hello 方法总会超时。另外 ,如果请求超过3次 并且失败率超过 50% ,断路器将被打开。编写控制器,调用 hello 服务,并查看断路器的情况,请见代码
@RestController
public class HelloController {
@Autowired
HelloClient helloClient;
@RequestMapping(value = "/feign/hello",method = RequestMethod.GET)
public String feignHello() {
// hello 方法会超时
String helloResult = helloClient.hello();
// 获取断路器
HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory.getInstance(HystrixCommandKey.Factory
.asKey("HelloClient#hello()"));
System.out.println("断路器状态:" + breaker.isOpen());
return helloResult;
}
}
在控制器的方法中,获取了 hello 方法的断路器,并输出其状态 。接下来,编 写一个测试客户端,多线程访问 http://localhost:9000/feign/hello/{index},也就是控制器的feignHello 方法。
public class TestFeignClient {
public static void main(String[] args) throws Exception {
//创建默认的HttpClient
final CloseableHttpClient httpClient = HttpClients.createDefault();
// 调用多次服务并输出结果
for (int i = 0; i < 6; i++) {
// 建立线程访问接口
Thread t = new Thread() {
public void run() {
try {
String url = "http://localhost:9000/feign/hello";
// 调用GET方法请求服务
HttpGet httpGet = new HttpGet(url);
// 获取响应
HttpResponse response = httpClient.execute(httpGet);
// 根据响应解析出字符串
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
// 等待完成
Thread.sleep(15000);
}
}
结果如下: 配置超过的请求数为 3,为什么第四个还是 false,测试结果如下: 配置 10 的时候,11个为true,配置 3的时候,第5个为true,配置 4 的时候,第5个true。 配置数为 奇数时,会加 1 不知原因。
断路器状态: false
断路器状态: false
断路器状态: false
断路器状态: false
断路器状态: true
断路器状态: true
7、Hystrix监控
为服务调用者加入 Actuator 可以对服务调用者的健康情况进行实时监控。例如可以看到某个方法的断路器是否打开、当前负载等情况,为服务调用者( spring-hystrix-invoker)加入以下依赖:
<!-- Hystrix 监控 依赖 start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>1.5.12</version>
</dependency>
<!-- Hystrix 监控 依赖 end -->
还需要注入一个Bean
/**
* 只有加入此Bean,Hystrix监控访问 http://localhost:9000/hystrix.stream 才有效果
*/
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registration = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registration.addUrlMappings("/hystrix.stream");
return registration;
}
重新启动 spring-hystrix-invoker ( 9000 端口),访问 http://localhost:9000/hystrix.stream, 可以看到 Hystrix 输出的 stream 数据。接下来,新建一个监控的 Web 项目,名称为hystrix-dashboard, 对应的代码路径为 codes\06\6.4\hystrix-dashboard 为该项目加入以下依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</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>
<version>1.5.3.RELEASE</version>
</dependency>
</dependencies>
启动类 添加 : 配置文件配置端口
@SpringBootApplication
@EnableHystrixDashboard
public class SrlHystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(SrlHystrixDashboardApplication.class, args);
}
}
使用了@EnableHystrixDashboard 注解开启 Hystrix 控制台,启动的端口为 8082 。完成后,启动整个集群,最后再启动监控项目,在浏览器输入以下地址 http://localhost: 8082/hystrix ,可以看到界面如图 所示。
在文本框中输入需要监控的地址 本例需要监控的地址是 http://localhost:9000/ hystry.stream。单击监控按钮后
如图 6-6 所示,名称为 HelloClient#hello() 的命令, 断路器被打开,该命令主要用于测 试Feign 的断路器。 其他断路器状态正常, 读者可以访问 本节案例中的各个URL 来测试这些命令的健康情况。 本节案例所监测的是单节点的健康情况* 如果需要监控整个集群的情况, 可以使用Turbine 框架 ,自己百度,和 这个一样