博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于 Hystrix 高并发服务限流第 2 篇 —— 服务隔离(线程池隔离、信号量隔离)
阅读量:2094 次
发布时间:2019-04-29

本文共 7636 字,大约阅读时间需要 25 分钟。

查看之前的博客可以点击顶部的【分类专栏】

 

为了解决上一篇博客的疑惑,我们开始继续实验。

 

服务隔离

没有线程池隔离的项目所有接口都运行在一个同一个线程池中,当某个接口压力过大出现故障时,会导致资源大量被消耗,从而影响其它接口的调用,直接导致服务雪崩效应。为防止服务雪崩,可以使用服务隔离机制(线程池方式或信号量),保证每个服务互不影响。

服务隔离分为:线程池隔离和信号量隔离。线程池隔离适用于 99% 的业务场景,而信号量隔离只适用于 1% 的场景,因为信号量隔离是同步的,会阻塞请求。

 

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 List
getList(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 发送并发请求,然后订单服务发送请求。可以看到产品服务的控制台输出

 

这次并没有被超时的现象发生,因为获取单个产品、集合是分开的线程池。

 

线程池隔离的优缺点

优点:

  1. 使用线程池隔离可以安全隔离依赖的服务,减少所依赖服务发生故障时的影响范围。
  2. 当失败的服务再次变得可用时,线程池将清理并立即恢复,而不需要长时间的恢复
  3. 独立的线程池提高了并发性。

缺点:

  1. 请求在线程池中执行,会带来任务的调度、排队和上下文切换带来的CPU开销
  2. 因为涉及到跨线程,那么就会存在 ThreadLocal 数据的传递问题。比如在主线程初始化的 ThreadLocal 变量,在线程池中的线程无法获取到。

 

2、信号量隔离

每次调用线程,先获取到信号量,获取到信号量才可以继续往后执行。执行完毕需要归还信号量。

如果信号量已经被获取完毕,新进来的请求需要排队,会进入阻塞。或者拒绝处理请求。

因为默认是线程池隔离,因此先把所有线程池隔离注释掉(别注释掉熔断的 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/

你可能感兴趣的文章
剑指offer 23.从上往下打印二叉树
查看>>
Leetcode C++《热题 Hot 100-18》538.把二叉搜索树转换为累加树
查看>>
Leetcode C++《热题 Hot 100-21》581.最短无序连续子数组
查看>>
Leetcode C++《热题 Hot 100-22》2.两数相加
查看>>
Leetcode C++《热题 Hot 100-23》3.无重复字符的最长子串
查看>>
Leetcode C++《热题 Hot 100-24》5.最长回文子串
查看>>
Leetcode C++《热题 Hot 100-28》19.删除链表的倒数第N个节点
查看>>
Leetcode C++《热题 Hot 100-29》22.括号生成
查看>>
阿里云《云原生》公开课笔记 第二章 容器基本概念
查看>>
阿里云《云原生》公开课笔记 第三章 kubernetes核心概念
查看>>
阿里云《云原生》公开课笔记 第四章 理解Pod和容器设计模式
查看>>
阿里云《云原生》公开课笔记 第五章 应用编排与管理
查看>>
阿里云《云原生》公开课笔记 第六章 应用编排与管理:Deployment
查看>>
阿里云《云原生》公开课笔记 第七章 应用编排与管理:Job和DaemonSet
查看>>
阿里云《云原生》公开课笔记 第八章 应用配置管理
查看>>
阿里云《云原生》公开课笔记 第九章 应用存储和持久化数据卷:核心知识
查看>>
linux系统 阿里云源
查看>>
国内外helm源记录
查看>>
牛客网题目1:最大数
查看>>
散落人间知识点记录one
查看>>