Hystrix整合Spring

Hystrix整合Spring

Hystrix主要用于保护调用服务的一方,如果被调用的服务发生故障,符合一定的条件,就开启断路器,对调用的程序进行隔离。

下面就一个测试项目入口,看看怎么在Spring中使用Hystrix。

测试项目分为3个部分:

  • hystrix-spring-server:作为Eureka服务端,监听端口8761
  • hystrix-spring-provider:提供REST服务,监听8080端口。提供一个/person/{personId}接口
  • hystrix-spring-invoker:服务调用方,监听9000端口,提供一个/router/{personId}接口

hystrix-spring-server

pom文件依赖为:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>

启动程序为:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(ServerApplication.class).run(args);
}
}

hystrix-spring-provider

pom文件依赖为:

1
2
3
4
5
6
7
8
9
10
<dependencies>
<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>
</dependencies>

REST controller为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@RestController
public class PersonController {

@RequestMapping(value = "/person/{personId}", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) {
Person person = new Person();
person.setId(personId);
person.setName("Crazyit");
person.setAge(33);
person.setMessage(request.getRequestURL().toString());
return person;
}

@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() throws Exception {
Thread.sleep(800);
return "Hello World";
}

@RequestMapping(value = "/persons", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Person> findPersons(@RequestBody List<Integer> personIds, HttpServletRequest request) {
List<Person> result = new ArrayList<Person>();
for(Integer id : personIds) {
Person person = new Person();
person.setId(id);
person.setName("angus");
person.setAge(new Random().nextInt(30));
person.setMessage(request.getRequestURL().toString());
result.add(person);
}
return result;
}
}

perosn 类为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Person {

private Integer id;

private String name;

private Integer age;

private String message;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}


}

启动类为

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@EnableEurekaClient
public class ProviderApplication {

public static void main(String[] args) {
// 设置启动的服务器端口
new SpringApplicationBuilder(ProviderApplication.class).properties(
"server.port=8080").run(args);
}
}

hystrix-spring-invoker

pom依赖为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<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.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>

Rest Controller为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@Configuration
public class InvokeController {
@Autowired
private PersonService personService;

@RequestMapping(value = "/router/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Person router(@PathVariable Integer personId) {
Person p = personService.getPerson(personId);
return p;

}
}

Person类同hystrix-spring-provider中的person

PersonService为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class PersonService {

@Autowired
private RestTemplate restTemplate;

@HystrixCommand(fallbackMethod = "getPersonFallback")
public Person getPerson(Integer personId) {
Person p = restTemplate.getForObject("http://spring-hystrix-provider/person/{personId}", Person.class, personId);
return p;
}

public Person getPersonFallback(Integer id) {
Person p = new Person();
p.setId(0);
p.setAge(-1);
p.setName("Error");
p.setMessage("Request error");
return p;
}
}

我们使用注解@HystrixCommand修饰getPerson方法,再配置一个fallbackMethod为getPersonFallback方法。被@HystrixCommand修饰的方法,会被AspectJ进行代理,Spring会将相关的类转换为Bean放到容器中。

最后启动类为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@ServletComponentScan
public class InvokeApplication {

@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(InvokeApplication.class, args);
}

}

使用注解@EnableCircuitBreaker,启用断路器。

启动三个服务之后,在浏览器中输入http://localhost:9000/router/2

就能看到正常结果了

然后,关闭hystrix-spring-provider,就能看到断路器发挥了作用

缓存注解

缓存与合并请求功能需要先初始化请求上下文才能实现。新建一个javax.servlet.filter,用于创建与销毁Hystrix的请求上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebFilter(urlPatterns = "/*", filterName = "hystrixFilter")
public class HystrixFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
context.shutdown();
}
}

public void destroy() {
}
}

添加CacheService:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class CacheService {
@CacheResult
@HystrixCommand
public Person getPerson(Integer id) {
System.out.println("执行getPerson方法");
Person p = new Person();
p.setId(id);
p.setName("name");
return p;
}
}

然后在InvokeController中添加/cache/{personId}

1
2
3
4
5
6
7
8
@RequestMapping(value = "/cache/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Person cacheResult(@PathVariable Integer personId) {
for (int i = 0;i < 3;i++) {
cacheService.getPerson(personId);
System.out.println("控制器调用服务 " + i);
}
return new Person();
}

调用接口/cache/2之后。可以正常获取结果,

查看后天,可以发现,只有getPerson方法只执行了一次,其他的都是走的缓存。

缓存相关的注解主要有以下3个:

  • @CacheResult:该注解修饰的方法,表示被修饰的方法返回结果将会被缓存,需要配合@HystrixCommand一起使用
  • @CacheRemove:用于修饰方法让缓存失效,需要与@CacheResult的缓存key关联
  • @CacheKey:用于修饰方法参数,表示该参数作为缓存的key
1
2
3
4
5
6
7
8
9
10
11
@CacheResult()
@HystrixCommand(commandKey = "removeKey")
public String cacheMethod(String name) {
return "hello"
}

@CacheRemove(commandKey = "removeKey")
@HystrixCommand
public String updateMethod(String name) {
return "update";
}

以上代码片段中的cacheMethod方法,使用的缓存key为removeKey,方法updateMethod被调用后,将会删除key为removeKey的缓存。

合并请求注解

在Spring Cloud中同样支持合并请求,在一次HTTP请求的过程中,收集一段时间内的相同请求,放到一个批处理命令中执行。实现合并请求,同样需要先初始化请求上下文。

新增CollapseService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class CollapseService {

@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 (int id : ids) {
Person p = new Person();
p.setId(id);
p.setName(String.valueOf(id));
ps.add(p);
}
return ps;
}
}

在InvokerController中添加新的/collapse接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value = "/collapse", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String collapse() 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 "";
}

使用

注解@HystrixCollapser(batchMethod = “getPersons”, collapserProperties = {
@HystrixProperty(name = “timerDelayInMilliseconds”, value = “1000”)
})

设置批量方法为getPersons,然后设置时间为1S。即,如果在1S的有相同的请求,就合并,而后调用getPersons方法。

从执行结果看,最终是只执行了getPersons方法。

参考资料

疯狂Spring Cloud微服务架构实战

0%