在分布式环境下,微服务之间不可避免的发生互相调用的情况,但是没有一个系统是能保证自身绝对正确的,在服务的调用过程中,很可能面临服务失败的问题,因此需要一个公共组件能够在服务通过网络请求访问其他微服务时,能对服务失效情况下有很强的容错能力,对微服务提供保护和监控。
Hystrix是netflix的一个开源项目,他能够在依赖服务失效的情况下,通过隔离系统依赖的方式,防止服务的级联失败(服务的雪崩)
对于服务的熔断机制,其实需要考虑两种情况
- 服务提供方存活,但调用接口报错
- 服务提供方本身就出问题了
服务提供方报错
其实这种情况类似于异常捕获机制,当出现异常,返回一个通用的接口报文
【springcloud-provider-product】 复制一份成为【springcloud-provider-product-hystrix】
【springcloud-provider-product-hystrix】修改pom文件,增加 Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
【springcloud-provider-product-hystrix】 修改ProductController
package cn.enjoy.controller;
import cn.enjoy.service.IProductService;
import cn.enjoy.vo.Product;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@RequestMapping("/prodcut")
public class ProductController {
@Resource
private IProductService iProductService;
@Resource
private DiscoveryClient client ; // 进行Eureka的发现服务
@RequestMapping(value="/get/{id}")
@HystrixCommand(fallbackMethod = "getFallback")
public Object get(@PathVariable("id") long id) {
Product product = this.iProductService.get(id);
if(product == null) {
throw new RuntimeException("该产品已下架!") ;
}
return product;
}
public Object getFallback(@PathVariable("id") long id){
Product product = new Product();
product.setProductName("HystrixName");
product.setProductDesc("HystrixDesc");
product.setProductId(0L);
return product;
}
@RequestMapping(value="/add")
public Object add(@RequestBody Product product) {
return this.iProductService.add(product) ;
}
@RequestMapping(value="/list")
public Object list() {
return this.iProductService.list() ;
}
@RequestMapping("/discover")
public Object discover() { // 直接返回发现服务信息
return this.client ;
}
}
一旦 get()方法上抛出了错误的信息,那么就认为该服务有问题
会默认使用“@HystrixCommand”注解之中配置好的fallbackMethod 调用类中的指定方法,返回相应数据
【springcloud-provider-product-hystrix】修改启动类,增加对熔断的支持
package cn.enjoy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@MapperScan("cn.enjoy.mapper")
@EnableEurekaClient
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ProductHystrixApp {
public static void main(String[] args) {
SpringApplication.run(ProductHystrixApp.class,args);
}
}
测试:localhost:8080/prodcut/get/100 访问
服务失连
在某些情况下,服务提供方并没有失效,但可能由于网络原因,服务的消费方并不能调用到服务接口,在这种情况下,直接在服务的提供方提供熔断机制依然还是不够的,这方面的处理需要在服务的消费方进行服务的回退(服务的降级)处理
服务的熔断:熔断指的是当服务的提供方不可使用的时候,程序不会出现异常,而会出现本地的操作调用,服务的熔断是在服务消费方实现的,在断网情况下服务提供方的任何处理都是没有意义的。
【springcloud-service】新增一个IProductClientService的失败调用(降级处理)
package cn.enjoy.service.fallback;
import cn.enjoy.service.IProductClientService;
import cn.enjoy.vo.Product;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class IProductClientServiceFallbackFactory implements FallbackFactory<IProductClientService> {
@Override
public IProductClientService create(Throwable throwable) {
return new IProductClientService() {
@Override
public Product getProduct(long id) {
Product product = new Product();
product.setProductId(999999L);
product.setProductName("feign-hystrixName");
product.setProductDesc("feign-hystrixDesc");
return product;
}
@Override
public List<Product> listProduct() {
return null;
}
@Override
public boolean addPorduct(Product product) {
return false;
}
};
}
}
【springcloud-service】 修改IProductClientService,增加fallback配置
package cn.enjoy.service;
import cn.enjoy.feign.FeignClientConfig;
import cn.enjoy.service.fallback.IProductClientServiceFallbackFactory;
import cn.enjoy.vo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(name = "MICROCLOUD-PROVIDER-PRODUCT",configuration = FeignClientConfig.class,
fallbackFactory = IProductClientServiceFallbackFactory.class)
public interface IProductClientService {
@RequestMapping("/prodcut/get/{id}")
public Product getProduct(@PathVariable("id")long id);
@RequestMapping("/prodcut/list")
public List<Product> listProduct() ;
@RequestMapping("/prodcut/add")
public boolean addPorduct(Product product) ;
}
【springcloud-consumer-feign】 复制一份成为【springcloud-consumer-hystrix】模块
【springcloud-consumer-hystrix】 修改application.yml配置文件,启用hystrix配置
feign:
hystrix:
enabled: true
compression:
request:
enabled: true
mime-types: # 可以被压缩的类型
- text/xml
- application/xml
- application/json
min-request-size: 2048 # 超过2048的字节进行压缩
启动,服务提供者
访问:http://localhost/consumer/product/get?id=1,能正常访问
关闭,服务提供者
访问:http://localhost/consumer/product/get?id=1,也能正常访问
HystrixDashboard
在hystrix里面提供一个Dashboard(仪表盘)的功能,他是一种监控的功能,可以利用它来进行整体服务的监控
新建一个模块【springcloud-consumer-hystrix-dashboard】
【springcloud-consumer-hystrix-dashboard】pom文件如下
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>enjoy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-hystrix-dashboard</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
【springcloud-provider-product-hystrix】 pom文件确保里面有健康检查模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
【springcloud-consumer-hystrix-dashboard】 修改application.yml配置文件
server:
port: 9001
【springcloud-consumer-hystrix-dashboard】 创建一个启动类
package cn.enjoy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApp {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApp.class,args);
}
}
启动运行:http://localhost:9001/hystrix
【springcloud-provider-product-hystrix】 修改applcation.yml文件
management:
endpoints:
web:
exposure:
include: '*'
【springcloud-provider-product-hystrix】启动
访问:localhost:8080/actuator/hystrix.stream
http://localhost:9001/hystrix 填写信息如下
http://admin:enjoy@localhost:8080/actuator/hystrix.stream
这个时候对localhost:8080的访问都可以被监控到
Turbine
HystrixDashboard 前面已经知道了,它的主要功能是可以对某一项微服务进行监控,但真实情况下,不可能只对某一个服务进行监控,更多的是对很多服务进行一个整体的监控,这个时候就需要使用到turbine来完成了。
为了演示监控多个服务模块,这个时候新建一个模块【springcloud-provider-user-hystrix】,为简单起见,这个模块并不连接数据库,也不做安全控制。
【springcloud-provider-user-hystrix】pom文件如下
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>enjoy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-user-hystrix</artifactId>
<dependencies>
<dependency>
<groupId>enjoy</groupId>
<artifactId>springcloud-api</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
【springcloud-api】新增一个VO类:Users
package cn.enjoy.vo;
import java.io.Serializable;
public class Users implements Serializable {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
【springcloud-provider-user-hystrix】 新建一个UserController
package cn.enjoy.controller;
import cn.enjoy.vo.Users;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
@RequestMapping("/get/{name}")
@HystrixCommand
public Object get(@PathVariable("name")String name) {
Users users = new Users();
users.setName(name);
users.setAge(18);
users.setSex("F");
return users;
}
}
【springcloud-provider-user-hystrix】新增启动类
package cn.enjoy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
public class UsersApp {
public static void main(String[] args) {
SpringApplication.run(UsersApp.class,args);
}
}
【springcloud-provider-user-hystrix】修改application.yml配置文件
server:
port: 8090
spring:
application:
name: springcloud-provider-users
logging:
level:
cn.enjoy.mapper: debug
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://admin:enjoy@eureka1:7001/eureka,http://admin:enjoy@eureka2:7002/eureka,http://admin:enjoy@eureka3:7003/eureka
instance:
instance-id: springcloud-provider-users
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
info:
app.name: springcloud-provider-users
company.name: enjoy
build.artifactId: $project.artifactId$
build.modelVersion: $project.modelVersion$
management:
endpoints:
web:
exposure:
include: '*'
启动后:
访问地址:http://localhost:8090/users/get/enjoy
hystrix监控地址:http://localhost:8090/actuator/hystrix.stream
前面准备工作完成后,如果想要实现 turbine 的配置,准备一个turbine模块
新增【springcloud-consumer-turbine】模块,pom文件如下
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>enjoy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-turbine</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
</dependencies>
</project>
【springcloud-consumer-turbine】修改application.yml配置文件
server:
port: 9101
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://admin:enjoy@eureka1:7001/eureka,http://admin:enjoy@eureka2:7002/eureka,http://admin:enjoy@eureka3:7003/eureka
turbine:
app-config: MICROCLOUD-PROVIDER-PRODUCT,MICROCLOUD-PROVIDER-USERS
cluster-name-expression: new String("default")
可以发现对于turbine,其实是从eureka配置在app-config中服务,然后进行监控
【springcloud-consumer-turbine】 新建一个启动类
package cn.enjoy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableTurbine
public class TurbineApp {
public static void main(String[] args) {
SpringApplication.run(TurbineApp.class,args);
}
}
turbine监控地址:
启动Dashboard: http://localhost:9001/hystrix
在Dashboard里面填上 turbine监控地址
发现目前turbine只监控了UserController的信息,看下turbine后台发现报错
其实原因也很简单,User服务并不需要用户验证,所以能正常访问,但对于Product服务,配置了用户名密码的,turbine肯定无法访问
【springcloud-security】如果现在需要turbine进行加密服务的访问,那么只能折衷处理,让访问/actuator/hystrix.stream与/turbine.stream这两个地址的时候不需要用户密码验证
【springcloud-security】 修改WebSecurityConfiguration
package cn.enjoy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("root").password(new BCryptPasswordEncoder().encode("enjoy")).roles("USER").
and().withUser("admin").password(new BCryptPasswordEncoder().encode("enjoy")).roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().anyRequest()
.fullyAuthenticated();
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/hystrix.stream","/turbine.stream") ;
}
}
turbine监控地址:http://localhost:9101/turbine.stream
启动Dashboard: http://localhost:9001/hystrix
在Dashboard里面填上 turbine监控地址
刷新:
http://localhost:8080/prodcut/get/1
http://localhost:8090/users/get/1
这就可以正常监控两个服务了