一、信号隔离机制
解决灾难性雪崩效应使用隔离机制中的信号量隔离处理。
1.什么是信号量隔离:
hystrix里面,核心的一项功能,其实就是所谓的资源隔离,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自自己的资源池内。避免对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。
2.测试信号量隔离
- 信号隔离注解中的参数:
参数 | 作用 | 默认值 |
---|---|---|
EXECUTION_ISOLATION_STRATEGY | 隔离策略的配置项 | THREAD |
EXECUTION_ISOLATION_THREAD_TIMEOUTINMILLISECONDS | 超时时间 | 1000ms |
EXECUTION_ISOLATION_THREAD_INTERRUPTONTIMEOUT | 是否打开超时线程中断 | TRUE |
EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS | 信号量最大并发度 | 10 |
FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENTREQUESTS | fallback最大并发度 | 10 |
- 创建Provider:
下面测试中会多次使用该服务和Service。
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
com.zlw
springcloud-eureka-ribbon-hystrix-service
0.0.1-SNAPSHOT
- 修改配置文件:
spring.application.name=product-provider-hystix
server.port=9011
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 创建启动类:
@EnableEurekaClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
- 编写Controller提供服务:
@RestController
public class ProductController implements ProductService{
@Override
public List findAll() {
List list = new ArrayList();
list.add(new Product(1,"手机"));
list.add(new Product(2,"电脑"));
list.add(new Product(3,"电视"));
return list;
}
}
-
创建项目Service:
- 修改POM文件,添加坐标:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
- 创建实体类:
public class Product {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Product(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Product() {
super();
}
}
- 编写Service接口:
@RequestMapping("/product")
public interface ProductService {
// 查询所有
@RequestMapping(value = "/findAll", method = RequestMethod.GET)
public List findAll();
}
-
创建consumer-semaphore:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-hystrix
- 修改配置文件:
spring.application.name=eureka-consumer-semaphore
server.port=9007
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 创建实体类:
private int id;
private String name;
- 创建Service:
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallback", commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
// 信号量 隔离
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "100")
// 信号量最大并度
})
public List getProduct() {
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("product-provider-hystix");
// 拼接访问服务的URL
StringBuffer sb = new StringBuffer();
// http://localhost:9001/product/findAll
sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
System.out.println(sb.toString());
// springMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference> type = new ParameterizedTypeReference>() {
};
// ResponseEntity:封装了返回值信息
ResponseEntity> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List list = response.getBody();
return list;
}
// 托底数据
public List fallback() {
System.out.println(Thread.currentThread().getName());
List list = new ArrayList();
list.add(new Product(-1, "托底数据"));
return list;
}
public void showThread() {
System.out.println(Thread.currentThread().getName());
}
}
- 创建Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List list() {
return this.productService.getProduct();
}
}
- 创建启动类:
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class SemaphoreApplication {
public static void main(String[] args) {
SpringApplication.run(SemaphoreApplication.class, args);
}
}
-
测试:
3.线程池和信号隔离的区别:
-- | 线程池隔离 | 信号量隔离 |
---|---|---|
线程 | 请求线程和调用provider线程不是同一条线程 | 请求线程和调用provider线程是同一条线程 |
开销 | 排队、调度、上下文开销等 | 无线程切换,开销低 |
异步 | 支持 | 不支持 |
并发支持 | 支持(最大线程池大小) | 支持(最大信号量上限) |
传递Header | 不可以 | 可以 |
是否支持超时 | 支持 | 不支持 |
- 什么情况下使用线程池隔离:
请求并发量大,并且耗时长(请求耗时长一般是计算量大,或读数据库);采用线程隔离策略,这样可以保证大量的容器线程可用,不会由于服务原因,一直处于阻塞状态,快速失败返回。
- 什么情况下使用信号隔离:
请求并发量大,并且耗时短(请求耗时短可能是计算量小,或读缓存);采用信号量隔离策略,因为这类服务的返回通常会非常的块,不会占用容器线程太长时间,而且也会减少了线程切换的一些开销,提高了缓存服务的效率。
二、Feign的服务降级
1.服务降级测试:
-
创建Consumer-fallback
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-feign
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
- 修改配置文件:
spring.application.name=eureka-consumer-feign-fallback
server.port=9020
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#Feign 默认是不开启 Hystrix 的。默认为:false
feign.hystrix.enabled=true
- 创建实体类:
private int id;
private String name;
- 创建Service接口:
@FeignClient(name = "product-provider",fallback = ProductConsumerFallback.class)
public interface ConsumerService {
@RequestMapping(value = "/product/findAll",method = RequestMethod.GET)
public ListfindAll();
}
- 创建托底数据:
@Component
public class ProductConsumerFallback implements ConsumerService {
// 返回托底数据的方法
@Override
public List findAll() {
ArrayList list = new ArrayList<>();
list.add(new Product(-1, "托底数据"));
return list;
}
}
- 创建Controller:
@RestController
public class ProductController {
@Autowired
ConsumerService consumerService;
// 查询所有
@RequestMapping(value = "list", method = RequestMethod.GET)
public List list() {
List list = consumerService.findAll();
return list;
}
}
- 修改启动类:
@EnableCircuitBreaker
@EnableHystrixDashboard
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
-
测试:
2.服务降级后的异常记录:
- 为什么记录降级后的异常信息?
我们调用provider服务的时候出现了故障从而返回了托底数据,我们怎么查看故障的日志信息呢?前面的处理我们是无法获取到consumer调用provider的错误信息的。此时就需要记录异常信息,方便查看异常。
-
创建测试项目:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-feign
- 修改配置文件:
spring.application.name=eureka-consumer-feign-factory
server.port=9021
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#Feign 默认是不开启 Hystrix 的。默认为:false
feign.hystrix.enabled=true
- 修改Service:
@FeignClient(name = "product-provider",fallbackFactory = ProductConsumerFactory.class)
public interface ConsumerService {
@RequestMapping(value = "/product/findAll",method = RequestMethod.GET)
public ListfindAll();
}
- 修改Hystrix:
@Component
public class ProductConsumerFactory implements FallbackFactory {
Logger logger = LoggerFactory.getLogger(ProductConsumerFactory.class);
@Override
public ConsumerService create(Throwable cause) {
return new ConsumerService() {
// 返回托底数据的方法
@Override
public List findAll() {
logger.warn("FallBack Exception:",cause);
ArrayList list = new ArrayList<>();
list.add(new Product(-1, "托底数据"));
return list;
}
};
}
}
- 修改启动类:
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
-
测试:
三、可视化数据监控dashboard
1.什么是Hystrix-dashboard?
Hystrix-dashboard 是一款针对 Hystrix 进行实时监控的工具,通过 Hystrix Dashboard 我们可以在直观地看到各 Hystrix Command 的请求响应时间, 请求成功率等数据。
- @EnableHystrix注解的作用:
启动熔断降级服务。
- @EnableHystrixDashboard注解的作用:
开启HystrixDashboard。
2.创建项目测试数据监控:
-
创建数据监控中心服务:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
- 修改配置文件:
spring.application.name=eureka-consumer-ribbon-dashboard
server.port=9009
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 创建实体类:
private int id;
private String name;
- 创建Service:
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;
@HystrixCommand(groupKey = "product-provider", commandKey = "getProduct", threadPoolKey = "product-provider", threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
// 线程池大小
@HystrixProperty(name = "maxQueueSize", value = "100"),
// 最大队列长度
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
// 线程存活时间
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")
// 拒绝请求
}, fallbackMethod = "fallback")
public List getProduct() {
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("product-provider-hystix");
// 拼接访问服务的URL
StringBuffer sb = new StringBuffer();
// http://localhost:9001/product/findAll
sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
System.out.println(sb.toString());
// springMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference> type = new ParameterizedTypeReference>() {
};
// ResponseEntity:封装了返回值信息
ResponseEntity> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List list = response.getBody();
return list;
}
// 托底数据
public List fallback() {
System.out.println(Thread.currentThread().getName());
List list = new ArrayList();
list.add(new Product(-1, "托底数据"));
return list;
}
public void showThread() {
System.out.println(Thread.currentThread().getName());
}
}
- 创建Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List list() {
return this.productService.getProduct();
}
- 修改启动类:
@EnableHystrix
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class DashboardApplication {
public static void main(String[] args) {
SpringApplication.run(DashboardApplication.class, args);
}
}
-
测试:
-
创建Hystrix-dashboard监控中心:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
- 修改配置文件:
spring.application.name=eureka-consumer-dashboard-view
server.port=1001
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 修改启动类:
@EnableHystrix
@EnableHystrixDashboard
@EnableCircuitBreaker//开启熔断器
@EnableEurekaClient
@SpringBootApplication
public class DashboardApplication {
public static void main(String[] args) {
SpringApplication.run(DashboardApplication.class, args);
}
}
- 测试:
启动顺序,先启动服务再启动监控中心。
四、使用Turbine集合集群
1.什么是Turbine?
Turbine 是聚合服务器发送事件流数据的一个工具,hystrix 的
监控中,只能监控单个节点,实际生产中都为集群,因此可以通过 turbine 来监控集群服务。
2.创建项目测试:
使用Turbine聚合Consumer-fallback和Consumer-dashboard项目。
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
org.springframework.cloud
spring-cloud-starter-turbine
org.springframework.cloud
spring-cloud-netflix-turbine
- 修改配置文件:
spring.application.name=eureka-consumer-turbine-view
server.port=1002
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#----------------------turbine---------------
#配置 Eureka 中的 serviceId 列表,表明监控哪些服务
turbine.appConfig=eureka-consumer-feign-fallback,eureka-consumer-ribbon-dashboard
#指定聚合哪些集群,多个使用","分割,默认为 default。可使用 http://.../turbine.stream?cluster={clusterConfig 之一}访问
turbine.aggregator.clusterConfig=default
# 1. clusterNameExpression 指定集群名称,默认表达式 appName;此 时:turbine.aggregator.clusterConfig 需要配置想要监控的应用名称;
# 2. 当 clusterNameExpression: default 时, turbine.aggregator.clusterConfig 可以不写,因为默认就是 default;
# 3. 当 clusterNameExpression: metadata['cluster']时,假设想要 监控的应用配置了 eureka.instance.metadata-map.cluster: ABC,
# 则需要配置,同时 turbine.aggregator.clusterConfig: ABC
turbine.clusterNameExpression="default"
- 修改启动类:
@EnableTurbine
@SpringBootApplication
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
- 修改聚合多个服务的POM文件,添加坐标:
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
- 修改被监控服务的启动类,添加注解:
@EnableHystrixDashboard
@EnableCircuitBreaker //开启熔断器 断路器
- 测试:
依次启动服务和监控中心。
3.监控集群:
将任意服务打包后,上传到Linux服务器,在/usr/local/目录下创建目录,“mkdir 目录名”,添加启动脚本。
- 测试:
依次启动服务和监控中心。
五、使用RabbitMQ收集监控数据(RabbitMQ+Turbine+dashboard)
1.为什么使用RabbitMQ?
使用的Turbine进行数据监控的虽然能够解决的集群服务的监控的问题,但是的也存在问题,比如频繁的配置,我们需要在Turbine内配置需要监控的服务,这是非常不好的,假如有1000个服务?那配置起来就很繁琐了,而且配置的服务不易管理,增加了管理的难度。
在实际的应用中我们并不采用turbine进行集群服务的监控,而是采用在服务和Turbine之间添加一个的TabbitMQ进行数据的收集,然后将Trubine从RabbitMQ中获取数据,在交给Dashboard进行显示。
2.RabbitMQ收集监控数据的步骤:
- 修改Consumer的配置信息:
(1)在POM文件中添加坐标:dashboard、actuator、hystrix-stream、stream-rabbit。
(2)修改配置文件:添加RabbitMQ的连接信息。
spring.rabbitmq.host=192.168.226.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtualHost=/
(3)修改启动类:添加@EnableHystrixDashboard注解。
- 修改Turbine的配置信息:
(1)修改POM文件,添加Turbine-stream和stream-Rabbit的坐标。
(2)修改配置文件:
spring.rabbitmq.host=192.168.226.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtualHost=/
(3)修改启动类,添加@EnableTurbineStream注解,开启Turbine-Stream的监控。
3.创建测试项目:
需要在Linux服务器中安装RabbitMQ。RabbitMQ安装和使用
-
创建Consumer服务:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
org.springframework.cloud
spring-cloud-netflix-hystrix-stream
org.springframework.cloud
spring-cloud-starter-stream-rabbit
- 修改配置文件:
spring.application.name=eureka-consumer-ribbon-dashboard-mq
server.port=9021
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
spring.rabbitmq.host=192.168.226.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtualHost=/
- 创建实体类:
private int id;
private String name;
- 创建Service:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List list() {
return this.productService.getProduct();
}
}
- 创建Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List list() {
return this.productService.getProduct();
}
}
- 修改启动类:
@EnableHystrix
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class DashboardApplication {
public static void main(String[] args) {
SpringApplication.run(DashboardApplication.class, args);
}
}
-
创建Turbine-mq:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-turbine-stream
org.springframework.cloud
spring-cloud-starter-stream-rabbit
- 修改配置文件:
spring.application.name=eureka-consumer-turbine-mq
server.port=1002
#设置服务注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
spring.rabbitmq.host=192.168.226.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtualHost=/
- 修改启动类:
@EnableTurbineStream
@SpringBootApplication
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
-
测试: