Spring Cloud 学习笔记

一、Eureka注册中心

在Eureka的服务治理体系中, 主要分为服务端与客户端两个不同的角色, 服务端为服务注册中心, 而客户端为各个提供接口的微服务应用。

对于服务注册中心、 服务提供者、 服务消费者这三个主要元素来说, 后两者(也就是 Eureka 客户端)在整个运行机制中是大部分通信行为的主动发起者, 而注册中心主要是处理请求的接收者。

在Eureka的服务治理体系中, 主要分为服务端与客户端两个不同的角色, 服务端为服务注册中心, 而客户端为各个提供接口的微服务应用。 当我们构建了高可用的注册中心之后, 该集群中所有的微服务应用和后续将要介绍的一些基础类应用(如配置中心、 API网关等)都可以视作该体系下的一个微服务(Eureka客户端)。 服务注册中心也一样, 只是高可用环境下的服务注册中心除了作为客户端之外, 还为集群中的其他客户端提供了服务注册的特殊功能。 所以,Eureka客户端的配置对象存在于所有Eureka服务治理体系下的应用实例中。

Eureka客户端的配置主要分为以下两个方面。• 服务注册相关的配置信息, 包括服务注册中心的地址、 服务获取的间隔时间、 可用区域等。• 服务实例相关的配置信息, 包括服务实例的名称、IP地址、 端口号、 健康检查路径等。

元数据

它是Eureka 客户端在向服务注册 中心发送注册请求时, 用来描述自身服务信息的对象, 其中包含了一些标准化的元数据, 比如 服务名称、 实例名称、 实例IP、 实例端口等用于服务治理的重要信息;以及一些用 千负载均衡策略或是其他特殊用途的自定义 元数据信息。

实例名配置

我们可以直接通过设置server.part=0 或者使用随机数 server.port=${randorn.int[10000,19999]} 来让Tomcat 启动的时候采用随机端口。 但是这个时候我们会发现注册到 Eureka Server 的实例名都是相同的, 这会使得只有一个服务实例能够正常提供服务。 对于这个问题, 我们就可以通过设置实例名规则来轻松解决:eureka.instance.instanceid=${spring.applica七ion.name}:${random.int}}通过上面的配置, 利用应用名加随机数的方式来区分不同的实例, 从而实现在同一主机上, 不指定端口就能轻松启动多个实例的效果

通信协议

    默认情况下, Eureka 使用Jersey和XStream配合JSON作为Server与Client之间的通信协议。 你也可以选择实现自己的协议来代替。

Jersey是JAX-RS的参考实现, 它包含三个主要部分。. 核心服务器C Core Server): 通过提供 JSR311中标准化的注释和API标准化,你可以用直观的方式开发RESTfulWeb服务。. 核心客户端C Core Client): Jersey客户端API帮助你与REST服务 轻松通信。. 集成C Integration): Jersey还提供可以轻松集成 Spring、Guice、A pache A bdera的库。• XStream是 用来将对象 序列化成XML CJSON)或 反序列化为对象的一 个Java类库。XStream在运行时使用Java反射机制对要进行序列化的对象树的结构进行探索, 并不需要对对象做出修改。XStream可以序列化内部 字段, 包括private和final字段,并且支持非公开类以及内部类。 默认情况下,XStream 不需要配置映射关系, 对象和字段将映射为同名XML元素。 但是当对象和字段名与XML中的元素名不同时,XStream支持指定别名。XStream支持以方法调用的方式, 或是Java标注的方式 指定别名。XStream 在进行数据类型转换时,使用系统默认的类型转换器。 同时, 也支持用户自定义的类型转换器。JAX求S 是将在Java EE 6中引入的一种新技术。JAX-RS即Java API forRESTful Web Services, 是一个Java编程语言的应用程序接口,支持按照表述性状态转移(REST)架构凤格创建Web服务。JAX—RS使用了Java SE 5引入的Java标注未简化Web服务的客户端和服务瑞的开发和部署。包括:• @Path, 标注资原类或者方法的相对路径 。• @GET、 @PUT、 @POST、 @DELETE, 标注方法是HTTP请求的类型。• @Produces, 标注返回的MIME媒体类型。• @Consun1es , 标注可接受请求的MIME媒体类型。• @PathParam、 @QueryPararn 、 @HeaderParam 、 @CookieParam 、 @MatrixParam 、@FormParam, 标注方法的参数未自HTTP请求的不同位置,例如,@PathParam来自URL 的路径, @QueryParam来自URL的查询参数, @HeaderParam未自HTTP请求的头信息, @CookieParam未自HTTP请求的Cookie。

跨平台支持

    除了 Eureka 的Java 客户端之外, 还有很多其他 语言平台对其的支待, 比如eureka-js-client、python-eureka等


DiscoveryClient 类

      这个类用于帮助与Eureka Server互相协作。Eureka Client负责下面的任务:-向Eureka Server注册服务实例-向Eureka Server服务租约- 当服务关闭期间, 向Eureka Server取消租约-查询Eureka Server中的服务实例列表Eureka Client还需要配置一个Eureka Server的 URL列表。

      注册操作也是通过REST请求的方式进行的。同时, 我们能看到发起注册请求的时候, 传入了 一 个 com.nettfix.appinfo.Instanceinfo 对象, 该对象就是注册时客户端给服务端的服务的元数据。


为了定期更新客户端的服务清单, 以保证客户端能够访问确实健康的服务实例, “服务获取 ” 的请求不会只限于服务启动, 而是一个定时执行的任务,它默认为 30秒。“服务续约” 的实现较为简单, 直接以REST请求的方式进行续约;而“服务获取” 则复杂一些, 会根据是否是第一次获取发起不同的 REST 请求和相应的处理。


二、Ribbon客户端负载均衡

     Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 NetflixRibbon 实现。 通过 Spring Cloud 的封装, 可以让我们轻松地将面向服务的 REST 模板请求自动转换成客户端负载均衡的服务调用。 Spring Cloud Rbbon 虽然只是一个工具类框架,它不像服务注册中心、 配置中心、 API 网关那样需要独立部署, 但是它几乎存在于每一个Spring Cloud 构建的微服务和基础设施中。 因为微服务间的调用,API 网关的请求转发等内容实际上都是通过Ribbon 来实现的,包括后续我们将要介绍的 Feign, 它也是基于 Ribbon实现的工具。 所以, 对 Spring Cloud Ribbon 的理解和使用, 对于我们使用 Spring Cloud 来构建微服务非常重要。


负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。 因为负载均衡是对系统的高可用、 网络压力的缓解和处理能力扩容的重要手段之一。 我们通常所说的负载均衡都指的是服务端负载均衡, 其中分为硬件负载均衡和软件负载均衡。 硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如 F5 等;而软件负载均衡则是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作, 比如Nginx 等。 不论采用硬件负载均衡还是软件负载均衡,只要是服务端负载均衡都能以类似下图的架构方式构建起来,

硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候, 该设备按某种算法(比如线性轮询、 按权重负载、 按流量负载等)从维护的可用服务端清单中取出一台服务端的地址, 然后进行转发。而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到的服务清单所存储的位置。 在客户端负载均衡中, 所有客户端节点都维护着自己要访问的服务端清单, 而这些服务端的清单来自于服务注册中心,比如上一章我们介绍的Eureka服务端。同服务端负载均衡的架构类似, 在客户端负载均衡中也需要心跳去维护服务端清单的健康性, 只是这个步骤需要与服务注册中心配合完成。 在Spring Cloud实现的服务治理框架中, 默认会创建针对各个服务治理框架的Ribbon自动化整合配置,


通过Spring Cloud Ribon的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:

1、服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心;2、 服务消费者直接通过调用被@LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务的接口调用。这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。


    从@LoadBalanced注解 源码的注释中可以知道, 该 注解用来给RestTemplate做标记, 以使用负载均衡的客户端(LoadBalancerClient)来配置它。(Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient);

    通过源码以及之前的自动化配置类, 我们可以看到在拦截器中注入了 LoadBalancerClient的实现。 当一个被@LoadBalanced 注解修饰的 RestTemplate 对象向外发起 HTTP 请求时, 会被 LoadBalancerinterceptor 类的 intercept 函数所拦截。 由于我们在使用 RestTemplate 时采用了服务名作为 host, 所以直接从 HttpRequest 的URI对象中通过 getHost ()就可以拿到服务名,然后调用 execute 函数去根据服务名来选择实例并发起实际的请求。

Spring Cloud Ribon中实现客户端负载均衡的基本脉络,了解了它是如何通过 LoadBalancerinterceptor拦截器对 RestTemplate的请求进行拦截, 并利用Spring Cloud的负载均衡器 LoadBalancerClient将以逻辑服务名为host的 URI转换成具 体的服务 实 例地址的过程。 同 时 通 过分 析LoadBalancerClient 的Ribbon实现RibbonLoadBalancerClient, 可以知道在使用Ribbon实现负载均衡器的时候,实际使用的还是Ribbon中定义的ILoadBalancer接口的实现,自动化配置会采用ZoneAwareLoadBalancer的实例来实现客户端负载均衡。

    以定时任务的方式进行服务列表的更新。

Ribbon它可以在通过客户端中配置的 ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。

当Ribbon与Eureka联合使用时,Rbbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写, 扩展成从Eureka注册中心中获取服务端列表。 同时它也会用 NIWSDiscoveryPing来取代工贮ng, 它将职责委托给Eureka 来确定服务端是否已经启动 。


为了实验Ribbon的客户端负载均衡功能, 我们通过 java-jar命令行的方式来启动两个不同端口的hello-service, 具体如下:

java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8081

java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8082


三、服务容错保护: Spring CloudHystrix

      在微服务架构中, 我们将系统拆分成了很多服务单元, 各单元的应用间通过服务注册与订阅的方式互相依赖。 由于每个单元都在不同的进程中运行, 依赖通过远程调用的方式执行, 这样就有可能因为网络原因或是依赖服务自身间题出现调用故障或延迟, 而这些问题会直接导致调用方的对外服务也出现延迟, 若此时调用方的请求不断增加, 最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。

      在微服务架构中, 存在着那么多的服务单元, 若一个单元出现故障, 就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构更加不稳定。为了解决这样的问题, 产生了断路器等一系列的服务保护机制。断路器模式源于 Martin Fowler 的 Circuit Breaker 一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载, 当线路中有电器发生短路时, "WT路器” 能够及时切断故障电路, 防止发生过载、 发热甚至起火等严重后果。在分布式架构中, 断路器模式的作用也是类似的, 当某个服务单元发生故障(类似用电器发生短路) 之后, 通过断路器的故障监控(类似熔断保险丝), 向调用方返回 一个错误响应, 而不是长时间的等待。 这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。针对上述问题, Spring Cloud Hystrix实现了断路器、 线程隔离等一系列服务保护功能。它也是基于Netflix的开源框架Hystrix实现的, 该框架的目标在于通过控制那些访问远程系统、 服务和第三方库的节点, 从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、 服务熔断、 线程和信号隔离、 请求缓存、 请求合并以及服务监控等强大功能。

    注意:这里还可以使用 Spring Cloud 应用中的@SpringCloudApplication 注解来修饰应用主类, 该注解的具体定义如下所示。 可以看到, 该注解中包含了上述我们所引用的三个注解, 这也意味着—个 Spring Cloud 标准应用应包含服务发现以及断路器。@Target ({ Elemen七Type.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreakerpublic釭nterface SpringCloudApplication {


   Hystrix 默认超时时间为 2000 毫秒

4、断路器是否打开

在命令结果没有缓存命中的时候, Hystrix在执行命令前需要检查断路器是否为打开状态:

如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(对应下面第8步)。

• 如果断路器是关闭的, 那么Hystrix跳到第5步,检查是否有可用资源来 执行命令。

5、线程池I请求队列I信号量是否占满

如果与命令相关的线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么Hystrix也不会执行命令,而是转接到fallback处理逻辑(对应下面第8步)。需要注意的是,这里Hystrix所判断的线程池并非容器的线程池,而是每个依赖服务的专有线程池。 Hystrix为了保证不会因为某个依赖服务的间题影响到其他依赖服务而采用了“舱壁模式" (Bulkhead Pattern)来 隔离每个依赖的服务。

6.HystrixObservableCommand.construct()或HystrixCommand.run()Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。

• HystrixCommand.run(): 返回一个单一 的结果,或者抛出异常。

• HystrixObservableCommand.construct(): 返回一个Observable对象来发射多个结果,或通过onError发送错误通知。如果run()或construe七()方法的执行时间超过了命令设置的超时阙值, 当前处理线程将会抛出一个Timeou七Exception (如果该命令不在其自身的线程中执行,则会通过单独的计时线程来 抛出)。在这种情况下,Hystrix会转接到fallback处理逻辑(第8步)。同时, 如果当前命令没有被取消或中断, 那么它最终会忽略run()或者construct ()方法的返回。如果命令没有抛出异常并返回了结果,那么Hystrix在记录一些日志并采集监控报告之后将该结果返回。在使用run()的情况下,Hystrix会返回一个Observable, 它发射单个结果并产生onCompleted的结束通知; 而在使用construct ()的情况下,Hystrix会直接返回该方法产生的Observable对象。

7. 计算断路器的健康度Hystrix会将 “ 成功 ”、“ 失败”、“ 拒绝”、“ 超时 ” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路 ”,直到恢复期结束。 若在恢复期结束后, 根据统计数据判断如果还是未达到健康指标,就再次 “ 熔断/短路 ”。

8. fallback处理当命令执行失败的时候, Hystrix会进入fallback尝试回退处理, 我们通常也称该操作为“ 服务降级”。而能够引起服务降级处理的清况有下面几种:• 第4步, 当前命令处于 “熔断I短路 ” 状态, 断路器是打开的时候。• 第5步, 当前命令的线程池、 请求队列或 者信号量被占满的时候。• 第6步,HystrixObservableCommand.construct()或HystrixCommand.run()抛出异常的时候。在服务降级逻辑中, 我们需要实现一个通用的响应结果,并且该结果的处理逻辑应当是从缓存或是根据一些静态逻辑来获取,而不是依赖网络请求获取。如果一定要在降级逻辑中包含网络请求,那么该请求也必须被包装在HystrixCommand或是HystxObservableCommand中, 从而形成级联的降级策略, 而最终的降级逻辑一定不是一个依赖网络请求的处理, 而是一个能够稳定地返回结果的处理逻辑。

如果我们没有为命令实现降级逻辑或 者降级处理逻辑中抛出了异常, Hystrix 依然会返回一个Observable对象, 但是它不会发射任何结果数据, 而是通过onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。实现一个有可能失败的降级逻辑是一种非常糟糕的做法, 我们应该在实现降级策略时尽可能避免失败的情况。

断路器原理

断路器在 HystrixCommand 和 HystrixObservableCommand 执行过程中起到了举足轻重的作用,它是 Hystrix 的核心部件。

可以看到它的接口定义并不复杂, 主要定义了三个断路器的抽象方法。• allowRequest (): 每个 Hystrix 命令的请求都通过它判断是否被执行。• isOpen(): 返回当前断路器是否打开。• markSuccess(): 用来闭合断路器。

isOpen (): 判断断路器的打开/关闭状态。 详细逻辑如下所示。. 如果断路器打开标识为true, 则直接返回true, 表示断路器处千打开状态。否则,就从度量指标对象 metrics 中获取 HealthCounts 统计对象做进一步判断(该对象记录了 一个滚动时间窗内的请求信息快照,默认时间窗为10秒)。o 如果它的请求总数(QPS)在预设的阙值范围内就返回 false, 表示断路器处于未打开状态。该阙值的配置参数为circuitBreakerRequestVolumeThreshold,默认值为20。口如果错误百分比在阑值范围内就返回 false, 表示断路器处于未打开状态。该阙值的配置参数为 circuitBreakerErrorThresholdPercentage, 默认值为50。口如果上面的两个条件都不满足,则将断路器设置为打开状态 (熔断/短路)。 同时,如果是从关闭状态切换到打开状态的话,就将当前时间记录到上面提到的circuitOpenedOrLastTestedTirne 对象中。

markSuccess(): 该函数用来在 “ 半开路” 状态时使用。若Hystrix 命令调用成功,通过调用它将打开的断路器关闭, 并重置度量指标对象。

allowRequest(): 判断请求是否被允许,这个实现非常简单。 先根据配置 对象propertes中的断路器判断强制打开或关闭属性是否被设置。 如果强制打开,就直接返回false, 拒绝请求。 如果强制关闭,它会允许所有请求,但是同时也会调用isOpen ()来执行断路器的计算逻辑, 用来模拟断路器打开/关闭的行为。 在默认情况下,断路器并不会进入这两个强制打开或关闭的分支中去,而是通过!isOpen ()I I allowSingleTest ()来判断是否允许请求访问。

从allowSingleTest()的实现中我们可以看到,这里使用了在isOpen()函数中当断路器从闭合到打开时候所记录的时间戳。 当断路器在打开状态的时候,这里会判断断开时的时间戳+配置中的circuitBreakerSleep阳ndowinM旦liseconds时间是否小于当前时间, 是的话,就将当前时间更新到记录断路器打开的时间对象circuitOpenedOrLas七Tested霆me 中,并且允许此次请求。 简单地说, 通过circuitBreakerSleepWindowinM口巨seconds 属性设置了 一个断路器打开之后的休眠时间(默认为5秒),在该休眠时间到达之后,将再次允许请求尝试访问,

此时断路器处于 “ 半开” 状态,若此时请求继续失败, 断路器又进入打开状态, 并继续等待下一个休眠窗口过去之后再次尝试;若请求成功, 则将断路器重新置于关闭状态。所以通过 allowSingleTest()与isOpen ()方法的配合,实现了断路器打开和关闭状态的切换。

markSuccess(): 该函数用来在 “ 半开路” 状态时使用。若Hystrix 命令调用成功,通过调用它将打开的断路器关闭, 并重置度量指标对象。


依赖隔离

    “ 舱壁模式” 对于熟悉 Docker 的读者一定不陌生, Docker 通过 “ 舱壁模式” 实现进程的隔离, 使得容器与容器之间不会互相影响。 而 Hystrix 则使用该模式实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池, 这样就算某个依赖服务出现延迟过高的情况, 也只是对该依赖服务的调用产生影响, 而不会拖慢其他的依赖服务。通过实现对依赖服务的线程池隔离, 可以带来如下优势:• 应用自身得到完全保护, 不会受不可控的依赖服务影响。 即便给依赖服务分配的线程池被填满, 也不会影响应用自身的其余部分。• 可以有效降低接入新服务的风险。 如果新服务接入后运行不稳定或存在问题, 完全不会影响应用其他的请求。

• 当依赖的服务从失效恢复正常后, 它的线程池会被清理并且能够马上恢复健康的服务, 相比之下, 容器级别的清理恢复速度要慢得多。• 当依赖的服务出现配置错误的时候, 线程池会快速反映出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。 同时, 我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍) 来处理它。• 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候, 线程池的监控指标信息会反映出这样的变化。 同时, 我们也可以通过实时动态刷新自身应用对依赖服务的阙值进行调整以适应依赖方的改变。• 除了上面通过线程池隔离服务发挥的优点之外, 每个专有线程池都提供了内置的并发实现, 可以利用它为同步的依赖服务构建异步访问。总之, 通过对依赖服务实现线程池隔离, 可让我们的应用更加健壮, 不会因为个别依赖服务出现问题而引起非相关服务的异常。 同时, 也使得我们的应用变得更加灵活, 可以在不停止服务的情况下, 配合动态配置刷新实现性能配置上的调整。虽然线程池隔离的方案带来如此多的好处, 但是很多使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。 对于这一点, 使用者不用过于担心, 因为这些顾虑也是大部分工程师们会考虑到的,Net和x在设计Hystrix的时候,认为线程池上的开销相对于隔离所带来的好处是无法比拟的。 同时, Netflix也针对线程池的开销做了相关的测试, 以用结果打消Hystrix实现对性能影响的顾虑。


定义服务降级

    fallback 是Hystrix命令执行失败时使用的后备方法, 用来实现服务的降级处理逻辑。在HystrixCommand中可以通过重载 getFallback ()方法来实现服务降级逻辑, Hystrix会在 run() 执行过程中出现错误、 超时、 线程池拒绝、 断路器熔断等情况时, 执行getFallback ()方法内的逻辑, 比如我们可以用如下方式实现服务降级逻辑:

在实际使用时,我们需要为大多数执行过程中可能会失败的Hystrix命令实现服务降级逻辑, 但是也有一些情况可以不去实现降级逻辑, 如下所示。

• 执行写操作的命令:当Hystrix命令是用来执行写操作而不是返回一些信息的时候,通常情况下这类操作的返回类型是 void 或是为空的 Observable, 实现服务降级的意义不是很大。 当写入操作失败的时候, 我们通常只需要通知调用者即可。

• 执行批处理或离线计算的命令:当Hystrix命令是用来执行批处理程序生成一份报告或是进行任何类型的离线计算时, 那么通常这些操作只需要将错误传播给调用者,然后让调用者稍后重试而不是发送给调用者一个静默的降级处理响应。

异常获取

当 Hystrix 命令因为异常(除了 HystrixBadRequestException 的异常)进入服务降级逻辑之后, 往往需要对不同异常做针对性的处理, 那么我们如何来获取当前抛出的异常呢?在以传统继承方式实现的 Hystrix 命令中, 我们可以用 getFallback ()方法通过Throwable getExecutionException() 方法来获取具体的异常, 通过判断来进入不同的处理逻辑。除了传统的实现方式之外,注解配置方式也同样可以实现异常的获取。 它的实现也非常简单, 只需要在 fallback 实现方法的参数中增加 Throwable e 对象的定义, 这样在方法内部就可以获取触发服务降级的具体异常内容了

@HystrixCommand(fallbackMethod = "fallbackl")User getUserByid(String id) {throw new RuntimeException("getUserByid command failed");User fallbackl{String id, Throwable e) {assert "ge七UserByid command failed". equals {e. getMessage {));}


Spring Cloud Feign

    它基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式。

    我们在使用 Spring Cloud Ribbon 时, 通常都会利用它对 RestTemplate 的请求拦截来实现对依赖服务的接口调用, 而 RestTemplate 已经实现了对 HTTP 请求的封装处理, 形成了一套模板化的调用方法。在之前的例子中,我们只是简单介绍了 RestTemplate 调用的实现,但是在实际开发中,由于对服务依赖的调用可能不止于一处,往往一个接口会被多处调用,所以我们通常都会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用。 这个时候我们会发现, 由于 RestTemplate 的封装, 几乎每一个调用都是简单的模板化内容。综合上述这些情况, Spring Cloud Feign 在此基础上做了进一步封装, 由它来帮助我们定义和实现依赖服务接口的定义。在 Spring Cloud Feign 的实现下, 我们只需创建一个接口并用注解的方式来配置它, 即可完成对服务提供方的接口绑定, 简化了在使用 Spring Cloud Ribbon 时自行封装服务调用客户端的开发量。 Spring Cloud Feign 具备可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。 同时, 为了适应 Spring 的广大用户,它在 Netflix Feign的基础上扩展了对 Spring MVC 的注解支待。

对于 Feign 自身的一些主要组件, 比如编码器和解码器等, 它也以可插拔的方式提供, 在有需求的时候我们可以方便地扩展和替换它们。

Spring Cloud Config

为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持, 它分为服务端与客户端两个部分。 其中服务端也称为分布式配置中心, 它是一个独立的微服务应用, 用来连接配置仓库并为客户端提供获取配置信息、 加密/解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施, 它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息

由于 Spring Cloud Config 实现的配置中心默认采用 Git 来存储配置信息, 所以使用 Spring Cloud Config 构建的配置服务器, 天然就支持对微服务应用配置信息的版本管理, 并且可以通过 Git 客户端工具来方便地管理和访问配置内容。

配置服务器在从 Git 中获取配置信息后, 会存储一份在 config-server 的文件系统中, 实质上config-server 是通过 git clone 命令将配置内容复制了 一份在本地存储, 然后读取这些内容并返回给微服务应用进行加载。config-server 通过Git 在本地仓库暂存,可以有效防止当 Git 仓库出现故障而引起无法加载配置信息的情况

本地Git仓库: 在Config Server的文件系统中, 每次客户端请求获取配置信息时,Config Server从Git仓库中获取最新配置到本地,然后在本地Git仓库中读取并返回 。当远程仓库无法获取时, 直接将本地内容返回。

客户端应用从配置管理中获取配置信息遵从下面的执行流程:

1. 应用启动时,根据bootstrap.propertes中配置的应用名{application}、环境名{profile}、 分支名{label}, 向ConfigServer请求获取配置信息。

2. Config Server根据自己维护的Git仓库信息和客户端传递过来的配置定位信息去查找配置信息。

3 通过git clone命令将找到的配置信息下载到ConfigServer的文件系统中。

4. Config Server创建Spring的ApplicationContext实例, 并从Git本地仓库中加载配置文件, 最后将这些配置内容读取出来返回给客户端应用。

5. 客户端应用在获得外部配置文件后加载到客户端的ApplicationContext实例,该配置内容的优先级高于客户端Jar包内部的配置内容, 所以在Jar包中重复的内容将不再被加载。

Config Server巧妙地通过git clone将配置信息存于本地, 起到了缓存的作用, 即使当Git服务端无法访问的时候, 依然可以取ConfigServer中的缓存内容进行使用。

Git配置仓库

在SpringCloud Config的服务端, 对于配置仓库的默认实现采用了Git。Git非常适用于存储配置内容,它可以非常方便地使用各种第三方工具来对其内容进行管理更新和版本化,同时Git仓库的Hook功能还可以帮助我们实时地监控配置内容的修改。 其中,Git自身的版本控制功能正是其他一些配置中心所欠缺的, 通过Git 进行存储意味着, 一个应用的不同部署实例可以从SpringCloud Config的服务端获取不同的版本配置, 从而支持一些特殊的应用场景

访问权限

Config Server在访问Git仓库的时候, 若采用HTTP的方式进行认证, 那么我们需要增加username和password属性来配置账户

本地文件系统

Spring Cloud Config也提供了一种不使用Git仓库或SYN仓库的存储方式, 而是使用本地文件系统的存储方式来保存配置信息。 实现方式也非常简单, 我们只需要设置属性spring.profiles.ac巨ve= na巨ve, Config Server会默认从应用的 src/main/resource 目录下搜索配置文件。 如果需要指定搜索配置文件的路径, 我们可以通过spring.cloud.config.server.native.searchLoca已ons 属性来指定具体的配置文件位置。

健康监测/属性覆盖/

安全保护

由于配置中心存储的内容比较敏感,做一定的安全处理是必需的。 为配置中心实现安全保护的方式有很多,比如物理网络限制、 0Auth2 授权等。 不过, 由于我们的微服务应用和配置中心都构建于 Spring Boot 基础上,所以与 Spring Security 结合使用会更加方便。我们只需要在配置中心的 pom.xml 中加入 spring-boot-starter-security 依赖,不需要做任何其他改动就能实现对配置中心访问的安全保护。

默认情况下,我们可以获得一个名为 user 的用户,并且在配置中心启动的时候,在日志中打印出该用户的随机密码,

由于我们已经为 config-server 设置了安全保护,如果这时候连接到配置中心的客户端中没有设置对应的安全信息,在获取配置信息时会返回401错误。 所以,需要通过配置的方式在客户端中加入安全信息来通过校验


加密解密

如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题,Spring Cloud Config 提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。在 Spring Cloud Config 中通过在属性值前使用 {cipher} 前缀来标注该内容是一个加密值, 当微服务客户端加载配置时,配置中心会自动为带有 {cipher} 前缀的值进行解密。


分布式服务跟踪: Spring Cloud Sleuth

在复杂的微服务架构系统中, 几乎每一个前端请求都会形成一条复杂的分布式服务调用链路, 在每条链路中任何一个依赖服务出现延迟过高或错误的时候都有可能引起请求最后的失败。这时候,对于每个请求, 全链路调用的跟踪就变得越来越重要, 通过实现对请求调用的跟踪可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等




注意事项:

1、必须要有 Dto的默认构造函数。 不然, Spring Cloud Feign 根据 JSON 字符串转换 User 对象时会抛出异常。

2、在定义各参数绑定时,@RequestParam、@RequestHeader 等可以指定参数名称的注解, 它们的 value 千万不能少。 在SpringMVC 程序中, 这些注解会根据参数名来作为默认值,但是在Feign 中绑定参数必须通过 value 属性来指明具体的参数名,不然会抛出口legalStateException 异常, value 属性不能为空。

3、使用 Spring Cloud Feign 继承特性的优点很明显, 可以将接口的定义从 Controller 中剥离, 同时配合Maven私有仓库就可以轻易地实现接口定义的共享, 实现在构建期的接口绑定, 从而有效减少服务客户端的绑定配置。 这么做虽然可以很方便地实现接口定义和依赖的共享, 不用再复制粘贴接口进行绑定, 但是这样的做法使用不当的话会带来副作用。 由于接口在构建期间就建立起了依赖,那么接口变动就会对项目构建造成影响, 可能服务提供方修改了一个接口定义,那么会直接导致客户端工程的构建失败。 所以, 如果开发团队通过此方法来实现接口共享的话, 建议在开发评审期间严格遵守面向对象的开闭原则, 尽可能地做好前后版本的兼容,防止牵一发而动全身的后果,增加团队不必要的维护工作量。

4、Ribbon的超时 与Hystrix的超时是两个概念。 为了让上述实现有效,我们需要 让Hystrix的超时时间大于Ribbon的超时时间, 否则Hystrix命令超时后, 该命令直接熔断, 重试机制就 没有任何意义了。

5、Feign默认对Hystrix未开启的,需要设置:feign:
  hystrix:
    enabled: true 

来开启。

6、请求压缩Spring Cloud Feign支持对请求与响应进行 GZIP压缩,以减少通信过程中的性能损耗。我们只需通过下面两个参数设置, 就能开启请求与响应的压缩功能:feign.compression.request.enabled=true feign.compression.response.enabled=true


spring cloud zuul:

优点:

• 它作为系统的统一入口, 屏蔽了系统内部各个微服务的细节。

• 它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。

• 它可以实现接口权限校验与微服务业务逻辑的解耦。

• 通过服务网关中的过滤器, 在各生命周期中去校验请求的内容, 将原本在对外服务层做的校验前移, 保证了微服务的无状态性, 同时降低了微服务的测试难度, 让服务本身更集中关注业务逻辑的处理。

API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、 负载均衡、 校验过滤等功能之外, 还需要更多能力, 比如与服务治理框架的结合、 请求转发时的熔断机制、 服务的聚合等一系列高级功能。

首先, 对千路由规则与服务实例的维护间题。 SpringCloud Zuul通过与SpringCloudEureka进行整合, 将自身注册为Eureka服务治理下的应用, 同时从Eureka中获得了所有其他微服务的实例信息。 这样的设计非常巧妙地将服务治理体系中维护的实例信息利用起来, 使得将维护服务实例的工作交给了服务治理框架自动完成, 不再需要人工介入。 而对千路由规则的维护, Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射,大部分情况下, 这样的默认设置已经可以实现我们大部分的路由需求, 除了一些特殊情况(比 如兼容一些老的URL)还需要做一些特别的配置 。 但是相比于之前架构下的运维工作量, 通过引入SpringCloud Zuul实现API网关后, 已经能够大大减少了。

其次, 对千类似签名校验、 登录校验在微服务架构中的冗余问题。 理论上来说, 这些校验逻辑在本质上与微服务应用自身的业务并没有多大的关系, 所以 它们完全可以独立成一个单独的服务存在, 只是它们被剥离和独立出来之后, 并不是给各个微服务调用, 而是在API网关服务上进行统一调用来对微服务接口做前置过滤, 以实现对微服务接口的拦截和校验 。 SpringCloud Zuul提供了一套过滤器机制, 它可以 很好地支持这样的任务。 开发者可以通过使用Zuul来创建各种校验过滤器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示。 通过这样的改造,各个业务层的微服务应用就不再需要非业务性质的校验逻辑了, 这使得我们的微服务应用可以更专注千业务逻辑的开发, 同时微服务的自动化测试也变得更容易实现。

请求路由

面向服务的路由很显然, 传统 路由的配置方式对于我们来说并不友好, 它同样需要运维人员花费大量的时间来维护各个路由 path与url的关系 。 为了解决这个问题, SpringCloud Zuul实现了与Spring Cloud Eureka的无缝整合, 我们可以让路由的path不是映射具体的url, 而是让它映射到某个具体的服务 , 而具体的url则交给Eureka的服务发现机制去自动维护, 我们称这类路由为面向服务的路由。

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.service!d=hello-service 

通过面向服务的路由配置 方式, 我们不需要再为各个路由维护微服务应用的具体 实例的位置, 而是 通过简单的path与serviceld的映射组合,使得维护工作变得非常简单。 这完全归功于Spring Cloud Eureka的服务发现机制,它使得API网关服务可以自动化完成服务实例清单的维护,完美地解决了对路由映射 实例的维护问题。

请求过滤

比较好的做法是将这些校验逻辑剥离出去, 构建出一个独立的鉴权服务。更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入, 外部客户端访问我们的系统已经有了统一入口, 既然这些校验与具体业务无关, 那何不在请求到达的时候就完成校验和过滤, 而不是转发后再过滤而导致更长的请求延迟。 同时, 通过在网关中完成校验和过滤, 微服务应用端就可以去除各种复杂的过滤器和拦截器了, 这使得微服务应用接口的开发和测试复杂度也得到了相应降低。

Zuul 允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值