本文共 7636 字,大约阅读时间需要 25 分钟。
查看之前的博客可以点击顶部的【分类专栏】
为了解决上一篇博客的疑惑,我们开始继续实验。
没有线程池隔离的项目所有接口都运行在一个同一个线程池中,当某个接口压力过大出现故障时,会导致资源大量被消耗,从而影响其它接口的调用,直接导致服务雪崩效应。为防止服务雪崩,可以使用服务隔离机制(线程池方式或信号量),保证每个服务互不影响。
服务隔离分为:线程池隔离和信号量隔离。线程池隔离适用于 99% 的业务场景,而信号量隔离只适用于 1% 的场景,因为信号量隔离是同步的,会阻塞请求。
我们在前面博客的基础上,在产品的微服务接口,增加线程名称的打印。
说明它们使用相同的线程池。
在 product-server 的 pom.xml 添加 Hystrix 依赖
org.springframework.cloud spring-cloud-starter-netflix-hystrix
业务层 ProductServiceImpl 详细代码如下:
package com.study.service.impl;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;import com.study.entity.ProductEntity;import com.study.service.ProductService;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;/** * @author biandan * @description * @signature 让天下没有难写的代码 * @create 2021-05-27 下午 4:12 */@Servicepublic class ProductServiceImpl implements ProductService { //根据产品ID获取信息,这里先写固定 @HystrixCommand( groupKey = "produce-server-single-pool",//服务名称,相同名称使用同一个线程池 commandKey = "getById",//接口名称,默认方法名 threadPoolKey = "produce-server-single-pool",//线程池名称,相同名称使用同一个线程池 commandProperties = { //超时时间,默认 1000 ms @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "5000") }, threadPoolProperties = { @HystrixProperty(name = HystrixPropertiesManager.CORE_SIZE,value = "3"),//线程池大小 @HystrixProperty(name = HystrixPropertiesManager.MAX_QUEUE_SIZE,value = "100"),//最大队列阈值,默认-1 @HystrixProperty(name = HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value = "2"),//线程存活时间,默认1分钟 @HystrixProperty(name = HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value = "100")//超出队列等待阈值拒绝策略 }, fallbackMethod = "getByIdFallback" //服务容错回调函数 ) @Override public ProductEntity getById(Integer id) { System.out.println(Thread.currentThread().getName()+" 单个产品接口。getById"); ProductEntity entity = new ProductEntity(); entity.setId(1); entity.setProductName("霸王防脱洗发液"); entity.setPrice(new Float("55.55")); return entity; } //单个商品的托底数据,注意参数要与被调用的一致 public ProductEntity getByIdFallback(Integer id){ System.out.println(Thread.currentThread().getName()+" getByIdFallback 托底数据"); ProductEntity entity = new ProductEntity(); entity.setId(110); entity.setProductName("单个商品托底数据"); entity.setPrice(new Float(11.11)); return entity; } //根据集合id查询 @HystrixCommand( groupKey = "produce-server-list-pool",//服务名称,相同名称使用同一个线程池 commandKey = "getList",//接口名称,默认方法名 threadPoolKey = "produce-server-list-pool",//线程池名称,相同名称使用同一个线程池 commandProperties = { //超时时间,默认 1000 ms @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "5000") }, threadPoolProperties = { @HystrixProperty(name = HystrixPropertiesManager.CORE_SIZE,value = "6"),//线程池大小 @HystrixProperty(name = HystrixPropertiesManager.MAX_QUEUE_SIZE,value = "100"),//最大队列阈值,默认-1 @HystrixProperty(name = HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value = "2"),//线程存活时间,默认1分钟 @HystrixProperty(name = HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value = "100")//超出队列等待阈值拒绝策略 }, fallbackMethod = "getListFallback" //服务容错回调函数 ) @Override public ListgetList(List ids) { System.out.println(Thread.currentThread().getName()+" 多个产品接口。getList"); List list = new ArrayList<>(); ProductEntity entity = new ProductEntity(); entity.setId(1); entity.setProductName("霸王防脱洗发液"); entity.setPrice(new Float("55.55")); list.add(entity); ProductEntity entity_2 = new ProductEntity(); entity_2.setId(2); entity_2.setProductName("人字拖"); entity_2.setPrice(new Float("9.9")); list.add(entity_2); return list; } //集合商品的托底数据,注意参数要与被调用的一致 public List getListFallback(List ids){ System.out.println(Thread.currentThread().getName()+" getListFallback 托底数据"); List list = new ArrayList<>(); ProductEntity entity = new ProductEntity(); entity.setId(111); entity.setProductName("托底数据-霸王"); entity.setPrice(new Float("55.55")); list.add(entity); ProductEntity entity_2 = new ProductEntity(); entity_2.setId(112); entity_2.setProductName("托底数据-人字拖"); entity_2.setPrice(new Float("9.9")); list.add(entity_2); return list; }}
@HystrixCommand 注解的说明:
1:commandKey:配置全局唯一标识服务的名称,如果不配置,则默认是@HystrixCommand注解修饰的函数的函数名。
2:groupKey:一个比较重要的注解,配置全局唯一标识服务分组的名称,Hystrix会根据组来组织和统计命令的告、仪表盘等信息。
3:threadPoolKey:对线程池进行设定,细粒度的配置,相当于对单个服务的线程池信息进行设置,也可多个服务设置同一个threadPoolKey构成线程组。
4:fallbackMethod:@HystrixCommand注解修饰的函数的回调函数,@HystrixCommand修饰的函数必须和这个回调函数定义在同一个类中,因为定义在了同一个类中,所以fackback method可以是public/private均可。
5:commandProperties:配置该命令的一些参数,如executionIsolationStrategy配置执行隔离策略,默认是使用线程隔离
6:threadPoolProperties:线程池相关参数设置
7:defaultFallback:默认的回调函数,该函数的函数体不能有入参,返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。
com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException: fallback method wasn't found: getByIdFallback([class java.lang.Integer])
因为我们之前在 product-server 的 yml 配置文件分配了10个线程,因此我们现在分配 6 个给 list 接口,3个给单个接口。在启动类增加注解 @EnableHystrix ,开启 Hystrix。
分别请求单个、多个产品接口,可以看到我们的线程池已经分配好了。
然后我们继续用 JMeter 测试高并发的场景。30个线程50次循环的请求 getList 接口,然后订单服务去请求单个产品的接口。注意需要把订单服务的【请求缓存】功能注释掉,否则直接从缓存里获取数据。
然后重启2个服务,先使用 Jmeter 发送并发请求,然后订单服务发送请求。可以看到产品服务的控制台输出
这次并没有被超时的现象发生,因为获取单个产品、集合是分开的线程池。
优点:
缺点:
每次调用线程,先获取到信号量,获取到信号量才可以继续往后执行。执行完毕需要归还信号量。
如果信号量已经被获取完毕,新进来的请求需要排队,会进入阻塞。或者拒绝处理请求。因为默认是线程池隔离,因此先把所有线程池隔离注释掉(别注释掉熔断的 fallback 方法),然后在 getList 接口增加信号量隔离的代码:
//信号量隔离 @HystrixCommand( commandProperties = { //超时时间,默认1000毫秒 @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "3000"), //信号量隔离 @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"), //信号量最大并发,调小方便测试 @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "5") }, fallbackMethod = "getListFallback" //服务容错回调函数 )
然后把 getList 休眠代码去掉。重启 product-server 服务,然后使用 JMeter 测试:
说明在大量并发的情况下,信号量达到了最大值,直接返回 fallback 方法的内容。
线程池:适合绝大多数的场景,99%的,线程池,对依赖服务的网络请求的调用和访问,timeout这种问题
信号量:你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,但是像这种访问,系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题,算法+数据结构的效率不是太高,并发量突然太高,因为这里稍微耗时一些,导致很多线程卡在这里的话,不太好,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率的代码,导致大量的线程被 hang 住。
本篇博客代码地址: 提取码:xxfz
转载地址:http://zkuhf.baihongyu.com/