分布式环境中,可能会有一些被依赖的服务会失效,影响系统的稳定运行。
Hystrix通过添加延迟阈值以及容错的逻辑,以控制分布式系统间组件的交互。
Hystrix通过隔离服务间的访问点、停止它们之间的级联故障、提供可回退操作来实现容错。
下面例子为在Spring Cloud的使用。
开发工具:IntelliJ IDEA 2019.2.3
一、服务器端
1、创建项目
IDEA中创建一个新的SpringBoot项目,名称为“spring-hystrix-server”,SpringBoot版本选择2.1.10,在选择Dependencies(依赖)的界面勾选Spring Cloud Discovery ->
Eureka Server,创建完成后的pom.xml配置文件自动添加SpringCloud最新稳定版本依赖,当前为Greenwich.SR3。
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 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、修改配置application.yml
修改端口号为8761;取消将自己信息注册到Eureka服务器,不从Eureka服务器抓取注册信息。
server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false
3、修改启动类代码
增加注解@EnableEurekaServer
二、服务提供者
1、创建项目
IDEA中创建一个新的SpringBoot项目,除了名称为“spring-hystrix-provider”,其它步骤和上面创建服务器端一样。
2、修改配置application.yml
server: port: 8080 spring: application: name: spring-hystrix-provider eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8761/eureka/
3、添加一个实体类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、修改启动类代码
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; } }
三、服务调用者
1、创建项目
IDEA中创建一个新的SpringBoot项目,名称为“spring-hystrix-invoker”,SpringBoot版本选择2.1.10,在选择Dependencies(依赖)的界面勾选Spring Cloud Discovery -> Eureka Server,Spring Cloud Circuit Breaker -> Hystrix。
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 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、修改配置application.yml
server: port: 9000 spring: application: name: spring-hystrix-invoker eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8761/eureka/
3、修改启动类代码
增加注解@EnableEurekaClient和@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、添加一个实体类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、添加一个服务类,在服务方法中调用服务
服务方法用@HystrixCommand注解进行修饰,并配置了回退方法。
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、添加控制器 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; } }
四、测试
1、启动服务器端。
浏览器访问http://localhost:8761/,正常
2、启动服务提供者
浏览器访问 http://localhost:8080/user/小明
页面输出:{"name":"小明","age":30}
3、启动服务调用者。
浏览器访问http://localhost:9000/invokeUser/小明
页面输出:{"name":"小明","age":30}
4、停止8080服务提供者,再访问9000地址
页面输出:{"name":"无名","age":20}
由此可见,由于调用失败,触发了回退方法。
五、缓存注解
Hystrix支持缓存功能,如果在一次请求过程中,多个地方调用同一个接口,可以考虑使用缓存。
缓存可以通过注解实现,缓存与合并请求功能需要先初始化请求上下文才能实现。
1、新建一个javax.servlet.Filter,用于创建与销毁Hystrix的请求上下文
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、在启动类中加入注解@ServletComponentScan
3、编写服务方法,使用缓存注解
(1)@CacheResult 被修饰的方法返回结果会被缓存,和@HystrixCommand搭配
(2)@CacheRemove 让缓存失效
(3)@CacheKey 修饰方法参数,表示该参数作为缓存的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、控制器中调用
浏览器访问:http://localhost:9000/cacheMethod,IDEA控制台输出:
控制器调用服务:0 执行cacheMethod方法 控制器调用服务:1 控制器调用服务:2 控制器调用服务:3 控制器调用服务:4 控制器调用服务:5 执行updateMethod方法 控制器调用服务:6 执行cacheMethod方法 控制器调用服务:7 控制器调用服务:8 控制器调用服务:9
六、Feign与Hystrix整合
修改服务调用者项目的代码
1、pom.xml加入Feign依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2、修改配置application.yml
增加下面配置:打开Feign的Hystrix的配置
feign: hystrix: enabled: true
3、启动类加上注解@EnableFeignClients
4、新建Feign接口
package 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、控制器InvokerController,添加代码
@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; }