本篇在用不到的时候可以不学,只要学会 sentinel 就可以了
Hystrix能干嘛
服务降级、服务熔断、接近实时的监控······
hystrix保障我们的分布式系统不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。
1、服务降级
让出服务的资源, 给另外的服务
服务降级强调的是当前服务 降级(或不再调用)被调用的报错服务
这里的报错服务包括:
- 对方服务的程序运行异常
- 超出我方规定的超时时间
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
2、服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝:服务的降级->进而熔断->恢复调用链路
3、服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
服务降级
8001先从自身找问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
为什么客户端和服务端都可以使用hystrix?
- 服务端
serverA 调用了 serverB 的服务, serverB可能已经宕机或者负载, 无法及时的做出反应, 所以我们可以给 serverA 提供 hystrix 保障 serverA 能够及时的做出反馈, 而不是 一直阻塞等待 serverB 最终弹出一堆报错的不友好信息(而且还能不安全)
- 客户端
clientA 调用了 serverA 的服务, clientA 不信任 serverA, 所以一般都会有个另外一个 serverB 做保障. 如果 clientA 访问 serverA 时阻塞超时, clientA 会熔断 serverA, 并对 serverA 降级, 最后clientA返回一个兜底的方法给用户, 下次再次访问 clientA 时, clientA 不再访问 serverA, 而是 serverA 集群中的另一台服务 serverB(多次访问时, 还会给个请求给 serverA 试试能不能用了)
这是一次hystrix 熔断并fallback 的过程
hystrix可以配置在 clientA 处 防止蓝标2出现问题阻塞
也可以配置在 serverB 防止 蓝标3 出现阻塞和超时的情况
了解这些, 接下来我们可以分别介绍介绍怎么玩
server服务端(提供端)
依赖:
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 其他略 -->
</dependencies>
只要导入包后, hystrix 默认启动, 不需要另外设置
底层默认使用的还是 ribbon 做负载均衡
application.yml :
###consul服务端口号
server:
port: 8001
spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
接着我们可以给 controller 或者 service 添加 hystrix 操作功能
这里我们选择标记 controller 让 hystrix 拦截并使用
hystrix 标记超时的注解是 @HystrixCommand
注解可以使用 fallbackMethod 标记一个方法, 让@HystrixCommand标记的方法在发生错误时调用的备选方案
贴出完整代码:
public interface PaymentService {
/**
* 正常访问一切 OK
*
* @param id
* @return
*/
String paymentInfoOk(Integer id);
/**
* 超时访问,演示降级
*
* @param id
* @return
*/
String paymentInfoTimeOut(Integer id);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfoOk(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O";
}
@Override
public String paymentInfoTimeOut(Integer id) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O,耗费3秒";
}
}
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOk(id);
log.info("****result: " + result);
return result;
}
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfoTimeOut(id);
log.info("****result: " + result);
return result;
}
public String fallbackPaymentInfoTimeOut(Integer id) {
String result = "fallback";
log.info("****result: " + result);
return result;
}
}
@EnableDiscoveryClient
@SpringBootApplication
// 通用熔断器注解, 有了这个下面的`EnableHystrix`注解就不需要了
@EnableCircuitBreaker
//@EnableHystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) throws Exception {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
这里选择 controller 做 hystrix 标记可能不太恰当, 可能标记 service 可能会更好一点, 不过没关系,能够产生效果就行
这部分完成
核心代码在这里:
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
超时时间是3秒, 实际延迟时间是 5 秒
所以代码肯定超时
访问: [http://localhost:8001/payment/hystrix/timeout/3](http://localhost:8001/payment/hystrix/timeout/3)
反馈的信息: fallback
报错, 然后被 hystrix 拦截
hystrix 默认超时时间为 1 秒, 而接口延迟是 5 秒, 所以这个接口会报错
将 hystrix 超时时间定位 6秒
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
启动jmeter
创建线程组:
10个线程, 跑 1秒 循环 1次
发现10个请求都成功了
改成 , 20个请求都会成功
但是如果改成:
就会走hystrix备选方案:
但是下面10个请求会成功
这是什么个情况?
hystrix 线程隔离
上图的情况是很容易出现的, 所以的请求都阻塞在 订单服务 这里.
而服务的线程总量是有限的(假设只有100条), 那么现在的请求全部被阻塞在 订单服务 前
那么用户就没有多余的线程去访问 商品和用户 服务, 服务出现级联错误, 也就是服务雪崩
遇到这种问题要怎么解决呢?
hystrix使用了线程隔离技术
大致的方法是, 给每个服务提供默认 10个线程大小 的线程池
这样即使订单服务出现问题, 导致线程阻塞, 最终阻塞的线程也只不过是 10条, 不会导致 另外 的 90 条线程出现问题
这种方式就是令牌桶限流算法
hystrix在遇到各个类型的 command 指令时, 会将该类型设计成 key , 然后创建一个线程池, 最为 value, 存放到一个 ConcurrentHashMap 中, 如下图所示
具体的源码:
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
ThreadFactory threadFactory = null;
if (!PlatformSpecific.isAppEngineStandardEnvironment()) {
threadFactory = new ThreadFactory() {
protected final AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet());
thread.setDaemon(true);
return thread;
}
};
} else {
threadFactory = PlatformSpecific.getAppEngineThreadFactory();
}
final int dynamicCoreSize = corePoolSize.get();
final int dynamicMaximumSize = maximumPoolSize.get();
if (dynamicCoreSize > dynamicMaximumSize) {
logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " +
dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory);
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory);
}
}
执行Command的方式一共四种
- execute():以同步堵塞方式执行run()。调用execute()后,hystrix先创建一个新线程运行run(),接着调用程序要在execute()调用处一直堵塞着,直到run()运行完成。
- queue():以异步非堵塞方式执行run()。调用queue()就直接返回一个Future对象,同时hystrix创建一个新线程运行run(),调用程序通过Future.get()拿到run()的返回结果,而Future.get()是堵塞执行的。
- observe():事件注册前执行run()/construct()。第一步是事件注册前,先调用observe()自动触发执行run()/construct()(如果继承的是HystrixCommand,hystrix将创建新线程非堵塞执行run();如果继承的是HystrixObservableCommand,将以调用程序线程堵塞执行construct()),第二步是从observe()返回后调用程序调用subscribe()完成事件注册,如果run()/construct()执行成功则触发onNext()和onCompleted(),如果执行异常则触发onError()。
- toObservable():事件注册后执行run()/construct()。第一步是事件注册前,调用toObservable()就直接返回一个Observable对象,第二步调用subscribe()完成事件注册后自动触发执行run()/construct()(如果继承的是HystrixCommand,hystrix将创建新线程非堵塞执行run(),调用程序不必等待run();如果继承的是HystrixObservableCommand,将以调用程序线程堵塞执行construct(),调用程序等待construct()执行完才能继续往下走),如果run()/construct()执行成功则触发onNext()和onCompleted(),如果执行异常则触发onError()
线程隔离的优点
[1]:应用程序会被完全保护起来,即使依赖的一个服务的线程池满了,也不会影响到应用程序的其他部分。
[2]:我们给应用程序引入一个新的风险较低的客户端lib的时候,如果发生问题,也是在本lib中,并不会影响到其他内容,因此我们可以大胆的引入新lib库。
[3]:当依赖的一个失败的服务恢复正常时,应用程序会立即恢复正常的性能。
[4]:如果我们的应用程序一些参数配置错误了,线程池的运行状况将会很快显示出来,比如延迟、超时、拒绝等。同时可以通过动态属性实时执行来处理纠正错误的参数配置。
[5]:如果服务的性能有变化,从而需要调整,比如增加或者减少超时时间,更改重试次数,就可以通过线程池指标动态属性修改,而且不会影响到其他调用请求。
[6]:除了隔离优势外,hystrix拥有专门的线程池可提供内置的并发功能,使得可以在同步调用之上构建异步的外观模式,这样就可以很方便的做异步编程(Hystrix引入了Rxjava异步框架)。
尽管线程池提供了线程隔离,我们的客户端底层代码也必须要有超时设置,不能无限制的阻塞以致线程池一直饱和。
线程隔离的缺点
[1]:线程池的主要缺点就是它增加了计算的开销,每个业务请求(被包装成命令)在执行的时候,会涉及到请求排队,调度和上下文切换。不过Netflix公司内部认为线程隔离开销足够小,不会产生重大的成本或性能的影响。
The Netflix API processes 10+ billion Hystrix Command executions per day using thread isolation. Each API instance has 40+ thread-pools with 5–20 threads in each (most are set to 10).
Netflix API每天使用线程隔离处理10亿次Hystrix Command执行。 每个API实例都有40多个线程池,每个线程池中有5-20个线程(大多数设置为10个)。
对于不依赖网络访问的服务,比如只依赖内存缓存这种情况下,就不适合用线程池隔离技术,而是采用信号量隔离,后面文章会介绍。
因此我们可以放心使用Hystrix的线程隔离技术,来防止雪崩这种可怕的致命性线上故障。
这一小段转载自简书
作者:新栋BOOK
标题:Hystrix线程隔离技术解析-线程池
client客户端(消费端)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
###consul服务端口号
server:
port: 80
spring:
application:
name: cloud-consumer-order
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
这里需要注意 application.yml 中如果开启了 hystrix 就需要同时启动 ribbon 和 hystrix 的超时配置
feign:
hystrix:
enabled: true
##设置feign 客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 7000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 7000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 7000
如果不启动 hystrix 也需要
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
//@EnableHystrix
@EnableCircuitBreaker
public class OrderHystrixMain80 {
public static void main(String[] args) throws Exception {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
@FeignClient("consul-provider-payment")
public interface PaymentClient {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) throws InterruptedException;
}
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentClient paymentClient;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
return paymentClient.paymentInfoOk(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) throws InterruptedException {
return paymentClient.paymentInfoTimeOut(id);
}
}
问题
问题一: 超时问题
现在使用[http://localhost:80/consumer/payment/hystrix/timeout/2](http://localhost:80/consumer/payment/hystrix/timeout/2)访问地址会报错
失败信息:
以上,面将错误直接丢给客户是一种非常失职的方式
那么要怎么解决呢?
非常简单, 就是不让用户看到不就行了.
方法有两种:
- 使用 hystrix 将熔断的服务降级掉(这样就遇不到报错的服务了)
- 熔断返回的错误数据包拦截下来, 包装一个新的页面给用户
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut")
缺少了自定义的超时时间
可以这么做:
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
给他定时 3 秒, 3秒后超时
如果你是在客户端客户端什么都有只用了 openFeign 的话, 可以这么做:
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
我们在客户端使用了
openFeign可以这么配置, 他底层其实使用了 ribbon(旧版本)
当然如果你给客户端添加 hystrix 就可以这么做:
feign:
hystrix:
enabled: true
##设置feign 客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 7000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 7000
# hystrix 内部的超时时间, 两个超时时间相同才会有准确的超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 7000
这种情况是使用了 hystrix 之后的超时时间方法, 当然你也可以添加 openFeign超时配置, 也能生效, 但还是要相同, 或者 比 7000 更小才能生效, 太麻烦了
问题二: 代码冗余
服务端
@HystrixCommand(fallbackMethod = "fallbackPaymentInfoTimeOut", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfoTimeOut(id);
log.info("****result: " + result);
return result;
}
private final static AtomicInteger counter = new AtomicInteger(0);
public String fallbackPaymentInfoTimeOut(Integer id) {
return "<h1>访问'/payment/hystrix/timeout'失败: " + counter.getAndIncrement() + "</h1>";
}
这段代码是有问题的
每标记一个方法, 都需要配合的写一个新的 fallbackMethod = "fallbackPaymentInfoTimeOut"方法明显是不合理的
public String fallbackPaymentInfoTimeOut(Integer id) {
return "<h1>访问'/payment/hystrix/timeout'失败: " + counter.getAndIncrement() + "</h1>";
}
并不是所有方法都需要专属的兜底方法
我们可以统一配置
第一步
给服务端提供代码
给需要配置的类上添加@DefaultProperties(defaultFallback = "globalFallback")
第二步
并且在该类中添加方法:
public String globalFallback() {
return "<h1>Global port: " + port + " counter: " + counter.getAndIncrement() + "系统繁忙, 请稍后再尝试</h1>";
}
第三步
接着添加两个方法:
@HystrixCommand
@GetMapping("/payment/hystrix/timeout01/{id}")
public String paymentInfoTimeOut01(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfoTimeOut(id);
log.info("timeout01: " + result);
return result;
}
@HystrixCommand
@GetMapping("/payment/hystrix/timeout02/{id}")
public String paymentInfoTimeOut02(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfoTimeOut(id);
log.info("timeout01: " + result);
return result;
}
记住需要加上
@HystrixCommand注解, 否则全局异常 hystrix 失效, hystrix 需要标记方法才能生效
问题三: 和业务逻辑混合在一起
解决办法: 把这种 兜底的方法 放在客户端上
在 客户端 添加下列代码:
import com.zhazha.springcloud.client.PaymentClient;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackClient implements PaymentClient {
@Override
public String paymentInfoOk(Integer id) {
return "====PaymentHystrixService fall back paymentInfoOk,o(╥﹏╥)o====";
}
@Override
public String paymentInfoTimeOut(Integer id) throws InterruptedException {
return "====PaymentHystrixService fall back paymentInfoTimeOut,o(╥﹏╥)o====";
}
}
@FeignClient(value = "consul-provider-payment", fallback = PaymentFallbackClient.class)
public interface PaymentClient {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) throws InterruptedException;
}
主要方法就是给
@FeignClient注解添加fallback属性 值得注意的是@Component注解一定不要忘记加上
此时关闭 8001 服务后, 客户端会走 兜底方法 , 不会再挂起等待
http://localhost/consumer/payment/hystrix/timeout/2
接着再启动 8001 等一下又可以重新放到到该服务
服务熔断
熔断机制是应对雪崩效应(级联故障)的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间太长时,
会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息,当检测到该节点微服务调用响应正常后,恢复调用链路
在 springcloud 中如果使用 hystrix 做服务熔断, 他就有一个阈值 如果 5 秒内出现 20次 错误, 那么就会触发熔断机制.
hystrix触发熔断机制的方式还是给方法添加@HystrixCommand注解
hystrix 熔断的功能总结下来就是:
- hystrix标记方法
- 方法错误时调用 fallback方法, 统计方法错误次数
- 次数统计触发阈值后, 直接熔断(熔断器在全开状态:线路断开)
- 下次调用不会再去访问该服务, 而是直接调用 fallback
- 熔断后服务不可用, 是因为客户端直接跳过了调用正常方法的过程, 直接执行 fallback 方法
- 熔断后不是一直都不去使用熔断的服务, hystrix会找机会去试试是否能够处理客户端的请求(熔断器在半开状态:线路接通), 以判断服务是否恢复(熔断器在关闭状态 :线路全部接通)
熔断器的状态:
- 熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
- 熔断关闭
熔断关闭不会对服务进行熔断
- 熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
可以配合大佬的博客看
可以看下Martin Fowler大佬的文章CircuitBreaker
It's common for software systems to make remote calls to software running in different processes, probably on different machines across a network. One of the big differences between in-memory calls and remote calls is that remote calls can fail, or hang without a response until some timeout limit is reached. What's worse if you have many callers on a unresponsive supplier, then you can run out of critical resources leading to cascading failures across multiple systems.
软件系统对运行在不同进程的软件进行远程调用是很常见的, 这些软件可能运行在网络上的不同机子上. 内存调用和远程调用的不同点之一是远程调用可能会失败或者挂起而没有响应直至超过规定时间的限制. 更糟糕的是如果你有很多无响应的callers, 则你可能耗尽重要资源从, 而导致多个系统的级联错误.
The basic idea behind the circuit breaker is very simple. You wrap a protected function call in a circuit breaker object, which monitors for failures. Once the failures reach a certain threshold, the circuit breaker trips, and all further calls to the circuit breaker return with an error, without the protected call being made at all. Usually you'll also want some kind of monitor alert if the circuit breaker trips.
断路器背后的思想非常简单. 你包装了个受保护的函数调用在断路器对象中, 该对象监听故障. 一旦错误达到某个阈值, 断路器会跳闸,并且对断路器的所有进一步调用都会返回错误,而根本不会进行受保护的调用。通常,如果断路器跳闸,您还需要某种监视器警报。
这是大佬对断路器的描述和使用
案例代码
现在我们根据上面的总结编写代码
在服务端添加代码:
在PaymentService添加
String circuitBreaker(@PathVariable("id") Integer id);
@Override
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 断路后重新开启服务的请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "30"),// 失败率达到多少后跳闸
})
public String circuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
hystrix 配置说明
circuitBreaker.enabled 设置断路器是否起作用。 默认值:truecircuitBreaker.requestVolumeThreshold 设置在一个滚动窗口中,打开断路器的最少请求数。 比如:断路器重新数量开启条件。如果值是20,在一个窗口内(比如10秒),收到19个请求,即使这19个请求都失败了,断路器也不会打开。 默认值:20
circuitBreaker.sleepWindowInMilliseconds 设置在回路被打开,拒绝请求到再次尝试请求并决定回路是否继续打开的时间。 窗口时间。一次统计的时间单位 默认值:5000(毫秒)
circuitBreaker.errorThresholdPercentage 设置打开回路并启动回退逻辑的错误比率。 在百分比为50%的条件下,一次时间窗口内, 发生了30次请求, 有15次失败,则触发 默认值:50%
PaymentController
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.circuitBreaker(id);
log.info("****result: " + result);
return result;
}
接着你就可以这么玩:
http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1
首先故意访问错误 30 次
然后立即在网页上访问11次: http://localhost:8001/payment/circuit/1
记住是立即访问 11 次哦, 否则就会恢复正常 因为超出 10 秒 的窗口时间,hystrix就会重新尝试放行请求
你会发现前面10次都是报错
这种现象完全符合大佬对 断路器的描述:
Once the failures reach a certain threshold, the circuit breaker trips, and all further calls to the circuit breaker return with an error, without the protected call being made at all.
断路器开启或者关闭的条件
当满足一定的阀值的时候(默认10秒内超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%的请求失败)
到达以上阀值,断路器将会开启
当开启的时候,所有请求都不会进行转发
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发;如果成功,断路器会关闭;若失败,继续开启。重复4和5
断路器打开之后
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback,通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果
原来的主逻辑如何恢复
对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复;如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时
所有配置
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
return "hello 2020";
}
public String str_fallbackMethod()
{
return "*****fall back str_fallbackMethod";
}
hystrix工作流程
Hystrix工作流程:github.com/Netflix/Hys…
官网图例:
步骤说明:
- 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象
- 命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)
- 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式返回
- 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)
- 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)
- Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知
- Hystrix会将 “成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”
- 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候
- 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回
tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者
服务监控hystrixDashboard
1、概述
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
2、仪表盘9001
新建cloud-consumer-hystrix-dashboard9001
(1)pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
HystrixDashboardMain9001+新注解@EnableHystrixDashboard
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboardMain9001 {
public static void main(String[] args) throws Exception {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
打开: http://localhost:9001/hystrix
所有Provider微服务提供类(8001/8002/8003/800x)都需要监控依赖配置
3、断路器演示
服务监控hystrixDashboard
(1)修改hystrix-payment8001
注意:新版本Hystrix需要在主启动类PaymentHystrixMain8001中指定监控路径
@EnableDiscoveryClient
@SpringBootApplication
// 通用熔断器注解, 有了这个下面的`EnableHystrix`注解就不需要了
@EnableCircuitBreaker
//@EnableHystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) throws Exception {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
监控测试
观察监控窗口
9001监控8001,填写监控地址:http://localhost:8001/hystrix.stream
测试地址:
http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1
上述测试通过
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的
监控结果,成功
监控结果,失败
如何看?
7色、1圈、1线
1圈:
实心圆:共有两种含义,它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例
1线:
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
整图说明:
整图说明2:
搞懂一个才能看懂复杂的: