Netflix Hystrix 的线程池隔离是其实现服务容错和资源隔离的重要机制之一。
线程池隔离是指为每个依赖服务创建一个独立的线程池,不同依赖服务的请求在各自的线程池中执行,从而实现不同服务之间的隔离。这样可以避免某个依赖服务的故障或长时间响应导致整个系统的线程资源被耗尽,影响其他服务的正常运行。
当一个请求到达时,Hystrix 会根据请求所对应的依赖服务,将请求提交到该服务对应的线程池中执行。如果线程池中有空闲线程,则直接使用空闲线程处理请求;如果线程池中的线程都在忙碌,则请求会被放入线程池的队列中等待处理。当队列满时,根据配置的拒绝策略来决定是否拒绝该请求或执行降级逻辑。
没有线程池隔离的时候可能因为某个接口的高并发导致其他接口也不可用。如下图:
上图中,接口 A 和接口 B 共享线程池,当某个接口出现问题,会导致当前线程池资源耗尽,从而影响其他接口。
当使用线程池隔离,不同接口有着自己独立的线程池。如下图:
此时,当接口 A 出现问题,将会耗尽线程池 1 的资源,如下图:
这时,即使线程池 1 耗尽资源对线程池 2 中的接口 B 不会产生任何影响,因为它们位于不同的线程池中。
线程池隔离作为一种重要的资源管理和并发控制机制,具有以下优缺点:
资源隔离性强:不同的任务或业务逻辑在各自独立的线程池中执行,彼此之间互不干扰。这意味着即使某个线程池中的任务出现异常或长时间运行,也不会影响到其他线程池中的任务执行,从而有效避免了一个任务的问题扩散到整个系统,确保了系统的整体稳定性。
增强系统的稳定性和可靠性:通过线程池隔离,可以为关键任务或重要服务分配独立的线程池,并根据其重要性和性能要求进行专门的配置和管理。这样在面对高并发、大流量或部分服务故障等复杂情况时,能够更好地控制和管理资源,保障关键服务的稳定运行,降低系统整体的故障风险。
便于监控和调优:每个线程池都对应着特定的业务功能或任务类型,这使得对系统的监控和性能调优更加有针对性。开发人员可以根据不同线程池的资源使用情况、任务执行时间、队列长度等指标,精确地了解各个业务功能的运行状态,及时发现潜在的性能瓶颈,并进行相应的优化调整。
灵活的资源分配:可以根据不同业务的负载特点和资源需求,为每个线程池分配不同数量的线程和队列容量等资源。
线程上下文切换开销:由于每个任务都在独立的线程池中执行,当任务在不同的线程之间切换时,会涉及到线程上下文的保存和恢复操作,这会带来一定的性能开销。
资源浪费:为每个业务或任务创建独立的线程池可能会导致资源的浪费。如果某些业务的请求量较小,但为其分配了较大的线程池,就会有大量的线程处于空闲状态,占用系统内存等资源却没有得到充分利用。
增加系统复杂性:线程池隔离需要对多个线程池进行管理和协调,包括线程池的创建、配置、销毁以及任务的分配和调度等,这增加了系统的复杂性和开发难度。
潜在的死锁风险:在多个线程池之间存在资源依赖或相互等待的情况下,如果线程池的配置和使用不当,可能会引发死锁问题。例如,线程池 A 中的任务等待线程池 B 中的任务完成后才能继续执行,而线程池 B 中的任务又在等待线程池 A 中的任务释放资源,就会导致两个线程池中的任务都无法继续执行,从而使系统陷入死锁状态。
Hystrix 采用 Bulkhead Partition 舱壁隔离技术。
舱壁隔离指的是将船体内部分为多个隔舱,一旦其中某几个隔舱发生破损进水,水流不会在其他舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船风险。
第一步:使用 Maven 引入 Hystrix 的依赖,请参考“Netflix Hystrix 入门实例”
第二步:在 Spring Boot 项目的启动类上添加 @EnableCircuitBreaker 注解,开启 Hystrix 的断路器功能。
第三步:创建服务类,其中包含一个获取用户信息的方法,并使用 @HystrixCommand 注解来配置线程隔离逻辑。代码如下:
package com.hxstrive.hystrix_demo.controller; import com.hxstrive.hystrix_demo.dto.CommonReturn; import com.hxstrive.hystrix_demo.entity.User; import com.hxstrive.hystrix_demo.service.UserService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Hystrix 线程池隔离 * @author hxstrive.com */ @RestController @RequestMapping("/demo4") public class Demo4Controller { @Autowired private UserService userService; @GetMapping("/getUserById") @HystrixCommand(groupKey = "gk_user", commandKey = "ck_getUserById", threadPoolKey = "tpk_user", threadPoolProperties = { // 设置线程池核心线程数为4,最大队列长度为128,空闲线程存活2分钟,当队列长度为 120 时开始拒绝请求 // 设置线程池的核心线程数量 @HystrixProperty(name = "coreSize", value = "4"), // 设置线程池的最大队列长度 @HystrixProperty(name = "maxQueueSize", value = "128"), // 设置线程池中空闲线程的存活时间,单位:分钟 @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), // 设置队列拒绝请求的阈值,该值通常小于 maxQueueSize @HystrixProperty(name = "queueSizeRejectionThreshold", value = "120") }) public CommonReturn<User> getUserById(@RequestParam Long id) { System.out.println("Thread Name :: " + Thread.currentThread().getName()); return userService.getUserById(id); } }
启动服务,使用浏览器访问 http://localhost:8080/demo4/getUserById?id=1 地址,输出日志信息如下图:
上图中,每次请求都是采用的不同的线程,并且输出的线程名中“tpk_user”是在 @HystrixCommand 注解中指定的线程池 KEY。