一、微服务简介
1.1 Spring Cloud简介1.2 微服务基础:Spring Boot 二、服务治理Eureka
2.1 Eureka使用2.2 Eureka理论
2.2.1 服务提供者2.2.2 服务消费者 2.3 Eureka配置 三、负载均衡Ribbon
3.1 RestTemplate3.2 Ribbon的配置与使用3.3 重试机制 四、容错保护Hystrix
4.1 依赖隔离4.2 服务降级4.3 Hystrix属性 五、服务调用Feign六、网关服务Zuul
6.1 路由6.2 过滤器
6.2.1 请求的生命周期6.2.2 异常处理6.2.3 禁用过滤器 七、CAP和base
7.1 CAP7.2 base
一、微服务简介 微服务是一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作。被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。
在微服务架构中,通常会使用以下两种服务调用方式:
- 使用HTTP的RESTfulAPI或轻量级的消息发送协议,实现信息传递与服务调用的触发。通过在轻量级消息总线上传递消息,类似RabbitMQ等一些提供可靠异步交换的中间件。
SpringCloud是一个基于SpringBoot实现的微服务架构开发工具。它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
SpringCloud包含了多个子项目:
- SpringCloudConfig:配置管理工具,支持使用Git存储配置内容,可以使用它实现应用配置的外部化存储,并支持客户端配置信息刷新、加密/解密配置内容等。SpringCloud Netflix:核心组件,对多个NetflixOSS开源套件进行整合。
Eureka:服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
Ribbon:客户端负载均衡的服务调用组件。
Feign:基于伈bbon和Hystrix的声明式服务调用组件。
Zuul:网关组件,提供智能路由、访问过滤等功能。
Archaius:外部化配置组件。
- SpringCloudBus:事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来动态刷新配置等。SpringCloudCluster:针对ZooKeeper、Redis、Hazelcast、Consul的选举算法和通用状态模式的实现。SpringCloud Cloudfoundry:与PivotalCloudfoundry的整合支持。SpringCloud Consul:服务发现与配置管理工具。SpringCloud Stream:通过Redis、Rabbit或者Kafka实现的消费微服务,可以通过简单的声明式模型来发送和接收消息。SpringCloud AWS:用于简化整合AmazonWebService的组件。SpringCloud Security:安全工具包,提供在Zuul代理中对0Auth2客户端请求的中继器SpringCloud Sleuth:SpringCloud应用的分布式跟踪实现,可以完美整合Zip虹n。SpringCloud ZooKeeper:基于ZooKeeper的服务发现与配置管理组件。SpringCloud Starters:SpringCloud的基础组件,它是基于SpringBoot风格项目的基础依赖模块。SpringCloud CLI:用于在Groovy中快速创建SpringCloud应用的SpringBootCLI。
Spring Boot是Spring Cloud的基础。
Spring Boot的基础结构有三大块:
1、src/main/java
Java代码目录。2、src/main/resources
配置目录,该目录用来存放应用的一些配置信息,比如应用名、服务端口、数据库连接信息等。3、src/test
单元测试目录。
SpringBoot 默认的配置文件是src/main/resources/application.properties ,当然也可以使用yml来实现配置文件的功能。
SpringBoot默认将Web应用打包成jar的方式,因为默认的Web模块依赖会包含嵌入式的Tomcat,这样使得应用jar自身就具备了提供Web服务的能力。
SpringBoot的Starter Poms采用spring-boot-starter-*的命名方式,*代表一个特别的应用功能模块,如web、test。
在定义属性时,常见的有properties和YAML两种方式。简单来说,YAML无法通过@PropertySource注解来加载配置。但是,YAML将属性加载到内存中保存的时候是有序的。
服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。
1、服务注册
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
同时,服务注册中心还需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。2、服务发现
由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口的时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。
Spring Cloud Eureka,使用Netflix Eureka来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用Java编写,所以Eurcka主要适用于通过Java实现的分布式系统,或是与JVM兼容语言构建的系统。但是,由于Eureka服务端的服务治理机制提供了完备的RESTful API,所以它也支持将非Java语言构建的微服务应用纳入Eureka的服务治理体系中来。
Eureka分服务端和客户端:
Eureka服务端
也称为服务注册中心。它同其他服务注册中心一样,支持高可用配置。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka 就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把它们的状态再次同步回来。
注册中心里有一个注册表,保存了各个服务所在的机器和端口号。Eureka客户端
主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。
Eureka客户端负责将这个服务的信息注册到Eureka服务端中。
2.1 Eureka使用
Eureka服务端所用的注解是@EnableEurekaServer,该注解的作用是启动一个服务注册中心提供给其他应用进行对话。该注解一般加在启动类上,示例:
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args){
//...
}
}
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己(在Eureka的服务治理设计中,所有的节点即使服务提供方,也是服务消费方)。如果要禁用它的客户端注册行为,需要设置属性eureka.client.register-with-eureka值为false。配置实例的主机名,示例:
eureka.instance.hostname = localhost
Eureka客户端所用的注解是@EnableDiscoveryClient,该注解让应用获得服务发现的能力,也一般加在启动类上,示例:
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args){
//...
}
}
Eureka客户端需要配置服务名和服务注册中心的地址,示例:
spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http:localhost:1111/eureka2.2 Eureka理论
构建Eureka服务治理有三个核心角色:服务注册中心、服务提供者和服务消费者。
- 服务注册中心:Eureka提供的服务端,提供服务注册和发现的功能;服务提供者:提供服务的应用,遵循Eureka通信机制的应用。它将自己注册到Eureka Server中,以供其他应用发现;服务消费者:消费者应用从服务注册中心获取服务列表,从而让消费者知道可以从哪个服务提供者调用其所需的服务。
“服务提供者”在启动的时候会通过发送 REST请求的方式将自己注册到Eureka Server行,同时带上了自身服务的一些元数据信息。
在注册完服务之后,服务提供者会维护一个心跳,用来通知注册中心“我还活着”,以防被注册中心的“剔除任务”将服务实例从服务列表中排除出去,这个行为称之为服务续约。
关于续约的两个属性(以下为默认值):
# 定义服务续约任务的调用间隔时间 eureka.instance.lease-renewal-interval-in-seconds=30 # 定义服务失效的时间 eureka.instance.lease-expiration-duration-in-seconds=902.2.2 服务消费者
当启动服务消费者的时候,它会发送一个rest请求给服务注册中心,来获取 上面注册的服务清单。为了性能考虑,Eureka server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。若希望修改缓存单的更新时间,可以通过eureka.client.register.fetch-interval-seconds=30参数进行修改,该参数默认值为30,单位为秒。
服务消费者在回去服务清单以后,通过服务名可以获得提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详情信息,所以客户端可以根据自己的需要决定具体使用哪个实例,在ribbon中会采默认采用轮训的方式进行调用,从而实现客户端的负载均衡。
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用到被关闭的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的rest请求给eureka server注册中心,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(down),并把该下线事件传播出去。然后注册中心之间服务同步就会都知道该服务客户端已下线。
有些时候服务实例并不一定是正常下线,有可能是内存溢出、网络故障等问题使得服务不能正常工作,而服务注册中心没有收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除掉,eureka server注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
Eureka客户端的配置主要分为两个方面:
服务注册相关的配置信息,包括服务注册中心的地址、服务获取的间隔时间可用区域等。服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号等。
Eureka客户端的配置大多数时候不需要修改。
org.springframework.cloud.netflix.eureka.EurekaClient-ConfigBean中定义的常用配置参数如下(这些参数均已eureka.client为前缀):
| 参数名 | 说明 | 默认值 |
|---|---|---|
| enabled | 启用Eureka客户端 | true |
| registryFetchIntervalSeconds | 从Eureka服务端获取注册信息的时间间隔,单位为秒 | 30 |
| instanceInfoReplicationIntervalSeconds | 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒 | 30 |
| initialInstanceInfoReplicationIntervalSeconds | 初始化实例信息到Eureka服务端的间隔时间,单位为秒 | 30 |
| eurekaServiceUrlPollIntervalSeconds | 轮询Eureka服务端地址更改的时间间隔,单位为秒 | 300 |
| eurekaServerReadTimeoutSeconds | 读取Eureka Server信息的超时时间,单位为秒 | 8 |
| eurekaServerConnectTimeoutSeconds | 连接Eureka Server超时时间,单位为秒 | 5 |
| eurekaServerTotalConnections | 从Eureka客户端到所有Eureka服务端的连接总数 | 200 |
| eurekaServerTotalConnectionsPerHost | 从Eureka客户端到所有Eureka服务端主机的连接总数 | 50 |
| eurekaConnectionIdleTImeoutSeconds | Eureka服务端连接的关闭空闲时间,单位为秒 | 30 |
| heartbeatExecutorThreadPoolSize | 心跳连接池的初始化线程数 | 2 |
| heartbeatExecutorExponentialBackOffBound | 心跳超时重试延迟时间的最大乘数值 | 10 |
| cacheRefreshExecutorThreadPoolSize | 缓存刷新线程池的初始化线程数 | 2 |
| cacheRefreshExecutorExponentialBackOffBound | 缓存刷新重试延迟时间的最大乘数值 | 10 |
| useDnsForFetchingServiceUrls | 使用DNS来获取Eureka服务端的serviceUrl | false |
| registerWithEureka | 是否要将自身的实例信息注册到Eureka服务端 | true |
| perferSameZoneEureka | 是否偏好使用处于相同Zone的Eureka服务端 | true |
| filterOnlyUpInstances | 获取实例时是否过滤,仅保留UP状态的实例 | true |
| fetchRegistry | 是否从Eureka服务端获取注册信息 | true |
健康检测:默认情况下,Eureka中各个服务实例的健康检测并不是通过spring-boot-actuator模块的/health端点来实现,而是依靠客户端心跳的方式来保持服务实例的存活。
三、负载均衡Ribbon Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,并且Feign也是通过Ribbon来实现的。
通常说的负载均衡指的是服务端负载均衡,分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等。软件负载均衡则是通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,比如Nginx等。
服务端的负载均衡:当客户端发送请求到负载均衡设备的时候,该设备按照某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务器的地址,然后进行转发。
客户端负载均衡:所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,在客户端负载均衡中心也要心跳去维护服务端清单的健康性,只是这个步骤需要和服务注册中心配合完成。
通过Ribbon的封装,在微服务架构中使用客户端负载均衡的步骤:
服务提供者只需要启动多个服务实例并注册到一个注册中心或者多个相关联的服务注册中心;服务消费者直接调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
当所调用的服务存在于多个服务器上时,就需要用到Ribbon了。Ribbon的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。
Ribbon的负载均衡默认使用的最经典的轮询算法。简单举例,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。
Ribbon是和Feign以及Eureka紧密协作的过程:
1、首先Ribbon会从EurekaClient里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号;2、然后Ribbon就可以使用默认的RoundRobin算法,从中选择一台机器;3、Feign就会针对这台机器,构造并发起请求。 3.1 RestTemplate
1、GET请求
在RestTemplate中,对GET请求可以通过以下两个方法调用。
第一种是getForEntity函数。该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装。
第二种是getForObject函数。该方法可以理解为对getForEntity的进一步封装,实现请求直接返回包装好的对象内容,示例:
RestTemplate restTemplate = new RestTemplate(); User result = restTemplate.getForObject(uri,User.class);
当不需要关注请求响应除body外的其他内容时,使用该函数九比较方便,可以少一个从Response中获取body的步骤。
2、POST请求 对Ribbon参数配置通常有两种方式:全局配置以及指定客户端配置。 1、全局配置 2、指定客户端配置 当在Spring Cloud的应用中同时引入Spring Cloud Ribbon和Spring Cloud Rureka依赖时,会触发Eureka中实现的对Ribbon的自动化配置。 由于Spring Cloud Eureka实现的服务治理机制强调了CAP原理中的AP,即可用性与可靠性。Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极端情况下它宁愿接受故障实例也不要丢弃“健康”实例。 在微服务架构中,存在着很多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪。因此,断路器等服务保护机制应运而生。 线程池 总之,通过对依赖服务实现线程池隔离,可让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。同时,也使得应用变得更加灵活,可以在不停止服务的情况,配合动态配置刷新实现性能配置上的调整。 信号量 fallback用来实现服务的降级处理逻辑。通过注解实现服务降级时,只需要使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法,示例: Hystrix属性分为4类,优先级从低到高:全局默认值、全局配置属性、实例默认值、实例配置属性。 execution.isolation.strategy THREAD(默认值):通过线程池隔离的策略。在独立的线程上执行,并且并发限制受到线程池中线程数量的限制。 execution.isolation.thread.timeoutInMilliseconds 在使用Ribbon时,通常都会利用它对RestTemplate的请求拦截来实现对依赖服务的接口调用,而RestTemplate已经实现了对HTTP请求的封装处理。Feign在此基础上做了进一步封装,在Feign的实现下,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用Ribbon时自行封装服务调用客户端的开发量。 Feign使用示例: 首先,如果你对某个接口定义了@FeignClient 注解,Feign 就会针对这个接口创建一个动态代理;接着你要是调用哪个接口,本质就是会调用 Feign 创建的动态代理;Feign的动态代理会根据你在接口上的@RequestMapping 等注解,来动态构造出你要请求的服务的地址;最后针对这个地址,发起请求、解析响应。
六、网关服务Zuul
Zuul的主要作用可以分为两类:首先,对于路由规则与服务实例的维护问题。其次,对于类似签名校验、登录校验等问题。 使用Zuul实现路由功能十分简单,示例: 在该配置中,所有符合/api-a-url/**规则的访问都将被路由转发到http://localhost:8080地址上。 路径匹配规则
在使用路由规则匹配请求路径时,是通过线性遍历的方式,在请求路径获取到第一个匹配规则时就返回并结束匹配过程。所以放存在多个匹配的路由规则时,匹配结果完全取决于路由规则的保存顺序。 路由前缀 cookie与头信息 这种方法并不推荐,虽然实现了cookie的传递,但破坏了默认设置的用意。 Zuul的路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。 自定义了过滤器后,不会直接生效,还需要为其创建Bean,一般在启动类中创建,示例: Zuul的具体作用: 在上面要继承四个方法,对应了过滤器的4个基本特征:过滤类型、执行顺序、执行条件、具体惭怍。 filterType pre:可以在请求被路由之前调用; filterOrder Zuul中4种不同类型的过滤器,他们覆盖了一个外部HTTP请求到达API网关,直到返回请求结果的全部生命周期。 默认启用的pre过滤器 对自定义过滤器中处理异常的两种基本方法:一种是通过在各个阶段的过滤器中增加try-catch块,实现过滤器内部的异常处理;另一种是利用error类型过滤器的生命周期特性,集中处理pre、route、post阶段抛出的异常信息。 不过是核心过滤器还是自定义过滤器,只要在API网关应用中为它们创建了实例,默认情况下,它们都是默认启用状态的。 一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partitiontolerance)这三项中的两项。C一致性即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。A可用性服务一直可用,而且是正常响应时间。P分区容错性即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性 对于多数大型互联网应用的场景,一般保证满足P和A,舍弃C(一致性无法保证,退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流失的严重程度。如原来同步架构的时候如果没有库存,就马上告诉客户库存不足无法下单。但在微服务框架下订单和库存可能是两个微服务对应两个数据库,用户下单时订单服务是立即生成的,很可能过了一会系统通知你订单被取消掉(最终一致性)。就像抢购“小米手机”一样,几十万人在排队,排了很久告诉你没货了,明天再来吧。对于涉及到钱财这样不能有一丝让步的场景,C必须保证。网络发生故障宁可停止服务,这是保证CA,舍弃P。还有一种是保证CP,舍弃A。例如网络故障事只读不写。
7.2 base
base是BasicallyAvailable(基本可用)、Softstate(软状态)Eventuallyconsistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展: 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中 base解决了CAP中理论没有网络延迟,在base中用软状态和最终一致,保证了延迟后的一致性。base和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
在RestTemplate中,对POST请求可以通过以下三个方法调用。
第一种:postForEntity函数。该方法同GET请求中的getForEntity类似, 会在调用后返回ResponseEntity
第二种:postForObject函数。该方法也跟getForObject类似,用来简化postForEntity的处理,通过直接将请求响应中的body内容包装成对象来使用。
第三种:postForLocation函数。该方法实现了以POST请求提交资源,并返回新资源的URI。3、PUT请求
在RestTemplate中,对PUT请求可以通过put方法调用。put函数为boid类型,所以没有返回内容。4、DELETe请求
在RestTemplate中,对DELETE请求可以通过delete方法调用。由于在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息。
3.2 Ribbon的配置与使用
使用ribbon.ribbon.ConnectTimeout=250
使用
在分布式架构中,断路器模式的作用是类似的,但某个服务单元发生故障时,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误相应,而不是长时间等待。这样就不会使得线程因调动故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。
在启动类加上@EnableCircuitBreaker注解代表引入Hystrix。当然,使用SpringCloudApplication也可,该注解包含了@SpringBootApplication、@EnbleDiscoveryClient、@EnableCircuitBreaker等注解。
使用@HystrixCommand可以用来指定回调方法。
Hystrix 是隔离、熔断以及降级的一个框架。简单来说,Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。
Hystrix通过“舱壁模式”实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
通过实现对依赖服务的线程池的隔离,可以带来如下优势:
应用自身得到保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的其余部分。可以有效降低接入新服务的风险。如果新服务介入后运行不稳定或存在问题,完全不会影响应用响应其他的请求。当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下,容器级别的清理恢复速度要慢得多。当依赖的服务出现配置错误时,线程池会快速反映此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。当依赖的服务因实现机制调整等原因造成其性能出现很大变化时,线程池的监控指标会反映出这样的变化。
在Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。所以,只有在依赖服务是足够可靠的情况下,才使用信号量。
使用信号量的场景:
4.2 服务降级
命令执行。如果将隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发。降级逻辑。 当Hystrix尝试降级逻辑时,它会在调用线程池中使用信号量。
@HystrixCommand(fallback = "defaultUser")
public User getUserById(long id){
//...
}
public User defaultUser(){
return new User();
}
4.3 Hystrix属性
用来设置HystrixCommand.run()执行的隔离策略。该属性有两个选项:
SEMAPHORE:通过信号量隔离的策略。它在调用线程上执行,并且它的并发限制受信号量计数的限制。
该属性用来配置HystrixCommand执行的超时时间,单位为毫秒,默认为1000毫秒。execution.isolation.enable
该属性用来配置HystrixCommand.run()的执行是否启用超时时间。默认为true,如果设置为false,那么超时时间的配置将不再起作用。execution.isolation.thread.interruptOnTimeout
该属性用来配置当HystrixCommand.run()执行超时的时候是否要将它中断。默认值为true。execution.isolation.thread.interruptOnCancel
该属性用来配置当HystrixCommand.run()执行被取消时是否要将它中断,默认值为true。execution.isolation.semaphore.maxConcurrentRequests
当HystrixCommand的隔离策略使用信号量时,该属性用来配置信号量的大小(并发请求数)。当最大并发请求数达到该设置值时,后续的请求将会被拒绝,默认值为10。fallback.isolation.semaphore.maxConcurrentRequests
该属性用来设置从调用线程中允许HystrixCommand.getFallback()方法执行的最大并发请求数。当达到最大并发请求数时,后续的请求将会被拒绝并抛出异常(因为已经没有后续的fallback可以被调用)。默认值为10。fallback.enabled
该属性用来设置服务降级策略是否启用,如果设置为false,那么当请求失败或拒绝发生时,将不会调用HystrixCommand.getFallback()来执行服务降级逻辑,默认值为true。circuitBreaker.enabled
该属性用来确定当服务请求命令失败时,是否使用断路器来跟踪器健康指标和熔断请求,默认值为true。circuitBreaker.requestValumeThreshold
用来设置在滚动时间窗中,断路器熔断的最小请求数,默认值为20。在默认情况下,如果滚动时间窗(默认值为10秒)内仅仅收到了19个请求,即使这19个请求都失败了,断路器也不会打开。circuitBreaker.sleepWindowInMilliseconds
给属性用来设置当断路器打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器置为“半开”状态,尝试熔断的请求命令,如果依然失败就将断路器设置为“打开”状态,如果成功就设置为“关闭”状态。默认值为5000。circuitBreaker.errorThresholdPercentage
该属性用来设置断路器打开的错误百分比条件,默认值为50,在滚动时间窗中,在请求数量超过circuitBreaker.requestValumeThreshold阈值的前提下,如果错误请求数的百分比超过50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态。circuitBreaker.forceOpen
该属性默认值为false。如果将该属性设置为true,断路器将强制进入“打开”状态,它会拒绝所有的请求。该属性优先于circuitBreaker.forceClosed属性。circuitBreaker.forceClosed
默认值为false。如果将该属性设置为true,断路器将强制进入“关闭”状态,它会接收所有请求。metrics.rollingStats.timeInMilliseconds
用来设置滚动时间窗的长度,单位为毫秒,默认值为10000。该时间用于断路器判断健康度时需要收集信息的持续时间。断路器在收集指标信息的时候会根据设置的时间长度拆分成多个“桶”来累计各度量值,每个“桶”记录了一段时间内的采集指标。例如,当采用默认值10000毫秒时,断路器默认将其拆分为10个桶,每个桶记录1000毫秒内的指标信息。metrics.rollingStats.numBuckets
该属性用来设置滚动时间窗统计指标信息时划分“桶”的数量。默认值为10。metrics.rollingPercentile.enabled
该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为false,那么所有的概要统计都将返回-1。默认值为true。metrics.rollingPercentile.timeInMilliseconds
用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。默认值为60000。metrics.rollingPercentile.numBuckets
该属性用来设置百分位统计滚动窗口中使用“桶”的数量,默认值为6。metrics.rollingPercentile.bucketSize
用来设置在执行过程中,每个“桶”中保留的最大执行次数。如果在滚动时间窗发生超过该设定值的执行次数,就从最初的位置开始重写,默认值为100。例如,该值为100,滚动窗口为10秒,若在10秒内一个“桶”中发生了500次执行,那么该“桶”中只保留最后的100次执行的统计。metrics.healthSnapshot.intervalInMilliseconds
该属性用来设置采集影响断路器状态的健康快照(请求的成功、错误百分比)的间隔等待时间,默认值为500。requeseCache.enabled
用来配置是否开启请求缓存,默认值为true。requeseLog.enabled
用来设置HystrixCommand的执行和事件是否打印日志到HystrixRequeseLog中,默认值为true。maxRequestsInBatch
用来设置一次请求合并批处理中允许的最大请求数。默认值为Integer.MAX_VALUE。timerDelayInMilliseconds
用来设置批处理过程中每个命令延迟的时间,单位为毫秒,默认值为10。requestCache.enabled
用来设置批处理过程中是否开启请求缓存,默认值为true。coreSize
用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量,默认值是10。maxQueueSize
该参数用来设置线程池的最大队列大小。当设置为-1时,线程池将使用SynchronousQueue实现的队列,否则将使用linkedBlockingQueue实现的队列。默认值为-1。quequeSizeRejectionThreshold
该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求。该参数主要是对linkedBlockingQueue的补充,因为linkedBlockingQueue队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。默认值为5。
五、服务调用Feign
@EnableFeignClients注解用来开启对Feign的支持功能。
@FeignClient注解用来指定服务名来绑定服务,示例: @FeignClient("hello-service")
public interface HelloService{
@RequestMapping("/hello")
String hello();
}
Feign 的一个关键机制就是使用了动态代理。其具体调用过程:
@EnableZuulProxy用于开启API网关服务功能。 zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:8080/
当然,也可以让路由的path不是映射到具体的url,而是让它映射到某个具体的服务,具体的url规则交给Eureka的服务发现机制去自动维护,这种称为面向服务的路由,示例: zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.serviceId=hello-service
通配符 说明 ? 匹配任意单个字符 * 匹配任意数量的字符 ** 匹配任意数量的字符,支持多级目录
由于properties的配置内容无法保证有序,所以当出现这种情况的时候,为了保证路由的优先顺序,需要使用YAML文件来配置,以实现有序的路由规则。
如果相位网关上的路由规则都增加/api前缀,就可以在配置文件中增加配置:zuul.prefix=/api,并且代理前缀会默认从路径中移除。此时,可以通过设置zuul.stripPrefix=false来关闭该移除代理前缀的动作,也可以通过zuul.routes.
在Zuul实现的API网关路由功能中,还可以支持forward形式的服务端跳转配置,示例:
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=forward:/local
默认情况下,Zuul在请求路由时,会过滤掉HTTP请求头中的一些敏感信息,防止它们被传递到下有的外部服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,包括cookie、Set-cookie、Authorization三个属性。因此,平时开发的Web项目中常用的cookie在Zuul网关中默认是不会传递的。为了解决这个问题,常常有两种做法:
通过设置全局参数为空来覆盖默认值,示例:
zuul.sensitiveHeaders=
通过指定路由的参数来配置,示例:
# 方法一:对指定路由开启自定义敏感头
zuul.routes.
6.2 过滤器
过滤器比较常见的做法是继承ZuulFilter,实现四个方法即可: //返回值为过滤器的类型,它决定过滤器在请求的哪个生命周期中执行
public String filterType();
//过滤器的执行顺序,当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值依次执行
public int filterOrder();
//判断该过滤器是否需要被执行
public boolean shouldFilter();
//过滤器的具体逻辑
public Object run();
@Bean
public AccessFilter accessFilter(){
return AccessFilter();
}
作为系统的统一入口,屏蔽了系统内部各个微服务的细节。与服务治理框架相结合,实现自动化的服务示例维护以及负载均衡的路由转发。实现接口权限检验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的检验前移,保证了微服务的无状态行,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
该函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了4种不同生命周期的过滤器类型,具体为:
routing:在路由请求时被调用;
post:在routing和error过滤器之后被调用;
error:处理请求,发生错误时被调用。
通过int值来定义过滤器的执行顺序,数值越小优先级越高。shouldFilter
返回一个boolean值来判断该过滤器是否要执行,可以通过此方法来指定过滤器的有效范围。run
过滤器的具体逻辑。
6.2.1 请求的生命周期
当外部HTTP请求到达API网关服务的时候,首先他会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型过滤器的主要目的是在进行请求路由之前做一些前置贾工,比如请求的校验等。在完成pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post。此时请求将会被post类型的过滤器处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些贾工或转换等内容。另外,还有一个特殊的阶段error,该阶段只有上述三个阶段发生异常的时候才会触发,但是它最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端。
ServletDetectionFilter:执行顺序为-3,是最先被执行的过滤器。
Servlet30WrapperFilter:执行顺序为-2,是第二个执行的过滤器。
FromBodyWrapperFilter:执行顺序为-1,是第三个执行的过滤器。
DebugFilter:执行顺序为1,是第四个执行的过滤器。
PreDecorationFilter:执行顺序为5,是pre阶段最后被执行的过滤器。默认启用的route过滤器
RibbonRoutingFilter:执行顺序为10,是route阶段第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。
SimpleHostRoutingFilter:执行顺序为100,是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。
SendForwardFilter:执行顺序为500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中forward本地跳转配置。默认启用的post过滤器
SendErrorFilter:执行顺序为0,是post阶段第一个执行的过滤器。给过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。
SendResponseFilter:执行吮吸是1000,是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含他们其中一个时执行处理逻辑。
6.2.2 异常处理
com.netflix.zuul.http.ZuulServlet的service中的各种过滤器的执行顺序: try{
preRoute();
}catch(ZuulException e){
error(e);
postRoute();
return;
}
try{
route();
}catch(ZuulException e){
error(e);
postRoute();
return;
}
try{
postRoute();
}catch(ZuulException e){
error(e);
return;
}
6.2.3 禁用过滤器
要禁用指定过滤器的话,示例: zuul.
和可用性的服务。
的不一致。最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。



