谈谈美股熔断和服务熔断

首先说一下最近比较火热的美股熔断,美股熔断究竟是什么意思?

先看看维基百科:

熔断机制(英语:Circuit breaker / Trading curb)指的是在股票市场的交易时间中,当价格波动的幅度达到某一个限定的目标(熔断点)时,对其暂停交易一段时间的机制。此机制如同保险丝在电流过大时候熔断比较相似,故而得名。熔断机制推出的目的是为了防范,给市场更多的冷静时间,避免恐慌情绪蔓延导致市场波动,从而防止大规模股价下跌现象的发生。然而熔断机制也因切断了资金的流通性,同样会造成市场情绪加大,并令市场风险在熔断期结束后继续扩大;

美国指数熔断机制的基准指数为标普500(标普500是由道琼斯指数延申而来,是一个由1957年起记录美国股市的平均记录,观察范围达美国的500家上市公司,与道琼斯指数相比,标准普尔500包含的公司更多,因此风险更为分散,能够反映更广泛的市场变化)单项跌幅阈值为7%、13%、20%。当指数较前一天收盘点位下跌7%、13%时,全美证券市场交易将暂停15分钟,当指数较前一天收盘点位下跌20%时,当天交易停止;

这就类似于我们的服务熔断,在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如:
商品详情展示服务会依赖商品服务, 价格服务, 商品评论服务;如果此时商品评论服务的健康状况低于设定阈值那么熔断就会打开,请求在一段时间内(这个一段时间可以说是美国证券交易所暂停15,其实在我们的服务中关闭时间是没有那么长的,这里只是比喻)就不会再去调用商品评论服务,而是直接返回错误信息或者执行降级策略,当5s(默认)之后断路器自动切换到半开路状态,会放进来几个请求,观察请求的返回情况,如果请求成功断路器切回闭路状态,否则重新切换到开路状态;其实无论是美股的熔断还是我们的服务熔断道理都是一样的;技术来源于生活!

下面我们来详细说一下服务熔断;

服务雪崩

服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用 逐渐放大 的过程;

下图中,A为服务的调用者,B是A的服务提供者,C是B的服务提供者,当C的不可用,引起B的不可用,并将不可用逐渐放大A时, 造成了整体的不可用服务雪崩就形成了.

image.png

服务雪崩的每个阶段都可能由不同的原因造成, 比如造成 服务不可用 的原因有:

  • 硬件故障

  • 程序Bug

  • 缓存击穿

  • 用户大量请求

硬件故障和程序Bug就不多解释了,说一说缓存击穿和用户大量请求,

缓存击穿:

缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用. 在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用.

用户大量的请求

在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单. 服务调用端的会存在大量服务异常后的重试逻辑. 这些重试都会进一步加大请求流量.

那服务的调用者会不会也不可用,当然,当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态;

服务雪崩的应对策略

  • 流量控制
  • 改进缓存模式
  • 服务自动扩容
  • 服务调用者降级服务

这里简单说一下啊降级!

服务调用者降级服务:

资源隔离

不可用服务的调用快速失败

资源隔离主要是对调用服务的线程池进行隔离.

不可用服务的调用快速失败一般通过 超时机制, 熔断器 和熔断后的降级方法来实现.

例如接下来我们会说到Hystrix,其中Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.

使用Hystrix预防雪崩

在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.

例如:

在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如: 商品详情展示服务会依赖商品服务, 价格服务, 商品评论服务. 如图所示:

image.png

调用三个依赖服务会共享商品详情服务的线程池. 如果其中的商品评论服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成服务雪崩. 如图所示:

image.png

Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩. 如下图所示, 当商品评论服务不可用时, 即使商品服务独立分配的20个线程全部处于同步等待状态,也不会影响其他依赖服务的调用.

image.png

创建 hystrix-consumer-service

引入依赖:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
</dependencies>

使用nacos作为服务注册发现;其中openfeign中已经包含了hystrix的依赖;

配置文件:

server.port=8765
spring.application.name=service-hystrix
#开启熔断,默认为false
feign.hystrix.enabled=true
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

创建CaseServiceClient:

@FeignClient(value = "service-say",fallbackFactory = CaseServiceFallback.class )
public interface CaseServiceClient {

@RequestMapping(value = "/say",method = RequestMethod.GET)
String sayFromClient(@RequestParam(value = "name") String name);
}

有很多案例中都是使用fallback,这里我使用的是fallbackFactory,这两个的区别是fallbackFactory可以返回失败的原因;例如下面的throwable

创建fallback:

@Component
public class CaseServiceFallback implements FallbackFactory<CaseServiceClient> {
@Override
public CaseServiceClient create(Throwable throwable) {
return new CaseServiceClient() {
@Override
public String sayFromClient(String name) {
return "sorry " + name +" error cause: "+ throwable;
}
};
}
}

编写一个Controller

@RestController
public class CaseController{

@Autowired
CaseServiceClient caseServiceClient;

@GetMapping(value = "/hi")
public String sayHi(@RequestParam String name) {
return caseServiceClient.sayFromClient( name );
}
}

在启动类上加上

@EnableFeignClients@EnableDiscoveryClient注解;

我们启动项目并浏览器访问 http://localhost:8765/hi?name=haoxy ,注意这时我们并没有启动hystrix-provider-service,因为还没有创建这个工程;

页面显示:

sorry haoxy error cause: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: service-say

创建 hystrix-provider-service工程

添加依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>

增加配置

server.port=8075
spring.application.name=service-say
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

创建Controller

@RestController
public class ProviderController {

@Value("${server.port}")
String port;

@RequestMapping("/say")
public String home(@RequestParam(value = "name", defaultValue = "haoxy") String name) {
return "hi " + name + " ,i am from port:" + port;
}
}

启动 hystrix-provider-service工程,并同样在浏览器访问: http://localhost:8765/hi?name=haoxy

页面显示:

hi haoxy ,i am from port:8075

源码地址:https://github.com/haoxiaoyong1014/springcloud-examples