Spring Cloud 组件原理系列 Hystrix篇

2,149 阅读24分钟

白菜Java自习室 涵盖核心知识

Spring Cloud 组件原理系列(一)Eureka篇
Spring Cloud 组件原理系列(二)Hystrix篇
Spring Cloud 组件原理系列(三)Feign篇

1. Hystrix 简介

Hystrix 是 Netflix 开源的一款容错系统,能帮助使用者码出具备强大的容错能力和鲁棒性的程序。Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包(request collapsing,即自动批处理,译者注),以及监控和配置等功能。

Hystrix 源于 Netflix API 团队在 2011 年启动的弹性工程工作,而目前它在 Netflix 每天处理着数百亿的隔离线程以及数千亿的隔离信号调用。Hystrix 是基于 Apache License 2.0 协议的开源的程序库,目前托管在 GitHub 上。

为什么用Hystrix?什么情况下用?

分布式系统中,或者说微服务,各个系统错综复杂,一个系统依赖的服务比较多,而且会有多级依赖。当其中某一个服务出现问题,在高并发的情况下都有可能导致整个系统的瘫痪,蝴蝶效应在这里表现明显。

也许你会问为什么会这样?如上图,假如服务I出现较严重延迟,这时上层应用访问量tps比较大时, 首先上层应用资源会被占满,并且一般网络请求(http/rpc)都有重试机制,服务I的压力会更大,严重时则会导致应用宕机。

d6f9ad10-fea7-11e7-806c-257f5e05f705.jpg

2. Hystrix 设计理念

在复杂的分布式系统中通常有很多依赖,如果一个应用不能对来自依赖故障进行隔离,那么应用本身就处于被拖垮的风险中。在一个高流量的网站中,某一个单一后端一旦发生延迟,将会在数秒内导致所有的应用资源被耗尽,这也就是我们常说的雪崩效应。

比如在电商系统的下单业务中,在订单服务创建订单后同步调用库存服务进行库存的扣减,假如库存服务出现了故障,那么会导致下单请求线程会被阻塞,当有大量的下单请求时,则会占满应用连接数从而导致订单服务无法对外提供服务。

容错设计的理念

  • 凡是依赖都有可能会失败。
  • 凡是资源都有限制,比如 CPU、Memory、Threads、Queue。
  • 网络并不可靠,可能存在网络抖动等其他问题。
  • 延迟是应用稳定的杀手,延迟会占据大量的资源。

容错限流的手段

  • 限流:限制最大并发数。
  • 熔断:错误数达到阈值时,类似于保险丝熔断。
  • 隔离:隔离不同的依赖调用。
  • 服务降级:资源不足时进行服务降级。
  • 主动超时:在调用依赖时尽快的超时,可以设置比较短的超时时间,防止长时间的等待。

3. Hystrix 核心概念

Hystrix是Netflix公司开源的一款容错框架。 它可以完成以下几件事情:

  • 熔断器:当请求失败率达到一定的阈值时,会打开断路器开关,直接拒绝后续的请求,并且具有弹性机制,在后端服务恢复后,会自动关闭断路器开关。
  • 资源隔离:包括线程池隔离和信号量隔离,避免某个依赖出现问题会影响到其他依赖。
  • 降级回退:当断路器开关被打开,服务调用超时/异常,或者资源不足(线程、信号量)会进入指定的fallback降级方法。
  • 请求合并:可以实现将一段时间内的请求合并,然后只对后端服务发送一次请求。
  • 请求结果缓存:hystrix实现了一个内部缓存机制,可以将请求结果进行缓存,那么对于相同的请求则会直接走缓存而不用请求后端服务。

3.1. 熔断器

断路器工作原理如下:

2591074-4188ce088305a4e5.webp

Hystrix 是基于滚筒式来处理,每一秒会产生一个 buckets,每产生一个新的 buckets 就会移除一个最老的 buckets,默认是 10 秒一个窗口。buckets 在内存中就是一种数据结构,每个 buckets 会记录 Metrics 的相关数据,比如 成功、失败、超时、拒绝。

当一个 HystrixCommand 进来后,会先通过 allowRequest() 方法判断是否允许通过该次请求,allowRequest() 方法会通过 isOpen 判断断路器是否打开。断路器关闭,则允许通过该次请求;断路器打开,则会判断是否过了睡眠周期。没有过睡眠周期则返回 false,拒绝通过该次请求,过了睡眠周期则会尝试放行。

isOpen() 方法会按照 (failure) / (success+failure) 公式计算出失败率,如果失败率大于阈值,则会触发熔断。公式中的成功、失败的数据就来源于每10秒中一个窗口的滚筒数据。对于一个依赖调用,要么调用成功,要么调用失败(包括异常、超时、拒绝),这些调用结果都会记录到 buckets 中。对于调用成功结果来说,还会判断断路器开关是否打开,如果是打开状态的话,则会关闭断路器并重置相关的计数器。

线路的开路闭路详细逻辑如下:

  1. 假设线路内的容量(请求QPS)达到一定阈值(通过 HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() 配置)
  2. 同时,假设线路内的错误率达到一定阈值(通过 HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 配置)
  3. 熔断器将从『闭路』转换成『开路』
  4. 若此时是『开路』状态,熔断器将短路后续所有经过该熔断器的请求,这些请求直接走『失败回退逻辑』
  5. 经过一定时间(即『休眠窗口』,通过 HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 配置),后续第一个请求将会被允许通过熔断器(此时熔断器处于『半开』状态),若该请求失败,熔断器将又进入『开路』状态,且在休眠窗口内保持此状态;若该请求成功,熔断器将进入『闭路』状态,回到逻辑1循环往复。

3.2. 资源隔离

资源隔离的思想参考上述的舱壁隔离模式,在 hystrix 中提供了两种资源隔离策略:线程池隔离、信号量隔离。(注:『舱壁模式』将船的底部划分成一个个的舱室,这样一个舱室进水不会导致整艘船沉没。将系统所有依赖服务隔离起来,一个依赖延迟升高或者失败,不会导致整个系统失败)

3768980-432a608625bbe5f2.webp

线程池隔离

线程池隔离:线程池隔离会为每一个依赖创建一个线程池来处理来自该依赖的请求,不同的依赖线程池相互隔离,就算依赖 A 出故障,导致线程池资源被耗尽,也不会影响其他依赖的线程池资源。

3768980-63e8ecb9052ee0c7.webp

Netflix 在设计 Hystrix 时,使用 线程/线程池 来实现隔离,原因如下:

  • 多数系统同时运行了(有时甚至多达数百个)不同的后端服务,这些服务由不同开发组开发。
  • 每个服务都提供了自己的客户端库。
  • 客户端库经常会发生变动。
  • 客户端库可能会改变逻辑,加入新的网络请求。
  • 客户端库可能会包含重试逻辑,数据解析,缓存(本地缓存或分布式缓存),或者其他类似逻辑。
  • 客户端库对于使用者来说,相当于『黑盒』,其实现细节,网络访问方式,默认配置等等均对使用者透明。
  • 即使客户端库本身未发生变化,服务自身发生变化,也可能会影响其性能,从而导致客户端配置不再可靠。
  • 中间依赖服务可能包含一些其依赖服务提供的客户端库,而这些库可能不受控且配置不合理。
  • 绝大多数网络访问都采用同步的方式进行。
  • 客户端代码可能也会有失效或者高延迟,而不仅仅是在网络访问时。

面对失效时 Hystrix 包装的请求拓扑图:

3768980-9ff0154056412880.webp

线程池隔离的优势

  1. 系统完全与依赖服务请求隔离开来,即使依赖服务对应线程池耗尽,也不会影响系统其它请求。
  2. 降低了系统接入新的依赖服务的风险,若新的依赖服务存在问题,也不会影响系统其它请求。
  3. 当依赖服务失效后又恢复正常,其对应的线程池会被清理干净,相对于整个 Tomcat 容器的线程池被占满需要耗费更长时间以恢复可用来说,此时系统可以快速恢复。
  4. 若依赖服务的配置有问题,线程池能迅速反映出来(通过失败次数的增加,高延迟,超时,拒绝访问等等),同时,你可以在不影响系统现有功能的情况下,处理这些问题(通常通过热配置等方式)。
  5. 若依赖服务的实现发生变更,性能有了很大的变化(这种情况时常发生),需要进行配置调整(例如增加/减小超时阈值,调整重试策略等)时,也可以从线程池的监控信息上迅速反映出来(失败次数增加,高延迟,超时,拒绝访问等等),同时,你可以在不影响其他依赖服务,系统请求和用户的情况下,处理这些问题。
  6. 线程池处理能起到隔离的作用以外,还能通过这种内置的并发特性,在客户端库同步网络IO上,建立一个异步的 Facade(类似 Netflix API 建立在 Hystrix 命令上的 Reactive、全异步化的那一套 Java API)。

简而言之,通过线程池提供的依赖服务隔离,可以使得我们能在不停止服务的情况下,更加优雅地应对客户端库和子系统性能上的变化。尽管线程池能提供隔离性,但你仍然需要对你的依赖服务客户端代码增加超时逻辑,并且/或者处理线程中断异常,以使这些代码不会无故地阻塞或者拖慢 Hystrix 线程池。

线程池隔离的弊端

使用线程池的主要弊端是会增加系统 CPU 的负载,每个命令的执行,都包含了 CPU 任务的排队,调度,上下文切换。

对于那些本来延迟就比较小的请求(例如访问本地缓存成功率很高的请求)来说,线程池带来的开销是非常高的,这时,你可以考虑采用其他方法,例如非阻塞信号量(不支持超时),来实现依赖服务的隔离,使用信号量的开销很小。但绝大多数情况下,Netflix 更偏向于使用线程池来隔离依赖服务,因为其带来的额外开销可以接受,并且能支持包括超时在内的所有功能。

线程池隔离适用场景

适合耗时较长的接口场景,比如接口处理逻辑复杂,且与第三方中间件有交互,因为线程池模式的请求线程与实际转发线程不是同一个,所以可以保证容器有足够的线程来处理新的请求。

信号量隔离

信号量隔离: 初始化信号量 currentCount=0,每进来一个请求需要先将 currentCount 自增,再判断 currentCount 的值是否小于系统最大信号量,小于则继续执行,大于则直接返回,拒绝请求。

Hystrix 可以利用信号量,而不是线程池,来控制系统负载,但信号量不允许我们设置超时和异步化,如果你对客户端库有足够的信任(延迟不会过高),并且你只需要控制系统负载,那么你可以使用信号量。

public boolean tryAcquire() {
    int currentCount = this.count.incrementAndGet();
    if (currentCount > (Integer) this.numberOfPermits.get()) {
        this.count.decrementAndGet();
        return false;
    } else {
        return true;
    }
}

HystrixCommandHystrixObservableCommand 在两个地方支持使用信号量:

  • 失败回退逻辑:当 Hystrix 需要执行失败回退逻辑时,其在调用线程(Tomcat 线程)中使用信号量。
  • 执行命令时:如果设置了 Hystrix 命令的 execution.isolation.strategy 属性为 SEMAPHORE ,则 Hystrix 会使用信号量而不是线程池来控制调用线程调用依赖服务的并发度。

你可以通过动态配置(即热部署)来决定信号量的大小,以控制并发线程的数量,信号量大小的估计和使用线程池进行并发度估计一样(仅访问内存数据的请求,一般能达到耗时在 1ms 以内,且能达到 5000rps,这样的请求对应的信号量可以设置为 1 或者 2。默认值为 10)。

注意:如果依赖服务使用信号量来进行隔离,当依赖服务出现高延迟,其调用线程也会被阻塞,直到依赖服务的网络请求超时。

信号量隔离适用场景

适合能快速响应的接口场景,不适合一些耗时较长的接口场景,因为信号量模式下的请求线程与转发处理线程是同一个,如果接口耗时过长有可能会占满容器的线程数。

3.3. 降级回退

降级,通常指事务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务,或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保障主体业务不受影响。 Hystrix 提供的降级主要是为了容错,保证当前服务不受依赖服务故障的影响,从而提高服务的健壮性。

降级进入时机

  • 断路器打开
  • 线程池/信号量资源不足
  • 执行依赖调用超时
  • 执行依赖调用异常

降级回退方式

  • Fail Fast 快速失败:快速失败是最普通的命令执行方法,命令没有重写降级逻辑。 如果命令执行发生任何类型的故障,它将直接抛出异常。
  • Fail Fast 无声失败:指在降级方法中通过返回null,空Map,空List或其他类似的响应来完成。
  • FallBack Static:指在降级方法中返回静态默认值。 这不会导致服务以“无声失败”的方式被删除,而是导致默认行为发生。如:应用根据命令执行返回true / false执行相应逻辑,但命令执行失败,则默认为true。
  • FallBack Stubbed:当命令返回一个包含多个字段的复合对象时,适合以 Stubbed 的方式回退。
  • FallBack Cache via Network:有时,如果调用依赖服务失败,可以从缓存服务(如redis)中查询旧数据版本。由于又会发起远程调用,所以建议重新封装一个Command,使用不同的ThreadPoolKey,与主线程池进行隔离。
  • Primary + Secondary with FallBack:有时系统具有两种行为- 主要和次要,或主要和故障转移。主要和次要逻辑涉及到不同的网络调用和业务逻辑,所以需要将主次逻辑封装在不同的 Command 中,使用线程池进行隔离。为了实现主从逻辑切换,可以将主次 command 封装在外观 HystrixCommand 的 run 方法中,并结合配置中心设置的开关切换主从逻辑。由于主次逻辑都是经过线程池隔离的 HystrixCommand,因此外观 HystrixCommand 可以使用信号量隔离,而没有必要使用线程池隔离引入不必要的开销。

3.4. 请求合并

下图展示了在两种场景(未增加『请求合并器』和增加『请求合并器』)下,线程和网络连接数量(假设所有请求在一个很小的时间窗口内,例如 10ms,是『并发』的):

3768980-df8daf259deef544.webp

在并发执行 HystrixCommand 时,利用请求合并能减少线程和网络连接数量。通过使用 HystrixCollapser ,Hystrix 能自动完成请求的合并,开发者不需要对现有代码做批量化的开发。

为什么要使用请求合并

很多时候,当你创建一个对象模型,适用于对象的消费者逻辑,结果发现这个模型会导致生产者无法充分利用其拥有的资源。

例如,这里有一个包含 300 个视频对象的列表,需要遍历这个列表,并对每一个对象调用 getSomeAttribute() 方法,这是一个显而易见的对象模型,但如果简单处理的话,可能会导致 300 次的网络请求(假设 getSomeAttribute() 方法内需要发出网络请求),每一个网络请求可能都会花上几毫秒(显然,这种方式非常容易拖慢系统)。

当然,你也可以要求用户在调用 getSomeAttribute() 之前,先判断一下哪些视频对象真正需要请求其属性。 或者,你可以将对象模型进行拆分,从一个地方获取视频列表,然后从另一个地方获取视频的属性。

但这些实现会导致 API 非常丑陋,且实现的对象模型无法完全满足用户使用模式。 并且在企业级开发时,很容易因为开发者的疏忽导致错误或者不够高效,因为不同的开发者可能有不同的请求方式,这样一个地方的优化不足以保证在所有地方都会有优化。

通过将合并逻辑下沉到 Hystrix 层,不管你如何设计对象模型,或者以何种方式去调用依赖服务,又或者开发者是否意识到这些逻辑需要不需要进行优化,这些都不需要考虑,因为 Hystrix 能统一处理。 可以将 getSomeAttribute() 方法能放在它最适合的位置,并且能以最适合的方式被调用,Hystrix 的请求合并器会自动将请求合并到合并时间窗口内。

请求合并器的执行流程

3768980-3118787d56110450.webp

请求合并带来的额外开销

请求合并会导致依赖服务的请求延迟增高(该延迟为等待请求的延迟),延迟的最大值为合并时间窗口大小。

请求合并带来的额外开销是否值得,取决于将要执行的命令,高延迟的命令相比较而言不会有太大的影响。同时,缓存 Key 的选择也决定了在一个合并时间窗口内能『并发』执行的命令数量:如果一个合并时间窗口内只有 1~2 个请求,将请求合并显然不是明智的选择。事实上,如果单线程循环调用同一个依赖服务的情况下,如果将请求合并,会导致这个循环成为系统性能的瓶颈,因为每一个请求都需要等待 10ms 的合并时间周期。

然而,如果一个命令具有高并发度,并且能批量处理多个,甚至上百个的话,请求合并带来的性能开销会因为吞吐量的极大提升而基本可以忽略,因为 Hystrix 会减少这些请求所需的线程和网络连接数量。

3.5. 请求结果缓存

下图展示了在一个完整 HTTP 请求周期内,两个线程执行命令的流程:

3768980-db1bbe1a236fd005.webp

在 HystrixCommand 和 HystrixObservableCommand 的实现中,你可以定义一个缓存的 Key,这个 Key 用于在同一个请求上下文(全局或者用户级)中标识缓存的请求结果,当然,该缓存是线程安全的。

请求缓存具有的优势

  1. 不同请求路径上针对同一个依赖服务进行的重复请求(有同一个缓存 Key),不会真实请求多次:在企业级系统中,开发者彼此隔离,不太可能使用同样的方法或者策略去请求同一个依赖服务提供的资源,Hystrix 的 RequestCache 只会在内部执行 run() 方法一次,上面两个线程在执行 HystrixCommand 命令时,会得到相同的结果,即使这两个命令是两个不同的实例。

  2. 数据获取具有一致性:因为缓存的存在,除了第一次请求需要真正访问依赖服务以外,后续请求全部从缓存中获取,可以保证在同一个用户请求内,不会出现依赖服务返回不同的回应的情况。

  3. 避免不必要的线程执行:在 construct() 或 run() 方法执行之前,会先从请求缓存中获取数据,因此,Hystrix 能利用这个特性避免不必要的线程执行,减小系统开销。若 Hystrix 没有实现请求缓存,那么 HystrixCommand 和 HystrixObservableCommand 的实现者需要自己在 construct() 或 run() 方法中实现缓存,这种方式无法避免不必要的线程执行开销。

4. Hystrix 工作流程

下图展示了当你使用 Hystrix 来包装你请求依赖服务时的流程:

3768980-559f95da323c580c.webp

4.1. 构建 HystrixCommand 或者 HystrixObservableCommand 对象

使用 Hystrix 的第一步是创建一个 HystrixCommand 或者 HystrixObservableCommand 对象来表示你需要发给依赖服务的请求。你可以向构造器传递任意参数。

若只期望依赖服务每次返回单一的回应,按如下方式构造一个 HystrixCommand 即可:

HystrixCommand command = new HystrixCommand(arg1, arg2);

若期望依赖服务返回一个 Observable,并应用『Observer』模式监听依赖服务的回应,可按如下方式构造一个 HystrixObservableCommand :

HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);

4.2. 执行命令

Hystrix 命令提供四种方式( HystrixCommand 支持所有四种方式,而 HystrixObservableCommand 仅支持后两种方式)来执行你包装的请求:

  • execute():阻塞,当依赖服务响应(或者抛出异常/超时)时,返回结果
  • queue():返回 Future 对象,通过该对象异步得到返回结果
  • observe():返回 Observable 对象,立即发出请求,在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果
  • toObservable():返回 Observable 对象,但只有在订阅该对象时,才会发出请求,然后在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果
K             value   = command.execute();
Future<K>     fValue  = command.queue();
Observable<K> ohValue = command.observe(); 
Observable<K> ocValue = command.toObservable();

4.3. 结果是否有缓存

如果请求结果缓存这个特性被启用,并且缓存命中,则缓存的回应会立即通过一个 Observable 对象的形式返回。

4.4. 请求线路(类似电路)是否是开路

当执行一个命令时,Hystrix 会先检查熔断器状态,确定请求线路是否是开路。如果请求线路是开路,Hystrix 将不会执行这个命令,而是直接使用『失败回退逻辑』。

4.5. 线程池/请求队列/信号量占满时会发生什么

如果和当前需要执行的命令相关联的线程池和请求队列(或者信号量,如果不使用线程池),Hystrix 将不会执行这个命令,而是直接使用『失败回退逻辑』。

4.6. 使用 HystrixObservableCommand.construct() 还是 HystrixCommand.run()

Hystrix 将根据你使用类的不同,内部使用不同的方式来请求依赖服务:

  • HystrixCommand.run():返回回应或者抛出异常
  • HystrixObservableCommand.construct():返回 Observable 对象,并在回应到达时通知 observers,或者回调 onError 方法通知出现异常

若 run() 或者 construct() 方法耗时超过了给命令设置的超时阈值,执行请求的线程将抛出 TimeoutException (若命令本身并不在其调用线程内执行,则单独的定时器线程会抛出该异常)。在这种情况下,Hystrix 将会执行失败回退逻辑,并且会忽略最终(若执行命令的线程没有被中断)返回的回应。

若命令本身并不抛出异常,并正常返回回应,Hystrix 在添加一些日志和监控数据采集之后,直接返回回应。Hystrix 在使用 run() 方法时,Hystrix 内部还是会生成一个 Observable 对象,并返回单个请求,产生一个 onCompleted 通知;而在 Hystrix 使用 construct() 时,会直接返回由 construct() 产生的 Observable 对象。

4.7. 计算链路健康度

Hystrix 会将请求成功,失败,被拒绝或超时信息报告给熔断器,熔断器维护一些用于统计数据用的计数器。 这些计数器产生的统计数据使得熔断器在特定的时刻,能短路某个依赖服务的后续请求,直到恢复期结束,若恢复期结束根据统计数据熔断器判定线路仍然未恢复健康,熔断器会再次关闭线路。

4.8. 失败回退逻辑

当命令执行失败时,Hystrix 将会执行失败回退逻辑,失败原因可能是:

  • construct() 或 run() 方法抛出异常
  • 当线路是开路,导致命令被短路时
  • 当命令对应的线程池或信号量被占满
  • 线路超时

失败回退逻辑包含了通用的回应信息,这些回应从内存缓存中或者其他固定逻辑中得到,而不应有任何的网络依赖。如果一定要在失败回退逻辑中包含网络请求,必须将这些网络请求包装在另一个 HystrixCommand 或 HystrixObservableCommand 中。

当使用 HystrixCommand 时,通过实现 HystrixCommand.getFallback() 返回失败回退时的回应。 当使用 HystrixObservableCommand 时,通过实现 HystrixObservableCommand.resumeWithFallback() 返回 Observable 对象来通知 observers 失败回退时的回应。

若失败回退方法返回回应,Hystrix 会将这个回应返回给命令的调用者。若 Hystrix 内部调用 HystrixCommand.getFallback() 时,会产生一个 Observable 对象,并包装用户实现的 getFallback() 方法返回的回应;若 Hystrix 内部调用 HystrixObservableCommand.resumeWithFallback() 时,会将用户实现的 resumeWithFallback() 返回的 Observable 对象直接返回。

若你没有实现失败回退方法,或者失败回退方法抛出异常,Hystrix 内部还是会生成一个 Observable 对象,但它不会产生任何回应,并通过 onError 通知立即中止请求。Hystrix 默认会通过 onError 通知调用者发生了何种异常。你需要尽量避免失败回退方法执行失败,保持该方法尽可能的简单不易出错。

若失败回退方法执行失败,或者用户未提供失败回退方法,Hystrix 会根据调用执行命令的方法的不同而产生不同的行为:

  • execute():抛出异常;
  • queue():成功返回 Future 对象,但其 get() 方法被调用时,会抛出异常;
  • observe():返回 Observable 对象,当你订阅它的时候,会立即调用 subscriber 的 onError 方法中止请求;
  • toObservable():返回 Observable 对象,当你订阅它的时候,会立即调用 subscriber 的 onError 方法中止请求。

4.9. 返回正常回应

若命令成功被执行,Hystrix 将回应返回给调用方,或者通过 Observable 的形式返回。根据上述调用命令方式的不同(如第2条所示), Observable 对象会进行一些转换:

3768980-4df125684c85071e.webp

  • execute():产生一个 Future 对象,行为同 .queue() 产生的 Future 对象一样,接着调用其 get() 方法,生成由内部产生的 Observable 对象返回的回应;
  • queue():将内部产生的 Observable 对象转换(Decorator 模式)成 BlockingObservable 对象,以产生并返回 Future 对象;
  • observe():产生 Observable 对象后,立即订阅(ReplaySubject)以使命令得以执行(异步),返回该 Observable 对象,当你调用其 subscribe 方法时,重放产生的回应信息和通知给用户提供的订阅者;
  • toObservable():返回 Observable 对象,你必须调用其 subscribe() 方法,以使命令得以执行。

Spring Cloud 组件原理系列(一)Eureka篇
Spring Cloud 组件原理系列(二)Hystrix篇
Spring Cloud 组件原理系列(三)Feign篇