Introduction
Spring cloud provides the Hystrix fault tolerance library to implement a downgrade strategy for methods configured with circuit breakers and temporarily invoke alternate methods when services are unavailable. This article will create a product microservice, register it with the eureka service registry, then we use the web client to access the /products
API to get the product list, and when the product service fails, call the local backup method to degrade but provide services normally.
basic environment
- JDK 1.8
- Maven 3.3.9
- IntelliJ 2018.1
- Git
Project source code
Add products and services
Create a new maven project in intelliJ with the following configuration
- groupId: cn.zxuqian
- artifactId: productService
Then add the following code to pom.xml:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zxuqian</groupId>
<artifactId>productService</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>Finchley.M9</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
We went ahead and used it spring-cloud-starter-netflix-eureka-client
to automatically register the product service with the eureka service. Then also used to spring-cloud-starter-config
read the configuration file of the configuration service center. This project is just a simple spring web project.
src/main/resources
Create a bootstrap.yml
file under and add the following:
spring:
application:
name: product-service
cloud:
config:
uri: http://localhost:8888
Create a file in the git repository of the configuration center, product-service.yml
add the following configuration and submit:
server:
port: 8081
This configuration specifies port 8081 for product services. Then create a Application
class and add the following code:
package cn.zxuqian;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableDiscoveryClient
The annotation will instruct spring cloud to automatically register this service with eureka. Finally, create a cn.zxuqian.controllers.ProductController
controller, provide an /products
API, and return sample data:
package cn.zxuqian.controllers;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@RequestMapping("/products")
public String productList() {
return "外套,夹克,毛衣,T恤";
}
}
Configure the web client
Open the web
project we created earlier pom.xml
and add a new Hystrix
dependency in:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
Then update Application
the code of the class:
package cn.zxuqian;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
}
This is used @EnableCircuitBreaker
to turn on the circuit breaker function, and then also add a rest
method and use the @Bean
annotation. This part belongs to Spring's dependency injection feature, using @Bean
the marked method will tell how to initialize such an object, for example, in this case it is used RestTemplateBuilder
to create an RestTemplate
object, which will be used later in the service using the circuit breaker.
Create a cn.zxuqian.service.ProductService
class and add the following code:
package cn.zxuqian.services;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class ProductService {
private final RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public ProductService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@HystrixCommand(fallbackMethod = "backupProductList")
public String productList() {
List<ServiceInstance> instances = this.discoveryClient.getInstances("product-service");
if(instances != null && instances.size() > 0) {
return this.restTemplate.getForObject(instances.get(0).getUri() + "/products", String.class);
}
return "";
}
public String backupProductList() {
return "夹克,毛衣";
}
}
The reason for creating a Service class is because Hystrix can only be used in classes marked with @Service
or @Component
, so that the API provided by Spring Context can be used normally. This will be explained later when we go deeper into Spring. After using the @HystrixCommand
annotation, Hystrix will monitor the annotated method productList
(the bottom layer uses a proxy to wrap this method to achieve monitoring). Once the errors of this method accumulate to a certain threshold, the circuit breaker will be activated, and all subsequent productList
requests to call the method will be fails, the fallbackMethod
specified method is called temporarily backupProductList()
, and then when the service returns to normal, the circuit breaker is closed. We also use DiscoveryClient
the uri address to find the product service in this class, use spring.application.name
the value of the configuration item of the product service, that is, product-service
as the method serviceID
passed to discoveryClient.getInstances()
, and then return a list, because currently we only have one product service started, so only You need to take the uri address of the first instance. Then we use RestTemplate
the api to access the product service. Note that Spring's constructor injection is used here, that is, @Bean
the method we used to annotate before will be used to initialize the restTemplate
variable, and we don't need to initialize it manually. RestTemplate
The class provides getForObject()
methods to access other Rest APIs and wrap the results in the form of objects. The first parameter is the uri address of the api to be accessed, and the second parameter is the type of the result obtained. Here we return a String, so pass to him String.class
. backupProductList()
The method returns the downgraded product list information.
Finally create a controller cn.zxuqian.controllers.ProductController
and add the following code:
package cn.zxuqian.controllers;
import cn.zxuqian.services.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/products")
public String productList() {
return productService.productList();
}
}
Here is used to provide data ProductService
for the /products
path.
test
First, we use the spring-boot:run
plug-in to start the configuration center service, config-server, then start eureka-server, then product-service, and finally start the web client, wait for a while and wait for the eureka service to be successfully registered to access http://localhost:8080/products
, under normal circumstances, the 外套,夹克,毛衣,T恤
result will be obtained , then we close product-service, and then access the same path, we will get the downgraded result:夹克,毛衣
Welcome to my blog http://zxuqian.cn/spring-cloud-tutorial-hystrix/