Spring Cloud为开发人员提供了工具,以快速构建分布式系统中的某些常见模式(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,领导选举,分布式会话,群集状态)。
Eureka 服务注册与发现
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
Eureka 服务发现组件应用场景:
首先有一个微服务发布在局域网IP地址为192.168.0.100这台机器的80端口,他提供User信息服务,可以通过id获取到用户信息。
OK,这样子编码似乎没什么问题,我想要的数据我已经获取到了。开始大量编码,从192.168.0.100这个IP地址获取用户信息应用在当前微服务的各个方面。
可是,如果获取用户信息这个微服务地址变了,怎么办?我们得修改所有的代码。
Eureka解决了这个问题。我们把Eureka理解为一个注册中心。
在分布式架构里面,有多个微服务,每个微服务有对应它自己的请求地址。然后微服务把这个地址提交到Eureka,由Eureka来进行记录。
比如:User微服务的地址是192.168.0.100,我只要问Eureka,我该从哪里获取到User信息啊?Eureka就告诉我:192.168.0.100。
当User微服务的地址换成192.168.0.101的时候,由它告诉Eureka。然后Eureka再记录下来,这时我再问它,我该从哪里获取到User信息啊?Eureka告诉我:192.168.0.101。
Eureka包含两个组件:Eureka Server和Eureka Client。
调用关系说明:
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址给消费者。
- 服务消费者从提供者地址中调用消费者。
Eureka Server: 提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,包括主机与端口号、服务版本号、通讯协议等。这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka服务端支持集群模式部署,首尾相连形成一个闭环即可,集群中的的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点每个实例关于所有服务的状态可能存在不一致的现象。
Eureka Client: 主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期行的刷新服务状态。
服务调用:
服务消费者在获取服务清单后,通过服务名可以获取具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
-
Eureka Server 的配置
① pom引入依赖
<!--eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
② yml文件配置
server: port: 7001 spring: application: # 用于指定这个微服务名称,也就是在注册中心显示的名称,其他微服务通过这个名称调用服务 name: eureka-server eureka: # Eureka Client客户端特性配置 client: service-url: # 这个就是注册中心的地址。就是当前配置的Eureka Server的地址。微服务将自己注册到Eureka、微服务查询其他微服务都是通过这个地址。 # (注意:地址最后面的 /eureka/ 这个是固定值) defaultZone: http://localhost:${server.port}/eureka # 以下两项一定要是false,表明自己是服务器,而不需要从其他主机发现服务 # false表示不向注册中心注册自己(eureka本身是不需要再注册自己的) register-with-eureka: false # 是否从eureka获取注册信息,false表示自己就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 fetch-registry: false # Eureka Server注册中心特性配置 server: enable-self-preservation: false # 关闭自我保护模式 eviction-interval-timer-in-ms: 10000 # 清理无效节点的时间间隔,默认60000毫秒,即60秒 wait-time-in-ms-when-sync-empty: 5 # 设置 eureka server同步失败的等待时间 默认为5分 在这期间,它不向客户端提供服务注册信息 number-of-replication-retries: 5 # 设置 eureka server同步失败的重试次数 默认为 5 次 # 当前Eureka Instance实例信息配置 instance: lease-renewal-interval-in-seconds: 15 #服务失效时间,Eureka多长时间没收到服务的renew操作,就剔除该服务,默认90秒
③ 启动类
@SpringBootApplication @EnableEurekaServer //启用Eureka服务端,声明一个注册中心 public class registryApplication { public static void main(String[] args) { SpringApplication.run(registryApplication.class); } }
-
高可用注册中心
Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。
Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现。
server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: ${spring.cloud.client.ipAddress} prefer-ip-address: true client: serviceUrl: defaultZone: http://127.0.0.1:8762/eureka/,http://172.20.10.3:8763/eureka/
打开 Eureka 监控 http://127.0.0.1:8761/eureka/ 出现下图说明配置正确。
-
Eureka Client 的配置
① pom引入依赖
<!--将微服务provider注册进eureka--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
② yml 文件配置
server: port: 8001 spring: application: name: study-springcloud-dept # 将来会作为微服务名称注入到eureka容器中 eureka: client: service-url: defaultZone: http://localhost:7001/eureka # 注册进对应此地址的eureka服务中心 registry-fetch-interval-seconds: 5 # 设置拉取服务的间隔时间5秒 # 实例信息配置 instance: instance-id: study-springcloud-dept # 自定义服务实例ID,不能与相同appname的其他实例重复。 prefer-ip-address: true # 将IP注册到Eureka Server上,如果不配置就是机器的主机名 lease-renewal-interval-in-seconds: 5 # 心跳时间:5秒钟发送一次心跳 lease-expiration-duration-in-seconds: 15 # 过期时间:15秒不发送就过期
③ 启动类
@SpringBootApplication @EnableDiscoveryClient //启用Eureka客户端,@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient可以是其他注册中心。 public class StudySpringCloudDept { public static void main(String[] args) { SpringApplication.run(StudySpringCloudDept.class); } }
④ 注册成功,在7001的Eureka服务站中会显示8001微服务
-
Eureka 常用配置及说明
配置参数 |
默认值 |
说明 |
服务注册中心配置 |
|
Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean |
eureka.server.enable-self-preservation |
false |
关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除 |
服务实例类配置 |
|
Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean |
eureka.instance.prefer-ip-address |
false |
不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了 eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址 |
eureka.instance.ip-address |
|
IP地址 |
eureka.instance.hostname |
|
设置当前实例的主机名称 |
eureka.instance.appname |
|
服务名,默认取 spring.application.name 配置值,如果没有则为 unknown |
eureka.instance.lease-renewal-interval-in-seconds |
30 |
定义服务续约任务(心跳)的调用间隔,单位:秒 |
eureka.instance.lease-expiration-duration-in-seconds |
90 |
定义服务失效的时间,单位:秒 |
eureka.instance.status-page-url-path |
/info |
状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
eureka.instance.status-page-url |
|
状态页面的URL,绝对路径 |
eureka.instance.health-check-url-path |
/health |
健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
eureka.instance.health-check-url |
|
健康检查页面的URL,绝对路径 |
服务注册类配置 |
|
Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean |
eureka.client.service-url. |
|
指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。 如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码 |
eureka.client.fetch-registery |
true |
检索服务 |
eureka.client.registery-fetch-interval-seconds |
30 |
从Eureka服务器端获取注册信息的间隔时间,单位:秒 |
eureka.client.register-with-eureka |
true |
启动服务注册 |
eureka.client.eureka-server-connect-timeout-seconds |
5 |
连接 Eureka Server 的超时时间,单位:秒 |
eureka.client.eureka-server-read-timeout-seconds |
8 |
读取 Eureka Server 信息的超时时间,单位:秒 |
eureka.client.filter-only-up-instances |
true |
获取实例时是否过滤,只保留UP状态的实例 |
eureka.client.eureka-connection-idle-timeout-seconds |
30 |
Eureka 服务端连接空闲关闭时间,单位:秒 |
eureka.client.eureka-server-total-connections |
200 |
从Eureka 客户端到所有Eureka服务端的连接总数 |
eureka.client.eureka-server-total-connections-per-host |
50 |
从Eureka客户端到每个Eureka服务主机的连接总数 |
-
Eureka自我保护机制
- Netflix在设置Eureka时,遵循AP原则
- 某时刻某一微服务不可用时,eureka不会立刻清理,依旧会对改微服务的信息进行保存。服务失去心跳、名称变更、网络拥堵
- 自我保护机制:应对网络异常的安全措施
微服务客户端之所以可以与 Eureka 之间保持联系,依靠的是心跳机制,默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险——因为微服务本身其实是健康的,此时本不应该注销这个服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点再短时间内丢失过多客户端时(可能发生网络故障),那么这个节点就会进入自我保护模式。一旦进去该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也即不会注销任何微服务)。当网络故障恢复后,该EurekaServer节点会自动退出自我保护模式。
宁可保护错误的注册信息,也不盲目注销任何可能健康的微服务实例。
禁用自我保护机制
可以使用 eureka.server.enable-self-preservation=false
-
Eureka服务发现
对于注册进 eureka 里面的服务,可以通过服务发现接口 DiscoveryClient 获得该服务的信息。
根据服务名返回对应的信息
import org.springframework.cloud.client.discovery.DiscoveryClient; //别导错包 @Autowired private DiscoveryClient discoveryClient; //添加服务发现接口 @GetMapping("/discover") public Object discovery(){ List<String> list = discoveryClient.getServices(); //通过服务 ID,获取当前服务的服务实例 List<ServiceInstance> instances = discoveryClient.getInstances("PROVIDER"); for (ServiceInstance element : instances){ System.out.println(element.getServiceId()); System.out.println(element.getHost()); System.out.println(element.getPort()); System.out.println(element.getUri()); } return this.discoveryClient; }
注册中心现有服务与实例数:
浏览器访问结果:
控制台打印结果:
获取注册中心所有服务下面的实例信息
@GetMapping("/discover") public Object discover(){ Map<String,List<ServiceInstance>> instances = new HashMap<>(); List<String> services = discoveryClient.getServices(); //获取所有服务 ID 列表 for(String service:services){ List<ServiceInstance> sis = discoveryClient.getInstances(service); //服务的详细信息 instances.put(service,sis); } return instances; }
浏览器访问结果:
{"zuul-gateway":[{"host":"DESKTOP-390CVI3","port":6001,"secure":false,"instanceInfo":{"instanceId":"DESKTOP-390CVI3:zuul-gateway:6001","app":"ZUUL-GATEWAY","appGroupName":null,"ipAddr":"192.168.1.146","sid":"na","homePageUrl":"http://DESKTOP-390CVI3:6001/","statusPageUrl":"http://DESKTOP-390CVI3:6001/actuator/info","healthCheckUrl":"http://DESKTOP-390CVI3:6001/actuator/health","secureHealthCheckUrl":null,"vipAddress":"zuul-gateway","secureVipAddress":"zuul-gateway","countryId":1,"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"DESKTOP-390CVI3","status":"UP","overriddenStatus":"UNKNOWN","leaseInfo":{"renewalIntervalInSecs":30,"durationInSecs":90,"registrationTimestamp":1571289340850,"lastRenewalTimestamp":1571298976811,"evictionTimestamp":0,"serviceUpTimestamp":1571288765718},"isCoordinatingDiscoveryServer":false,"metadata":{"management.port":"6001"},"lastUpdatedTimestamp":1571289340850,"lastDirtyTimestamp":1571289340818,"actionType":"ADDED","asgName":null},"metadata":{"management.port":"6001"},"serviceId":"ZUUL-GATEWAY","uri":"http://DESKTOP-390CVI3:6001","scheme":null}],"provider":[{"host":"192.168.1.146","port":1001,"secure":false,"instanceInfo":{"instanceId":"DESKTOP-390CVI3:provider:1001","app":"PROVIDER","appGroupName":null,"ipAddr":"192.168.1.146","sid":"na","homePageUrl":"http://192.168.1.146:1001/","statusPageUrl":"http://192.168.1.146:1001/actuator/info","healthCheckUrl":"http://192.168.1.146:1001/actuator/health","secureHealthCheckUrl":null,"vipAddress":"provider","secureVipAddress":"provider","countryId":1,"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"192.168.1.146","status":"UP","overriddenStatus":"UNKNOWN","leaseInfo":{"renewalIntervalInSecs":5,"durationInSecs":15,"registrationTimestamp":1571283266142,"lastRenewalTimestamp":1571298914780,"evictionTimestamp":0,"serviceUpTimestamp":1571283012727},"isCoordinatingDiscoveryServer":false,"metadata":{"management.port":"1001"},"lastUpdatedTimestamp":1571283266142,"lastDirtyTimestamp":1571283266102,"actionType":"ADDED","asgName":null},"metadata":{"management.port":"1001"},"serviceId":"PROVIDER","uri":"http://192.168.1.146:1001","scheme":null},{"host":"192.168.1.146","port":1002,"secure":false,"instanceInfo":{"instanceId":"DESKTOP-390CVI3:provider:1002","app":"PROVIDER","appGroupName":null,"ipAddr":"192.168.1.146","sid":"na","homePageUrl":"http://192.168.1.146:1002/","statusPageUrl":"http://192.168.1.146:1002/actuator/info","healthCheckUrl":"http://192.168.1.146:1002/actuator/health","secureHealthCheckUrl":null,"vipAddress":"provider","secureVipAddress":"provider","countryId":1,"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"192.168.1.146","status":"UP","overriddenStatus":"UNKNOWN","leaseInfo":{"renewalIntervalInSecs":5,"durationInSecs":15,"registrationTimestamp":1571283280282,"lastRenewalTimestamp":1571298916396,"evictionTimestamp":0,"serviceUpTimestamp":1571283036067},"isCoordinatingDiscoveryServer":false,"metadata":{"management.port":"1002"},"lastUpdatedTimestamp":1571283280282,"lastDirtyTimestamp":1571283280247,"actionType":"ADDED","asgName":null},"metadata":{"management.port":"1002"},"serviceId":"PROVIDER","uri":"http://192.168.1.146:1002","scheme":null}],"microservice-order-consumer":[{"host":"192.168.1.146","port":2000,"secure":false,"instanceInfo":{"instanceId":"DESKTOP-390CVI3:microservice-order-consumer:2000","app":"MICROSERVICE-ORDER-CONSUMER","appGroupName":null,"ipAddr":"192.168.1.146","sid":"na","homePageUrl":"http://192.168.1.146:2000/","statusPageUrl":"http://192.168.1.146:2000/actuator/info","healthCheckUrl":"http://192.168.1.146:2000/actuator/health","secureHealthCheckUrl":null,"vipAddress":"microservice-order-consumer","secureVipAddress":"microservice-order-consumer","countryId":1,"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"192.168.1.146","status":"UP","overriddenStatus":"UNKNOWN","leaseInfo":{"renewalIntervalInSecs":5,"durationInSecs":15,"registrationTimestamp":1571299013879,"lastRenewalTimestamp":1571299033849,"evictionTimestamp":0,"serviceUpTimestamp":1571296919932},"isCoordinatingDiscoveryServer":false,"metadata":{"management.port":"2000"},"lastUpdatedTimestamp":1571299013879,"lastDirtyTimestamp":1571299013850,"actionType":"ADDED","asgName":null},"metadata":{"management.port":"2000"},"serviceId":"MICROSERVICE-ORDER-CONSUMER","uri":"http://192.168.1.146:2000","scheme":null}]}
Ribbon负载均衡
概述:
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向 服务的REST模版请求自动转换成客户端负载均衡的服务调用。
重要功能是提供客户端的软件负载均衡算法,Ribbon客户端组件提供一系列的配置项如链接超时、重试等。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
- 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
- 服务消费者直接通过调用被 @LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务的接口调用。
这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。
Ribbon与负载均衡
负载均衡,即利用特定方式将流量分摊到多个操作单元上,它对系统吞吐量与系统处理能力有着质的提升。
负载均衡分类:
1 集中式负载均衡:也叫做服务端负载均衡,位于英特网与服务提供者之间,负责把网络请求转发给各个提供单位,典型产品是Ngixn;
2 进程负载均衡:也叫作客户端负载均衡,位于服务提供者内部,从注册中心的已注册实例库选取实例进行流量导入,整个过程都是在进程间通信。
Ribbon就是典型的客户端负载均衡,它赋予了应用一些支配HTTP与TCP行为的能力,如果没有Ribbon,Spring Cloud构建的服务则无法横向扩展,所以Spring Cloud的Feign和Zuul默认集成了Ribbon。
Ribbon配置
① 修改pom配置,增加相关引用
<!--Ribbon相关引用,与eureka有关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
② 修改application.yml ,追加Eureka的注册服务地址
eureka: client: service-url: defaultZone: eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka service_provider: #采用Ribbon的服务实例 ribbon: eager-load: enabled: true #开启饥饿加载 clients: server-1,server-2,server-3 #为哪些服务的名称开启饥饿加载,多个用逗号分隔 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 将Ribbon的负载均衡策略修改为随机
③ 主启动类中增加注解@EnableEurekaClient
@SpringBootApplication @EnableEurekaClient public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class); } }
④ 建立一个配置类,在类中增加getRestTemplate方法,并增加@LoadBalance注解,开启客户端负载均衡
@Configuration public class RibbonConfig { @Bean // 在启动类配置RestTemplate的Bean,把他交由spring进行管理 @LoadBalanced /*开启负载均衡,默认会轮询调用提供者*/ public RestTemplate restTemplate(){ return new RestTemplate(); } }
⑤ 将地址改为注册在EurekaServer中的微服务名称
//注入刚才交由spring托管的bean @Autowired RestTemplate restTemplate; @GetMapping @ResponseBody public User queryUserById(Long id){ return this.restTemplate.getForObject("http://service-provider/user/"+id,User.class); }
在使用RestTemplate的时候,千万别忘了加@LoadBalance注解。
负载均衡策略
Ribbon 组成
Java类方式修改负载均衡策略
@Configuration //通过用@RibbonClient来为Ribbon客户端声明额外的配置 //name 用来指定需要均衡的服务,即服务提供者,configuration 用来指定所用的策略配置,这里使用我们自定义的一个配置 RibbonRuleConfig。 //添加Ribbon的配置类,注意该类必须配置在@SpringBootApplication主类以外的包下。不然的话所有的服务都会按照这个规则来实现。会被所有的RibbonClient共享。主要是主类的主上下文和Ribbon的子上下文起冲突了。父子上下文不能重叠。 @RibbonClient(name = "PROVIDER", configuration = RibbonRuleConfig.class) public class RibbonConfig { @Bean @LoadBalanced /*添加负载均衡,默认会轮询调用提供者*/ public RestTemplate restTemplate(){ return new RestTemplate(); } } // 自定义负载均衡策略配置类 @Configuration public class RibbonRuleConfig { @Bean public IRule ribbonRule() { return new RandomRule(); } }
Feign 声明式服务调用
简介:
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
Spring Cloud 对 Feign 进行了增强,使 Feign 支持了 Spring MVC 注解,并整合了 Ribbon 和 Eureka,从而让Feign的使用更加方便。
Spring Cloud Feign 帮助我们定义和实现依赖服务接口的定义。在 Spring Cloud feign 的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用 Spring Cloud Ribbon 时自行封装服务调用客户端的开发量。
Spring Cloud Feign 具备可插拔的注解支持,支持Feign注解、JAX-RS 注解和 Spring MVC 的注解。
① 引入Feign 依赖
<!--增加Feign的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.2.RELEASE</version> </dependency>
② yml配置
#feign的配置,连接超时及读取超时配置 feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
③ 创建一个Feign接口,并添加@FeignClient注解,定义Feign客户端,并指定微服务id。
/** * Feign通过接口的方法调用Rest服务(之前是Ribbon+RestTemplate) * 通过 @FeignClient("服务名") 注解来指定调用哪个服务 */ @FeignClient("service-provider") public interface FeignUserClient{ @GetMapping("{user/id}") //调用绑定微服务的对应Controller层请求 public User queryUserById(@PathVariable("id")Long id); }
③ SpringbootApplication启动类加上@FeignClient注解,以及@EnableDiscoveryClient。
④ 上面是最简单的 Feign Client 的使用,声明完为 Feign Client 后,在Controller层就可以注入 FeignUserClient 这个接口,进行远程服务调用了。
@RestController public class ConsumerFeignController { //这里直接注入feign client @Autowired private FeignUserClient userClient; @GetMapping("/user/{id}") public ResponseEntity queryUser(@PathVariable("id") Long id){ Long result = userClient.queryUserById(id); return ResponseEntity.ok(result); } }
这看起来有点像我们 Spring MVC 模式的 Controller 层的 RequestMapping 映射。Feign 是用@FeignClient 来映射服务的。
Feign集成了Ribbon
利用Ribbon维护了微服务列表,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅的而简单的实现了服务调用。
Hystrix 服务熔断与服务降级
简介:
分布式面临的问题:
复杂分布式体系结构复杂的依赖关系,不可避免的存在服务宕机,网络中断的问题。
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。并且这种不可用可能沿请求调用链向上传递,这种现象被称为雪崩效应。
Hystrix介绍:
Hystrix是Netflix开源的一个延迟和容错库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
Hystrix解决雪崩问题的手段有两个:
- 线程隔断
- 服务熔断
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判断时间。
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理——优先保证核心服务,而非核心服务不可用或弱可用。
用户的请求故障时,不会被立即阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回一个提示信息)。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个以来服务对应的线程池中的资源,对其他服务没有响应。
触发Hystrix服务降级的情况:
- 线程池已满
- 请求超时
服务降级:
整体资源不够了,忍痛将某些服务先关掉,待度过难关后再开启。
所谓降级,一般是从整体负荷考虑,当某个服务熔断后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值,这样做,虽然服务水平下降,好歹能用,比直接关掉要强。
服务降级是在客户端(消费者)处理完成的,与服务端没关系。
① 引入Hystrix依赖
<!--Hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
② 主启动类上添加 @EnableHystrix 注解开启熔断
//@SpringBootApplication //@EnableDiscoveryClient //@EnableCircuitBreaker @SpringCloudApplication //组合注解,相当于上面三个注解的组合 @EnableHystrix //或者单独加入这个注解,它继承了@EnableCircuitBreaker注解 public class ItcastServiceConsumerApplication {
③ 定义一个局部熔断方法(要和被熔断的方法返回值和参数列表一致)
public String queryUserByIdFallback(Long id){ return "服务正忙,请稍后再试!"; }
④ @HystrixCommand 定义服务降级的方法
一旦服务调用失败并抛出错误信息后,会自动调用 @HystrixCommand 标注好的 fallBackMethod 调用类中知道的方法
@GetMapping @ResponseBody @HystrixCommand(fallbackMethod="queryUserByIdFallback") public String queryUserById(Long id){ return this.restTemplate.getForObject("http://service-provider/user/"+id,User.class); }
⑤ 也可以在类上定义一个全局的熔断方法(返回值类型要和被熔断的方法一致,参数列表必须为空)
@DefaultProperties(defaultFallback = queryUserByIdFallback ) @Controller public class UserController{ @HystrixCommand //哪个方法需要熔断就加在哪个方法上面 public String queryUserById(Long id){ } }
设置超时
在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,我们可以通过设置修改这个值:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds:6000 # 设置hystrix的超时时间为6000ms
这样的机制,对自身服务起到了基础的保护,同时还为异常情况提供了自动的服务降级切换机制。
依赖隔离
在上面的示例中,我们使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的,这样利用Hystrix来实现服务容错保护在编程模型上就非常方便的,并且考虑更为全面。除了依赖隔离、服务降级之外,还有一个重要元素:断路器。 这三个重要利器构成了Hystrix实现服务容错保护的强力组合拳。
服务熔断
熔断原理:
熔断器,也叫断路器,其英文单词为:Circult Breaker。
在服务消费端的服务降级逻辑因为hystrix命令调用依赖服务超时,触发了降级逻辑,但是即使这样,受限于Hystrix超时时间的问题,我们的调用依然很有可能产生堆积。
每个请求都会在当hystrix超时之后返回fallback,每个请求时间延迟就是近似hystrix的超时时间,如果设置为5秒,那么每个请求就都要延迟5秒才会返回。当熔断器在10秒内发现请求总数超过20,并且错误百分比超过50%,这个时候熔断器打开。打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就不会等待5秒之后才返回fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
在断路器打开之后,处理逻辑并没有结束,我们的降级逻辑已经被成了主逻辑,那么原来的主逻辑要如何恢复呢?对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
通过上面的一系列机制,hystrix的断路器实现了对依赖资源故障的端口、对降级策略的自动切换以及对主逻辑的自动恢复机制。这使得我们的微服务在依赖外部服务或资源的时候得到了非常好的保护,同时对于一些具备降级逻辑的业务需求可以实现自动化的切换与恢复,相比于设置开关由监控和运维来进行切换的传统实现方式显得更为智能和高效。
熔断机制3个状态:
- Closed:关闭状态,所有请求都正常访问。
- Open:打开状态,所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔 断,断路器会完全打开。默认失败的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5秒)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持断开,再次进行休眠计时。
通过配置修改熔断策略:
circuitBreaker.requestVolumThreshold=20 # 触发熔断的最小请求次数,默认20 circuitBreaker.sleepWindowInMillisseconds=50000 # 休眠时长,默认是5000毫秒 circuitBreaker.errorThresholdPercentage=50 # 出发熔断的失败请求最小占比,默认50%
断路器监控——Hystrix Dashboard
Hystrix Dashboard 是作为断路器状态的一个组件,实现了 Hystrix 指标数据的可视化面板。
被 @HystrixCommand 注解修饰的接口,它的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用
① 引入Hystrix依赖
<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>
② 主启动类上添加 @EnableHystrixDashboard 注解
③ 启动后访问Hystrix仪表盘:http://localhost:端口号/hystrix
zuul 路由网关
概述:
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Neflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
Zuul包含了对请求的路由和过滤的主要功能:
- 路由功能
主要负责将外部请求转发到具体的微服务上,是实现外部访问入口的基础
- 过滤功能
负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础
Zuul与Eureka进行整合,将Zuul自身注册为Eurekad的服务治理下的应用,同时从Eureka中获得其他微服务的信息,也即以后的微服务访问都是通过Zuul跳转后获得。
三大功能: 代理、路由、过滤
不用zuul,让客户端直接与各个微服务通讯,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通讯,那么重构将会很难实施。
- 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。
① 引入Zuul依赖
<!--zuul依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
② yml文件配置
server: port: 6001 spring: application: name: zuul-gateway eureka: client: service-url: defaultZone:http://localhost:10086/eureka # 如果不配置: 默认路由规则 = 域名 + 端口 + 对应服务名 + 请求路径 http://localhost:6001/service-provider/user
③ 主启动类添加注解@EnableZuulProxy,启用Zuul组件
@SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy // 在主启动类上加该注解即可开启zuul public class ZuulGatewayApp { public static void main(String[] args) { SpringApplication.run(ZuulGatewayApp.class,args); } }
路由配置
- 同时指定微服务的serviceId和对应路径
server: port: 6001 zuul: routes: service-provider: # 该配置方式中,service-provider 标识 代表一组路由的名称,只是给路由一个名称,可以任意起名 service-id: microservice-provider-user # 要被路由的微服务名 path: /user/** # service-id对应的真实路径
正常访问 microservice-provider-user 微服务的方式为:http://localhost:8761/user/getUser。
启动网关服务后如果没有配置 zuul 节点默认访问方式:http://localhost:6001/microservice-provider-user/user/getUser。
这样设置,microservice-provider-user微服务就会被映射到 /user/** 路径,也就是 http://localhost:6001/user/** 开头的请求就会访问到 http://localhost:8761/user/** 微服务。
- 同时指定path和URL
zuul: routes: service-provider: # 该配置方式中,service-provider 标识 代表一组路由的名称,只是给路由一个名称,可以任意起名 url: http://localhost:8011/ # 映射路径对应的真实的url path: /user/** # 这里是浏览器映射路径
这样设置,就可以将 http://localhost:6001/user/** 开头的请求映射到http://localhost:8011/user/**。
- 最简单的配置方式
配置zuul.routes.指定微服务serviceId : 指定路径即可
zuul: routes: microservice-provider-user: /user/**
这样设置,microservice-provider-user微服务就会被映射到/user/** 路径,也就是/user/开头的请求就会访问到 microservice-provider-user微服务
- 忽略指定微服务
忽略服务非常简单,可以使用zuul.ignored-services配置需要忽略的服务,多个用逗号分隔。例如:
zuul:
ignored-services: microservice-provider-user
这样就可让Zuul忽略microservice-provider-user微服务,只代理其他微服务。
- 忽略所有微服务,只路由指定微服务
zuul: ignored-services: '*' # 使用'*' 可忽略掉所有微服务 routes: microservice-provider-user: /user/**
如此,便可以将所有url统一请求到网关服务,然后再路由到各自的服务。
- 忽略某些路径
zuul: ignored-patterns: /**/admin/** # 忽略所有包含/admin/的路径 routes: microservice-provider-user: /user/**
这样就可以将microservice-provider-user微服务映射到/user/**路径,但会忽略该微服务中所有包含/admin/的路径。
- 路由前缀
zuul: prefix: /api routes: microservice-provider-user: /user/**
这样,访问Zuul的 /api/user/** 路径,请求将会被转发到 microservice-provider-user的/user/**
Spring Cloud Config 配置中心
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。方便部署与运维。分客户端、服务端。是一种用来动态获取Git、SVN、本地的配置文件的一种工具。
服务端:也称分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端:则是通过指定配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。默认采用 git,并且可以通过 git 客户端工具来方便管理和访问配置内容。
Config Server 服务端
新建一个模块名为ConfigServer
① 引入依赖
<!--Spring Cloud Config 服务端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
② yml文件配置
server: port: 7001 spring: application: name: config-server cloud: config: server: git: uri: https://github.com/hellxz/SpringCloudlearn # 指向的是配置文件所在的git项目uri search-paths: config-repo # 指定的是匹配查询的路径名 username: username # 是访问仓库的用户名和密码 password: password
③ 主启动类ConfigServerApp
/** * 这里没有使用SpringBootApplication或SpringCloudApplication注解,会报错 * 原因也很简单,我们的java源码目录下没有目录,我们手动加一个也就正常了, * 为了写点体会和这里没必要用到包和类,所以使用这种方式 */ @EnableConfigServer @SpringBootConfiguration @EnableAutoConfiguration public class ConfigServerApp { public static void main(String[] args) { SpringApplication.run(ConfigServerApp.class, args); } }
访问方式:
/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties
label 为分支,application 为应用名,profile 为环境信息。
注意:
- 第一个规则的分支名是可以省略的,默认是master分支
- 无论你的配置文件是properties,还是yml,只要是应用名+环境名能匹配到这个配置文件,那么就能取到
- 如果是想直接定位到没有写环境名的默认配置,那么就可以使用default去匹配没有环境名的配置文件
- 使用第一个规则会匹配到默认配置
- 如果直接使用应用名来匹配,会出现404错误,此时可以加上分支名匹配到默认配置文件
- 如果配置文件的命名很由多个-分隔,此时直接使用这个文件名去匹配的话,会出现直接将内容以源配置文件内容直接返回,内容前可能会有默认配置文件的内容
Config Client 客户端
客户端向服务端获取相关信息。
① 引入依赖
<!-- SpringCloud Config客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
② yml文件配置
spring: cloud: config: name: microservicecloud-config-dept-client #需要从github上读取的资源名称,注意没有yml后缀名 profile: test #本次访问的配置项 label: master uri: http://config-3344.com:3344 #本微服务启动后先去找3344号服务,通过SpringCloudConfig获取GitHub的服务地址