服务容错保护(6、线程池与信号量隔离)

1. 线程池隔离

1.1 简介

  1. 容器线程(tomcat、jetty)与 远程服务调用线程隔离,即异步执行服务间远程调用
  2. 如果依赖多个微服务,多个依赖间相互隔离
    线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。
  3. 线程隔离的优点:
    • 一个依赖可以给予一个线程池,这个依赖的异常不会影响其他的依赖。
    • 使用线程可以完全隔离第三方代码,请求线程可以快速放回。
      当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复。
    • 可以完全模拟异步调用,方便异步编程。
    • 使用线程池,可以有效的进行实时监控、统计和封装。
  4. 线程隔离的缺点:
    增加了线程的开销。每一个依赖调用都会涉及到队列,调度,上下文切换,而这些操作都有可能在不同的线程中执行。
  5. 线程隔离的适用场景
    • 不受信服务(第三方接口服务)
    • 有限依赖(依赖的服务不能太多)

1.2 核心代码

	@HystrixCommand
	(
		//熔断回调方法
		fallbackMethod = "fallBack02",
		//给provider提供的服务进行线程池分组,如果groupKey一样,运行在同一个线程池
		groupKey = "products-provider",
		//指定需要调用的provider接口的方法名字
		commandKey = "getProductTreadIsolution",
		//给线程池起名字
		threadPoolKey = "products-provider",
		threadPoolProperties = {
			//线程池大小
			@HystrixProperty(name = "coreSize", value = "30"),
			//最大队列长度,正数表示阻塞队列
			@HystrixProperty(name = "maxQueueSize", value = "100"),
			//线程存活时间
			@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
			//拒绝请求
			@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")
		}
	)
	@Override
	public Product getProductTreadIsolution(Long id) {
		System.out.println("=== getProductTreadIsolution:"+Thread.currentThread().getName());
		return restTemplate.getForObject("http://PRODUCT-PROVIDER/product/" + id, Product.class);
	}


	@HystrixCommand
		(
			//熔断回调方法
			fallbackMethod = "fallBack02",
			//给provider提供的服务进行线程池分组,如果groupKey一样,运行在同一个线程池
			groupKey = "products-provider2",
			//指定需要调用的provider接口的方法名字
			commandKey = "getProductTreadIsolution2",
			//给线程池起名字
			threadPoolKey = "products-provider2",
			threadPoolProperties = {
				//线程池大小
				@HystrixProperty(name = "coreSize", value = "30"),
				//最大队列长度,正数表示阻塞队列
				@HystrixProperty(name = "maxQueueSize", value = "100"),
				//线程存活时间
				@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
				//拒绝请求
				@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")
			}
		)
	@Override
	public Product getProductTreadIsolution2(Long id) {
		System.out.println("=== getProductTreadIsolution:"+Thread.currentThread().getName());
		return restTemplate.getForObject("http://PRODUCT-PROVIDER/product/" + id, Product.class);
	}

	public Product fallBack02(Long id) {

		System.out.println(LocalTime.now() + " =========fallBack02");
		Product product = new Product();
		product.setId(-1111L);
		product.setNote("系统繁忙稍后再试......");
		return product;
	}

1.3 线程池隔离参数介绍

参数 作用 默认值 备注
groupKey 服务名(相同服务用同一个名称,如商品、用户等) getClass().getSimpleName(); 在consumer里面为每个provider服务,设置group标识,一个group使用一个线程池
commandKey 接口(服务下面的接口,如购买商品) 当前执行方法名 consumer的接口名称
threadPoolKey 线程池的名称:配置全局唯一标识线程池的名称,相同名称的线程池是同一个 默认是分组名groupKey 配置全局唯一标识线程池的名称,相同名称的线程池是同一个
coreSize 线程池大小:最大的并发执行数量 10 设置标准:每秒最大支撑的请求数(99%平均响应时间+一个桓冲值)
比如:每秒能处理1000个请求,99%的响应时间是60ms,那么公式是:1000*(0.060 + x)
maxQueueSize 最大队列长度 设置BlockingQueue的最大长度 -1
queueSizeRejectionThreshold 拒绝请求:设置拒绝请求的临界值 5 此属性是不用于maxQueueSize = -1时;设置这个值的原因是maxQueueSize运行时不能改变,我们可以通过这个变量动态修改允许排队的长度
keepAliveTimeMinutes 线程存活时间:设置存活时间,单位分钟 1 控制一个线程从使用完成到释放的时间

1.4 测试结果

服务容错保护(6、线程池与信号量隔离)_第1张图片
postman分别调用
http://localhost:8001/hystrixproduct/1
http://localhost:8001/hystrixproduct2/1
从控制台日志的线程名称可以看到,这是在两个线程池分别占用了一个线程完成的

2. 信号量隔离

2.1 简介

信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请)
如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。

  • 优点:轻量,无额外开销
  • 缺点:不支持异步调用
  • 适用场景:
    1. 受信服务(公司内部服务)
    2. 高依赖(网关)

2.2 代码示例

	//fallbackMethod当前请求数在一个窗口期内容达到阈值,会调用fallBack02
	@HystrixCommand(fallbackMethod = "fallBack02", commandProperties = {
		@HystrixProperty(
			// SEMAPHORE表示信号量隔离
			name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,
			value = "SEMAPHORE"),
		@HystrixProperty(
			// 信号量最大限度,当请求达到或超过该设置值后,其其余就会被拒绝。默认值是10。
			name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,
			value = "5")
	})
	@Override
	public Product getProductSemaphoreIsolution(Long id) {
		return restTemplate.getForObject("http://PRODUCT-PROVIDER/product/" + id, Product.class);
	}

2.3 信号量隔离参数介绍

参数 作用 默认值 备注
execution.isolation.strategy 隔离策略配置项 THREAD 只有两种:THREAD和SEMAPHORE
execution.isolation.thread.timeoutInMilliseconds 超时时间 1000ms 1.在THREAD模式下,达到超时时间自动中断
2.在SEMAPHORE模式下,会等待执行完成后,再去判断是否超时
execution.isolation.thread.interruptOnTimeout 是否打开超时线程中断 TRUE THREAD模式有效
execution.isolation.semaphore.maxConcurrentRequests 信号量最大并发数 10 SEMAPHORE模式有效
fallback.isolation.semaphore.maxConcurrentRequests fallback最大并发数 10 SEMAPHORE模式有效

3.线程池与信号量隔离

3.1 区别

- 线程池隔离 信号量隔离
线程 请求线程和调用provider不是同一条线程 请求线程和调用provider是同一条线程
开销 排队、调度、上下文开销等 无线程切换、开销低
异步 支持 不支持
并发支持 支持(最大线程池大小) 支持(最大信号量上线)
传递HttpHeader 不支持 支持
支持超时 支持 不支持

3.2 什么情况下用线程隔离

  • 请求并发量大,并且耗时长(请求耗时长一般是计算量大或读数据库):
    采用线程隔离策略,可以保证大量的容器(tomcat)线程可用,不会由于服务原因一致处于阻塞或等待状态,快速失败返回。
  • 不受信服务(第三方接口服务)

3.3 什么情况下用信号量隔离

请求并发量大,并且耗时短(请求耗时短可能是计算量小,或读缓存):
采用信号量隔离策略,因为这类服务返回通常会非常快,不会占用容器太长时间,而且也减少了线程切换的一些开销,提高了缓存服务的效率。

你可能感兴趣的:(springcloud)