基于 Hystrix 高并发服务限流第 2 篇 —— 服务隔离(线程池隔离、信号量隔离)

本文详细介绍了如何使用Spring Cloud Hystrix实现服务隔离,包括线程池隔离和信号量隔离。通过示例展示了线程池隔离如何避免服务雪崩,以及信号量隔离在特定场景下的应用。同时,分析了两种隔离方式的优缺点,并提供了并发测试的实践结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

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

 

服务隔离

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

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

 

1、线程池隔离

我们在前面博客的基础上,在产品的微服务接口,增加线程名称的打印。

说明它们使用相同的线程池。

 

在 product-server 的 pom.xml 添加 Hystrix 依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

 

业务层 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
 */
@Service
public 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<ProductEntity> getList(List<Integer> ids) {
        System.out.println(Thread.currentThread().getName()+" 多个产品接口。getList");
        List<ProductEntity> 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<ProductEntity> getListFallback(List<Integer> ids){
        System.out.println(Thread.currentThread().getName()+" getListFallback 托底数据");
        List<ProductEntity> 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 住。

 

本篇博客代码地址:https://pan.baidu.com/s/1KRv6n7_LDfIA6N5CcZsgKw  提取码:xxfz

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值