一、雪崩效应和解决方案
1.什么是灾难性的雪崩效应 ?
服务T出现故障后在微服务架构中,一个请求需要调用多个服务是非常常见的。若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
导致的连锁反应
导致整个系统瘫痪
2.出现雪崩的原因:
(1)服务提供者不可用(硬件故障,程序bug,缓存击穿,用户的大量请求);
(2)重试加大流量(用户重试,代码逻辑重试);
(3)服务调用者不可用(同步等待造成的资源耗尽);
最终的结果就是一个服务不可用,导致一系列服务的不可用,而往往这种后果是无法预料的。
3.如何解决雪崩效应:
- 降级 :
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。 实现一个 fallback 方法, 当请求后端服务出现异常的时候, 可以使用 fallback 方法返回的值. 隔离(线程池隔离和信号量隔离) 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
- 熔断:
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快 速失败会进行快速恢复。
- 缓存:
提供了请求缓存。
- 请求合并:
提供请求合并。
二、服务降级
1.什么是服务降级?
就是对不怎么重要的服务进行低优先级的处理。尽可能的把系统资源让给优先级高的服务。资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。
- 什么时候会触发getFallback调用:
(1) 方法抛出非 HystrixBadRequestException 异常。
(2) 方法调用超时。
(3) 熔断器开启拦截调用。
(4) 线程池/队列/信号量是否跑满。
2.如何使用服务降级:
- 创建简单测试降级实现:
Consumer——服务的消费者使用Eureka注册中心注册服务,下面测试都使用这个Provider提供服务。
Provider服务的生产者
1.1Consumer-hystrix:
- 修改 pom 文件添加 hystrix 的坐标:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 修改全局配置文件:
spring.application.name=product-provider-hystix
server.port=9010
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/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();
}
}
- 编写启动类开启熔断器:
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;
@EnableCircuitBreaker // 开启熔断器 断路器
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
- 编写ProductService:
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;// ribbon负载均衡器
@HystrixCommand(fallbackMethod = "fallback")//当下面方法服务出现异常停止会返回托底数据
public List<Product> 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<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {
};
// ResponseEntity:封装了返回值信息
ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Product> list = response.getBody();
return list;
}
//返回托底数据的方法
public List<Product> fallback(){
List<Product> list = new ArrayList<>();
list.add(new Product(-1,"托底数据!"));
return list;
}
}
- 编写Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List<Product> list(){
return productService.getProduct();
}
}
1.2Service-hystrix:
- 修改POM文件:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
- 创建实体类:
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;
}
}
- 编写Service:
@RequestMapping("/product")
public interface ProductService {
// 查询所有
@RequestMapping(value = "/findAll", method = RequestMethod.GET)
public List<Product> findAll();
// ID查询
@RequestMapping(value = "/findById", method = RequestMethod.GET)
public Product getProductById(@RequestParam("id") Integer id);
// 传递多个参数 方式一 :GET方式
@RequestMapping(value = "/add", method = RequestMethod.GET)
public Product addProduct(@RequestParam("id") Integer id, @RequestParam("name") String name);
// ----------------------Httpclient----------------------------------------------
//传递多个参数 方式二 :POST方式
@RequestMapping(value = "/add2", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public Product addProduct2(@RequestBody Product product);
// 使用HttpClient工具 传递多个参数 :基于GET方式
@RequestMapping(value = "/add3", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE)
public Product addProduct3(Product product);
}
1.3Provider- hystrix:
- 修改POM文件:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 添加product-service坐标 -->
<dependency>
<groupId>com.zlw</groupId>
<artifactId>springcloud-eureka-ribbon-hystrix-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 修改全局配置文件:
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);
}
}
- 编写ProviderController:
/**
* Product-Provider服务
* @author zhang
*
*/
@RestController
public class ProductController implements ProductService{
@Override
public List<Product> findAll() {
List<Product> list = new ArrayList<Product>();
list.add(new Product(1,"手机"));
list.add(new Product(2,"电脑"));
list.add(new Product(3,"电视"));
return list;
}
@Override
public Product getProductById(Integer id) {
return new Product(id,"Product Provider");
}
@Override
public Product addProduct(Integer id, String name) {
return new Product(id,name);
}
@Override
public Product addProduct2(@RequestBody Product product) {
return product;
}
@Override
public Product addProduct3(@RequestBody Product product) {
return product;
}
}
-
测试:
示例
示例
三、请求缓存
Hystrix 为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次 请求的 URL 没有变化,那么 Hystrix 不会请求服务,而是直接从缓存中将结果返回。这样可 以大大降低访问服务的压力。
- Hystrix 自带缓存。有两个缺点:
(1)是一个本地缓存。在集群情况下缓存是不能同步的。
(2)不支持第三方缓存容器。Redis,memcache 不支持的。 可以使用 spring 的 cache。
1.使用Redis
1.1创建Consumer-cache:
示例- 修改POM文件添加 springCache 坐标 :
<!-- springCache Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 修改配置文件添加redis链接信息:
spring.application.name=product-consumer-redis
server.port=9012
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#Redis
spring.redis.datasource=0
#Redis服务器地址
spring.redis.host=192.168.226.130
#Reids服务器连接的端口
spring.redis.port=6379
#Redis 服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(负值表示没有限制)
spring.redis.pool.max-active=100
#连接池最大阻塞等待时间(负值表示没有限制)
spring.redis.pool.max-wait=3000
#连接池最大空闭连接数
spring.redis.pool.max-idle=200
#连接汉最小空闲连接数
spring.redis.pool.min-idle=50
#连接超时时间(毫秒)
spring.redis.pool.timeout=600
- 修改启动类开启缓存:
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
- 创建实体类(get和set方法):
private int id;
private String name;
- 修改ProductService:
@CacheConfig(cacheNames = { "product.consumer" })
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;// ribbon负载均衡器
public List<Product> 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<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {
};
// ResponseEntity:封装了返回值信息
ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Product> list = response.getBody();
return list;
}
// 根据ID查询
@Cacheable(key = "'product'+#id")
public Product findById(Integer id) {
System.out.println("------Find------" + id);
return new Product(id, "Product Redis");
}
// 根据ID删除
@CacheEvict(key = "'product'+#id")
public void delProduct(Integer id) {
System.out.println("-------Delete--------"+id);
}
}
-
测试:
控制台打印
redis的存储
四、请求合并
就是把请求合并起来一起发送给服务端(一次性批量发送请求),服务端批量处理完成后再返回给客户端。
请求合并的功能是客户端来控制的,由客户端整合发起,然后由服务端统一处理,但是需要服务端支持批量处理请求的能力。
1.什么情况下使用请求合并:
在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程 调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加, 同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致 响应延迟,为了解决这些问题,我们需要来了解 Hystrix 的请求合并。
- 请求合并的缺点:
设置请求合并之后,本来一个请求可能 5ms 就搞定了,但是现在必须再等 10ms 看看还 有没有其他的请求一起的,这样一个请求的耗时就从 5ms 增加到 15ms 了,不过,如果我们 要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个 时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场 景。
2.创建Consumer-batch测试:
示例- 修改POM文件:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
- 修改配置文件:
spring.application.name=product-provider-batch
server.port=9009
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 创建实体类:
private Integer id;
private String name;
- 修改启动类:
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class ProviductApplication {
public static void main(String[] args) {
SpringApplication.run(ProviductApplication.class, args);
}
}
- 修改ProductService:
@Service
public class ProductService {
// 使用hystrix进行合并请求
@HystrixCollapser(batchMethod = "productMethod", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL, collapserProperties = {
// 请求时间间隔在 20ms 之内的请求会被合并为一个请求,默认 为 10ms
@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
// 设置触发批处理执行之前,在批处理中允许许的最大请求数。
@HystrixProperty(name = "maxRequestsInBatch", value = "3")
})
// consumer的Controller调用的方法,该方法返回值必须要返回 Future类型
public Future<Product> getFuture(Integer id) {
System.out.println("-------" + id + "---------");
return null;
}
//调用Provider服务的方法
@HystrixCommand
public List<Product> productMethod(List<Integer> id){
System.out.println("---------productMethod---------");
for (Integer num : id) {
System.out.println("========id:"+num);
}
List<Product> list = new ArrayList<Product>();
list.add(new Product(1,"手机"));
list.add(new Product(2,"电脑"));
list.add(new Product(3,"电视"));
list.add(new Product(4,"音箱"));
list.add(new Product(99,"...."));
System.out.println("=================");
return list;
}
}
- 修改Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public void list() throws Exception {
Future<Product> future = productService.getFuture(1);
Future<Product> future2 = productService.getFuture(2);
Future<Product> future3 = productService.getFuture(4);
Future<Product> future4 = productService.getFuture(3);
System.out.println(future.get().toString());
System.out.println(future2.get().toString());
System.out.println(future3.get().toString());
System.out.println(future4.get().toString());
}
}
-
测试:
示例
五、服务熔断
示例熔断机制相当于电路的跳闸功能。
1.创建Consumer
示例- 修改POM文件:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
- 修改全局配置文件:
spring.application.name=product-provider-fuse
server.port=9008
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 编写启动类:
@EnableCircuitBreaker//开启熔断器,断路器
@EnableEurekaClient
@SpringBootApplication
public class FuseApplication {
public static void main(String[] args) {
SpringApplication.run(FuseApplication.class, args);
}
}
- 创建实体类:
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() {
super();
}
public Product(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
- 编写Service:
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallback", commandProperties = {
// 默认 20 个;10s 内请求数大于 20 个时就启动熔断器,当请 求符合熔断条件时将触发 getFallback()
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"),
// 默认 20 个;10s 内请求数大于 20 个时就启动熔断器,当请 求符合熔断条件时将触发 getFallback()
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
// 默认 5 秒,熔断多少秒后去尝试请求
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000") })
public List<Product> list(int flag) {
System.out.println("fuse---productService---list:"+flag);
if (flag == 1) {
throw new RuntimeException();
}
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("eureka-provider");
// 拼接访问服务的 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<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {
};
// ResponseEntity:封装了返回值信息
ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Product> list = response.getBody();
return list;
}
public List<Product> fallback(int flag) {
List<Product> list = new ArrayList<Product>();
list.add(new Product(-1, "托底数据!"));
return list;
}
}
- Controller:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public List<Product> list(@RequestParam("flag") Integer flag) {
return productService.list(flag);
}
}
-
测试:
示例
示例
六、线程池隔离
示例什么是线程隔离,例如Customer通过线程池,访问服务接口,接口A面对的10次的请求量,接口B是10次的访问量,比例是1:10000,此时接口A和接口B的连接是在同一个的线程池中,如果接口A因为的访问量过大的原因出现问题,势必影响线程池的效率,而线程池中的其他线程也会受到影响,从而造成雪崩效应。
1.如何使用线程隔离解决:
示例那就要用到线程的的隔离技术了。把可能出现问题的服务独立的运行在一个独立的线程池中。
示例线程池的隔离如下图中两个线程池我们将服务量大的请求单独的运行在一个的独立的线程池中,两个线程池相互之间是不影响的。可以使用线程池隔离的技术将的线程池内的可能预估出现问题的线程和其他的线程的隔开运行在一个独立的线程池中,一旦此线程出现问题,不会影响到其他线程的运行,解决雪崩效应的产生。
- 使用线程隔离的优缺点:
(1)优点:
使用线程隔离可以完全隔离依赖的服务。
当线程池出现问题时,线程池隔离是独立的,不影响其他服务和接口。
当失败的服务再次可用时,线程池将清理并可立即恢复,不需要长时间的恢复。
独立的线程池提供了并发性。
(2)缺点:
线程池隔离的主要缺点是他们增加计算开销,每个命令的执行涉及到排队,调度和上下文切换都是在一个单独的线程上运行的。
2.创建Consumer测试:
示例- 修改POM文件:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
- 修改配置文件:
spring.application.name=product-provider-threadpool
server.port=9007
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 修改启动类:
@EnableCircuitBreaker //开启熔断器 断路器
@EnableEurekaClient
@SpringBootApplication public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
- 修改ProductService:
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient;// ribbon 负 载均衡器
@HystrixCommand(groupKey="ego-product-provider", commandKey = "getUsers",threadPoolKey="ego-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<Product> getUsers() {
System.out.println(Thread.currentThread().getName());
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("ego-product-provider");
// 拼接访问服务的 URL
StringBuffer sb = new StringBuffer();
// http://localhost:9001/product/findAll
sb.append("http://").append(si.getHost()).append(":").appen d(si.getPort()).append("/product/findAll");
System.out.println(sb.toString());
// springMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Product>> type = new ParameterizedTypeReference<List<Product>>() { };
// ResponseEntity:封装了返回值信息
ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Product> list = response.getBody();
return list; } // 返回托底数据的方法
public List<Product> fallback() {
System.out.println(Thread.currentThread().getName());
List<Product> list = new ArrayList<>();
list.add(new Product(-1, "我是托底数据"));
return list;
}
public void showThread() {
System.out.println(Thread.currentThread().getName());
}
}
- 修改ProductController:
@RestController
public class ProductController {
@Autowired
private ProductService userService;
@RequestMapping("/consumer")
public List<Product> getUsers() {
return this.userService.getUsers();
}
@RequestMapping("/consumer1")
public void getUsers1() {
this.userService.showThread();
}
}
七、使用的注解
1.降级中使用的注解:
名称 | 作用 |
---|---|
@HystrixCommand | 当下面方法服务出现异常停止会返回托底数据 |
@EnableCircuitBreaker | 开启熔断器,断路器 |
2.请求缓存中使用的注解:
名称 | 作用 |
---|---|
@CacheConfig | 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig |
@Cacheable | 根据一定条件对缓存添加数据 |
@CacheEvict | 主要针对方法配置,能够根据一定的条件对缓存进行清空。 |
@EnableCaching | 注解是spring framework中的注解驱动的缓存管理功能,开启缓存。 |
3.请求合并:
@HystrixCollapser:利用 hystrix 实现请求合并。
属性名称 | 作用 |
---|---|
batchMethod | 设置合并请求的方法 |
scope | 设置请求的方式 |
@HystrixProperty:Hystrix 的命令属性是由@HystrixProperty 注解数组构成的,HystrixProperty 由 name 和 value 两个属性,数据类型都是字符串。
属性名称 | 作用 |
---|---|
timerDelayInMilliseconds | 请求时间间隔默认 为 10ms之内的请求会被合并为一个请求 |
maxRequestsInBatch | 设置触发批处理执行之前,在批处理中允许许的最大请求数。 |
4.服务熔断:
@HystrixProperty中的属性HystrixPropertiesManager参数的作用。
参数名称 | 作用 |
---|---|
circuitBreaker.requestVolumeThreshold | 一个统计窗口内熔断触发的最小个数。 |
circuitBreaker.enabled | 是否开启熔断。 |
circuitBreaker.sleepWindowInMiliseconds | 熔断多少秒后去尝试请求。 |
circuitBreaker.errorThresholdPercentage | 失败率达到多少百分比后熔断。 |
circuitBreaker.forceOpen | 是否强制开启熔断。 |
circuitBreaker.forceClosed | 是否强制关闭熔断。 |
5.线程隔离:
(1) @HystrixCommand注解中的threadPoolProperties属性的作用配置与线程池相关的属性。
参数名称 | 作用 |
---|---|
groupKey | 设置服务名(相同的服务用一个名称),默认值getClass().getSimpleName() |
commandKey | 设置服务下面的接口,默认当前执行方法名 |
threadPoolKey | 设置线程池的名称,全局唯一标识线程池的名称,相同线程池名称的线程池是同一个 |
coreSize | 设置线程池大小 |
maxQueueSize | 设置最大队列长度 |
queueSizeRejectionThreshold | 设置拒绝请求的临界值 |
keepAliveTimeMinutes | 设置线程存活时间,默认为1min |
网友评论