Hystrix解析(一)
- 熔断的目的是为了起到保护作用(熔断是一种降级策略)
- 降级
- 主动降级,促销的时候关闭非核心服务.
- 被动降级, 熔断降级、限流降级
Hystrix中的三种降级方案
熔断触发降级
@HystrixCommand(commandProperties = {
@HystrixProperty(name="circuitBreaker.enabled",value ="true"),
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "5"),
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50")
},fallbackMethod = "fallback",
groupKey = "",threadPoolKey = "order-service")
@GetMapping("/hystrix/order/{num}")
public String queryOrder(@PathVariable("num")int num){
if(num%2==0){
return "正常访问";
}
//restTemplate默认有一个请求超时时间
return restTemplate.getForObject("http://localhost:8082/orders",String.class);
}
public String fallback(int num){
return "系统繁忙";
}
- 熔断开启之后,后续的正常请求也无法发送过去.
- 如何触发熔断?"判断阈值"
10s钟之内,发起了20次请求,失败率超过50%。 熔断的恢复时间(熔断5s),从熔断开启到后续5s之内的请求,都不会发起到远程服务端.
- 熔断会有一个自动恢复。
@HystrixProperty(name="circuitBreaker.enabled",value ="true"), --开启状态
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "5"), --最小请求次数
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"), --5s熔断的时间
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value ="50") -百分比(50%)
请求超时触发降级
@HystrixCommand(fallbackMethod ="timeoutFallback",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
})
public String timeoutFallback(){
return "系统繁忙";
}
资源隔离触发降级
平台隔离、部署隔离、业务隔离、 服务隔离、资源隔离
execution.isolation.strategy - 隔离的种类,可选值只有THREAD(线程池隔离)和 SEMAPHORE(信号量隔离)。默认是THREAD线程池隔离。设置信号量隔离后,线程池相关配置失效。
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 是否是异步调用 | 资源消耗 |
---|---|---|---|---|---|
线程池隔离 | 支持,可直接返回 | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 每个服务单独用线程池 | 可以是异步,也可以是同步。看调用的方法 | 大,大量线程的上下文切换,容易造成机器负载高 |
信号量隔离 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) | 支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback | 通过信号量的计数器 | 同步调用,不支持异步 | 小,只是个计数器 |
- 信号量隔离
信号量隔离就是hystrix的限流功能。虽然名字叫隔离,实际上它是通过信号量来实现的。而信号量说白了就是个计数器。计数器计算达到设定的阈值,直接就做异常处理。
ratelimiter的令牌桶算法和漏桶算法,都是直接对请求量来计数。只是令牌桶算法可以将前面一段时间没有用掉的请求量允许余额拿过继续用。而漏桶算法一段时间就允许这么多,前面没用掉的也不能用了。
而hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多。多余的请求再来请求线程资源,就被拒绝了。所以是一种“曲径通幽”的限流方式。因为实际是通过隔离了部分容器线程资源,也算是一种隔离方式。
不涉及线程切换,资源调度,上下文的转换等,相对效率高。信号量隔离也会启动熔断机制。如果请求并发数超标,则触发熔断,返回fallback数据。commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串来配置。
execution.isolation.semaphore.maxConcurrentRequests - 信号量最大并发数。默认值是10。常见配置500~1000。如果并发请求超过配置,其他请求进入fallback逻辑。
@HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
commandProperties={
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"), // 信号量隔离
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,
value="100") // 信号量最大并发数
- 线程池隔离
信号量隔离只是起了个限制作用,它的保护能力有限:如果下游服务有问题,长时间不返回结果。本身信号量隔离对这个单个请求是起不到任何作用的。它只能限制这样的请求太多了就拒绝,不让整个服务挂。
为了解决这个问题,hystrix又产生了线程池隔离。这种隔离方式是通过引入额外线程的方式。对原来的web容器线程做管理控制:如果一个线程超时未返回,则熔断。既然引入额外的线程就涉及线程池管理、
线程的上下文切换这些额外的开销。所以相比信号量隔离,线程池隔离成本更高。
@HystrixCommand(groupKey="order-service",
commandKey = "queryOrder",
threadPoolKey="order-service",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),//线程池大小
@HystrixProperty(name = "maxQueueSize", value = "100"),//最大队列长度
@HystrixProperty(name = "keepAliveTimeMinutes", value ="2"),//线程存活时间
@HystrixProperty(name = "queueSizeRejectionThreshold", value= "15")//拒绝请求
},
fallbackMethod = "fallback")
Hystrix的请求合并
目的
- 减少访问数据库的次数
- 单位时间内的多个请求,合并为一个请求。让业务逻辑层把单个查询的sql,改为批量查询的sql。或者逻辑里面需要调用redis,那批量逻辑里面就可以用redis的pipeline去实现。
主要解决手段
- SpringCloud的Hystrix的自定义HystrixCollapse和HystrixCommand
- SpringCloud的Hystrix注解方式。
- 没有服务治理框架时,利用JDK队列、定时任务线程池处理。
示例
public class MyHystrixCommand {
@HystrixCollapser(batchMethod = "getUserByIds", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "100")})
public Future<User> getUserById(String id) {
return null;
}
@HystrixCommand
public List<User> getUserByIds(List<String> ids) {
List<User> users = new ArrayList<User>();
for (String id : ids) {
users.add(new User(id, "name: " + id));
}
return users;
}
}