In a distributed environment, some dependent services may fail, affecting the stable operation of the system.
Hystrix controls the interaction of components between distributed systems by adding delay thresholds and fault-tolerant logic.
Hystrix achieves fault tolerance by isolating access points between services, stopping cascading failures between them, and providing fallback operations.
The following example is for use in Spring Cloud.
Development tools: IntelliJ IDEA 2019.2.3
One, server side
1. Create a project
Create a new SpringBoot project in IDEA, the name is "spring-hystrix-server", SpringBoot version select 2.1.10, in the selection of Dependencies (dependencies) interface, check Spring Cloud Discovery ->
Eureka Server, create the pom after completion. The xml configuration file automatically adds dependencies on the latest stable version of SpringCloud, currently Greenwich.SR3.
The complete content of pom.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-hystrix-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-hystrix-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Modify the configuration application.yml
Modify the port number to 8761; cancel the registration of your information to the Eureka server, and do not grab the registration information from the Eureka server.
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
3. Modify the startup code
Add annotation @EnableEurekaServer
2. Service provider
1. Create a project
Create a new SpringBoot project in IDEA, except that the name is "spring-hystrix-provider", the other steps are the same as the server-side creation above.
2. Modify the configuration application.yml
server:
port: 8080
spring:
application:
name: spring-hystrix-provider
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
3. Add an entity class User.java
package com.example.springhystrixprovider;
public class User {
String name;
Integer age;
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;
}
}
4. Modify the startup code
package com.example.springhystrixprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class SpringHystrixProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringHystrixProviderApplication.class, args);
}
@RequestMapping("/hello")
public String hello(HttpServletRequest request) {
return "hello world." + request.getServerPort();
}
@RequestMapping(value="/user/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
public User user(@PathVariable String name) {
User u = new User();
u.setName(name);
u.setAge(30);
return u;
}
}
Three, the service caller
1. Create a project
Create a new SpringBoot project in IDEA, the name is "spring-hystrix-invoker", SpringBoot version select 2.1.10, in the interface of selecting Dependencies (dependencies) check Spring Cloud Discovery -> Eureka Server, Spring Cloud Circuit Breaker -> Hystrix.
The complete content of pom.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-hystrix-invoker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-hystrix-invoker</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Modify the configuration application.yml
server:
port: 9000
spring:
application:
name: spring-hystrix-invoker
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
3. Modify the startup code
Added annotations @EnableEurekaClient and @EnableCircuitBreaker.
package com.example.springhystrixinvoker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class SpringHystrixInvokerApplication {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringHystrixInvokerApplication.class, args);
}
}
4. Add an entity class User.java
package com.example.springhystrixinvoker;
public class User {
String name;
Integer age;
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;
}
}
5. Add a service class and call the service in the service method
The service method is decorated with @HystrixCommand annotation, and the fallback method is configured.package com.example.springhystrixinvoker;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
public class UserService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getUserFallback")
public User getUser(String name){
User user = restTemplate.getForObject("http://spring-hystrix-provider/user/{name}", User.class, name);
return user;
}
public User getUserFallback(String name){
User user = new User();
user.setName("无名");
user.setAge(20);
return user;
}
}
6. Add the controller InvokerController.java
package com.example.springhystrixinvoker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class InvokerController {
@Autowired
private UserService userService;
@RequestMapping(value = "/invokeUser/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public User invokeUser(@PathVariable String name){
User user = userService.getUser(name);
return user;
}
}
Four, test
1. Start the server.
Browser visit http://localhost:8761/, normal
2. Start the service provider
Browser visit http://localhost:8080/user/ Xiaoming
page output: {"name":"小明","age":30 }
3. Start the service caller.
Browser visit http://localhost:9000/invokeUser/Xiaoming
page output: {"name":"Xiaoming","age":30}
4. Stop the 8080 service provider, and then visit the 9000 address
page output: {"name ":"Unnamed","age":20}
It can be seen that the fallback method was triggered because the call failed.
Five, cache annotations
Hystrix supports caching. If the same interface is called in multiple places during a request , you can consider using caching.
Caching can be implemented through annotations, and the caching and merge request functions need to be initialized before the request context can be implemented.
1. Create a new javax.servlet.Filter to create and destroy Hystrix request context
package com.example.springhystrixinvoker;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*", filterName = "hystrixFilter")
public class HystrixFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try{
chain.doFilter(request,response);
}finally {
context.shutdown();
}
}
public void destroy() {
}
}
2. Add the annotation @ServletComponentScan to the startup class
3. Write service methods and use cache annotations
(1) @CacheResult The modified method returns the result will be cached, and @HystrixCommand collocation
(2) @CacheRemove invalidates the cache
(3) @CacheKey modifies the method parameter, which means that the parameter is used as the cache key
package com.example.springhystrixinvoker;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import org.springframework.stereotype.Component;
@Component
public class CacheService {
@CacheResult
@HystrixCommand(commandKey = "removeKey")
public String cacheMethod(String name){
System.out.println("执行cacheMethod方法");
return "hello";
}
@CacheRemove(commandKey = "removeKey")
@HystrixCommand(commandKey = "removeKey")
public String updateMethod(String name){
System.out.println("执行updateMethod方法");
return "update";
}
}
4. Call in the controller
Browser access: http://localhost:9000/cacheMethod, IDEA console output:控制器调用服务:0
执行cacheMethod方法
控制器调用服务:1
控制器调用服务:2
控制器调用服务:3
控制器调用服务:4
控制器调用服务:5
执行updateMethod方法
控制器调用服务:6
执行cacheMethod方法
控制器调用服务:7
控制器调用服务:8
控制器调用服务:9
6. Integration of Feign and Hystrix
Modify the code of the service caller project
1. Add Feign dependency to pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. Modify the configuration application.yml
Add the following configuration: Open Feign's Hystrix configuration
feign:
hystrix:
enabled: true
3. Add annotation @EnableFeignClients to the startup class
4. New Feign interfacepackage com.example.springhystrixinvoker;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
//指定调用的服务名称
@FeignClient(name="spring-hystrix-provider", fallback = UserClient.UserClientFallback.class)
public interface UserClient {
@RequestMapping(method = RequestMethod.GET, value = "/user/{name}")
User getUser(@PathVariable("name") String name);
@Component
static class UserClientFallback implements UserClient{
public User getUser(String name){
User user = new User();
user.setName("无名");
user.setAge(20);
return user;
}
}
}
5. Controller InvokerController, add code
@Autowired
private UserClient userClient;
@RequestMapping(value = "/invokeUser2/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public User invokeUser2(@PathVariable String name){
User user = userClient.getUser(name);
return user;
}