SpringCloud简介
SpringCloud与SpringBoot的依赖版本:
- Finchley是基于Spring Boot 2.0.x构建的不再Boot 1.5.x。
- Dalston和Edgware是基于Spring Boot 1.5.x构建的,不支持Spring Boot 2.0.x。
- Camden构建于Spring Boot 1.4.x, 但依然能支持Spring Boot 1.5.x。
SpringCloud组件简介:
SpringCloud组件变化:
Eureka
什么是服务治理:
- Spring Cloud封装了Netflix 公司开发的Eureka模块来实现服务治理。
- 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
什么是服务注册与发现:
- Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
- 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供 者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一一个注册中心(存放服务地址相关信息(接口地址))。
Eureka两组件:
- Eureka包含两个组件:Eureka Server和Eureka Client
- Eureka Server提供服务注册服务:各个微服务节点通过配置启动后,会在EurekaServer中进行注册, 这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
- EurekaClient通过注册中心进行访问:是一个Java客户端, 用于简化Eureka Server的交互, 客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server使用:
- pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- application.yml:
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
- 主启动类:使用@EnableEurekaServer。
Eureka Client使用:
- pom:
<dependency>
<groupId>org.springframework.cloud</ groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml:
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
- 主启动类:使用@EnableEurekaClient。
- 微服务注册名配置说明:
集群Eureka构建:
-
问题:微服务RPC远程服务调用最核心的是什么 可用,试想你的注册中心只有一个only one,它出故障了那就呵呵了,会导致整个为服务环境不可用, 所以 解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错。
-
Eureka Server改application.yml:
------------集群A:------------
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
------------集群B:------------
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
- Eureka Client改application.yml:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
actuator微服务信息完善:
- 主机名称:服务名称修改:改application.yml
instance:
instance-id: payment8001
- 访问信息有ip信息提示:改application.yml
instance:
prefer-ip-address: true
服务发现Discovery:
- 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息。
- 代码:
@Resource
private DiscoveryClient discoveryClient;
- 方法: ①discoveryClient.getServices():获取所有服务信息。 ②discoveryClient.getInstances("服务名称"):获取指定服务名称信息。
- 在主启动类上加@EnableDiscoveryClient。
Eureka自我保护:
- 概述:保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式, EurekaServer将会尝试保护其服务注册表中的信息, 不再删除服务注册表中的数据,也就是不会注销任何微服务。
- 如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CL AIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
- 为什么产生Eureka自我保护机制? ①为了防止EurekaClient可以正常运行但是与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
- 什么是自我保护模式?
①默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳, EurekaServer将会注销该实例(默认90秒)。但当网络分区故障发生(延时、 卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了。因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过 ”自我保护模式”来解决这个问题一 当EurekaServer节点在时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
②在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。
- 综上:
①
自我保护模式是一 种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、 稳定。②一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。③属于CAP里面的AP分支。 - 怎么禁止自我保护(一般生产环境中不会禁止自我保护):
一、改EurekaSever的application.yml:
#出厂默认,自我保护机制是开启的:
eureka.server.enable-self-preservation = true
二、改EurekaClient的application.yml:
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
instance :
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
Zookeeper
注册中心Zookeeper:
- zookeeper是一个分布式协调工具,可以实现注册中心功能。
- 关闭Linux服务器防火墙后启动zookeeper服务器。
- zookeeper服务器取代Eureka服务器,zk作为服务注册中心。
- zookeeper服务节点是临时节点还是持久节点?
①
客户端断开连接后配置信息消失,所以是临时节点。
Zookeeper Client使用:
- pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 改application.yml:
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.136.140:2181
- 主启动类上使用@EnableDiscoveryClient。
①@EnableDiscoveryClient注解是基于spring-cloud-commons依赖,并且在classpath中实现;
②@EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用;
③
其实用更简单的话来说,就是如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient,如果是其他的注册中心,那么推荐使用@EnableDiscoveryClient。 - 其他与Eureka没区别。
Consul
Consul简介:
- 是什么: ①Consul是一开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go语言开发。 ②提供了微服务系统中的服务治理、I配置中心、控制总线等功能。这些功能中的每一个 都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。 ③它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查, 同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨平台,支持Linux、Mac、 Windows。
- 能干嘛: ①服务发现:提供HTTP和DNS两种发现方式。 ②健康监测:支持多种协议,HTTP、TCP、Docker、Shell脚本定制化。 ③KV存储:key , Value的存储方式。 ④多数据中心:Consul支持多数据中心。 ⑤可视化Web界面。
- 安装运行: ①下载完成后只有一个consul.exe文件,硬盘路径下双击运行。 ②使用开发模式启动:consul agent -dev。 ③通过以下地址可以访问Consul的首页:http://localhost:8500。
Consul Client使用:
- pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- 改application.yml:
server:
port: 8006
spring:
application:
name: consul-provider-payment
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
- 其他与Zookeeper没区别。
三个注册中心异同点:
- CAP:CAP理论关注粒度是数据,而不是整体系统设计的策略 ①C:Consistency(强一致性) ②A:Availability(可用性) ③P:Partition tolerance(分区容错)
- CAP理论的核心是一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类: ①CA-单点集群,满足一致性,可用性的系统,通常在可扩展性不太强大。 ②CP -满足一致性,分区容忍必的系统,通常性能不是特别高。 ③AP -满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
AP(Eureka)CP(Zookeeper/Consul)
Ribbon
Ribbon简介:
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一 套客户端负载均衡的工具。
- 简单的说,Ribbon是Netflix发布的开源项目, 主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
- 不过Ribbon目前也进入维护模式,未来的替代方案:LoadBalancer。
- LB(负载均衡):
①
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。 ②进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。 Ribbon就属于进程内LB,它只是一个类库, 集成于消费方进程,消费方通过它来获取到服务提供方的地址。Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
使用:
- pom:
<dependency>
<groupld> org.springframework.cloud </groupld>
<artifactld> spring-cloud-starter-netflix-ribbon </artifactld>
</dependency>
- 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
Ribbon核心组件IRule:
- IRule:根据特定算法从服务列表中选取一个要访问的服务 ①com.netflix.loadbalancer.RoundRobinRule:轮询 ②com.netflix.loadbalancer.RandomRule:随机 ③com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试 ④WeightedResponseTimeRule :对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 ⑤BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 ⑥AvailabilityFilteringRule :先过滤掉故障实例,再选择并发较小的实例 ⑦ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
- 如何替换: ①官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。 ②代码:
----------------配置类----------------
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机
}
}
----------------主启动类----------------
@EnableEurekaClient
@SpringBootApplication
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
Ribbon负载均衡算法:
负载均衡算法: rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD- PAYMENT-SERVICE");
如:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
当总请求数为1时: 1 %2 =1对应下标位置为1,则获得服务地址为1 27.0.0.1:8001
当总请求数位2时: 2 % 2 =0对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 %2 =1对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推.....
- 自己试着写一个本地负载均衡器试试:
----------------负债均衡接口----------------
public interface LoadBalancer {
//收集服务器总共有多少台能够提供服务的机器,并放到list里面
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
----------------负债均衡实现类----------------
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
//坐标
private final int getAndIncrement(){
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
}while (!this.atomicInteger.compareAndSet(current,next)); //第一个参数是期望值,第二个参数是修改值是
System.out.println("*******第几次访问,次数next: "+next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) { //得到机器的列表
int index = getAndIncrement() % serviceInstances.size(); //得到服务器的下标位置
return serviceInstances.get(index);
}
}
OpenFeign
简介:
- 是什么: ①Feign是一个声明式WebService客户端。 使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 ②Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
- Feign能干什么:
①Feign旨在使编写Java Http客户端变得更容易。
②
前面在使用Ribbon+ RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下 我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。 ③Feign集成了Ribbon:利用Ribbon维护了Payment的服务列表信息, 并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
Feign和OpenFeign两者区别:
- Feign: ①Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon, 用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 ②pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
- OpenFeign: ①OpenFeign是Spring Cloud在Feign的基础 上支持了SpringMVC的注解,如@RequesMapping等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 ②pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
OpenFeign使用步骤:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 主启动类:@EnableFeignClients
- 微服务调用接口+@FeignClient
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
Feign自带负载均衡配置项
OpenFeign超时控制:
- 默认Feign客户端只等待一秒钟, 但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
- OpenFeign默认支持Ribbon:
- YML文件里需要开启OpenFeign客户端超时控制:
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
OpenFeign日志打印功能:
- Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是对Feign接口的调用情况进行监控和输出。
- 日志级别: ①NONE:默认的,不显示任何日志。 ②BASIC:仅记录请求方法、URL、 响应状态码及执行时间。 ③HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息。 ④FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
- 配置日志bean:
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
- YML文件里需要开启日志的Feign客户端:
logging:
level:
com.atguigu.springcloud.service.PaymentFeignService: debug
Hystrix
分布式系统面临的问题:
- 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
- 服务雪崩: ①多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。 ②如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。 ③对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。 ④所以通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
Hystrix是什么:
- 是什么: ①Hystrix是一个用于处理分布式系统的延迟和容错的开源库, 在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 ②"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝) ,向调用方返回-个符合预期的、可处理的备选响应(FallBack) ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
- 能干嘛: ①服务降级 ②服务熔断 ③接近实时的监控
配置:
- pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
服务降级:
- 降级配置:@HystrixCommand
- 主启动类激活:添加注解@EnableCircuitBreaker或者@EnableHystrix。
①
这两个注解都是激活hystrix的功能,我们根据上面代码得出来结论,只需要在服务启动类加入@EnableHystrix注解即可,无须增加@EnableCircuitBreaker注解,本身@EnableHystrix注解已经涵盖了EnableCircuitBreaker的功能。 - 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback。
//失败
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") //3秒钟以内就是正常的业务逻辑
})
public String paymentInfo_TimeOut(Integer id){
// int timeNumber = 5;
int age = 10/0;
// try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
//return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)";
}
//兜底方法
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙, 请稍候再试 ,id: "+id+"\t"+"哭了哇呜";
}
每个业务方法对应一个兜底的方法,代码膨胀,可以把统一和自定义的分开。①没有特别指明就使用统一的。
@DefaultProperties (defaultFallback = "payment_ Global_ FallbackMethod" )
public class PaymentControllerFeign{
@Autowired
private PaymentService paymentService;
@GetMapping(" /consumer/payment/{id}" )
public String paymentInfo(@PathVariable("id") Integer id)
{
return paymentService . getPaymentInfo(id);
}
@GetMapping("/consumer/ paymentTimeOuE/{id}" )
@HystrixCommand
//@HystrixCommand(fallbackMethod = "paymentTimeOutFal lbackMethod")
public String payment TimeOut (@PathVariable("id") Integer id)
{
return paymentService . paymentTime0ut(id);
//throw new Runt imeException(*"********Exception 2001 ");
}
public String paymentTime0utF allbackMethod(@PathVariable("id") Integer id)
{
return "对方系统繁忙或者已经down机,请10秒钟后再次尝试";
}
}
- 但是这样还是和业务逻辑混在一起。
①
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。
------------------接口------------------
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
------------------实现类------------------
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}
---------------application.yml---------------
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
服务熔断:
- 代码:
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}
-
大神结论:
-
熔断类型: ①熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态。 ②熔断关闭:熔断关闭不会对服务进行熔断。 ③熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。
-
断路器在什么情况下开始起作用: ①涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。 <1>快照时间窗:断路器确定是否打开需要统计- 些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。 <2>请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。 <3> 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
-
断路器开启或者关闭的条件: ①当满足一定阀值的时候(默认10秒内超过20个请求次数) ②当失败率达到一定的时候(默认10秒内超过50%请求失败) ③到达以上阀值,断路器将会开启 ④当开启的时候,所有请求都不会进行转发 ⑤一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5。
-
断路器打开之后: ①再有请求调用的时候,将不会调用主逻辑,而是直接调用降级allback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。 ②原来的主逻辑要如何恢复呢? <1>对于这一问题,hystrix也为我们实现了自动恢复功能。 <2>当断路器打开,对主逻辑进行熔断之后, hystrix会启动- 个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑, <3>当休眠时间窗到期,断路器将进入半开状态,释放-次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合, <4>主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
-
All配置:
Hystrix重要概念:
- 服务降级: ①服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback ②哪些情况会触发降级: <1>程序运行异常 <2>超时 <3>服务熔断触发服务降级 <4>线程池/信号量打满也会导致服务降级
- 服务熔断:
①类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
②
就是保险丝:服务的降级->进而熔断->恢复调用链路 - 服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
hystrix工作流程:
服务监控hystrixDashboard:
- 除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,拟统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合, 对监控内容转化成可视化界面。
- 仪表盘工程:
--------------pom.xml-----------------
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
--------------主启动类加@EnableHystrixDashboard-----------------
- 被监控工程:
所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
--------------pom.xml-----------------
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
新版本Hystrix需要在主启动类中指定监控路径
--------------主启动类加-----------------
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
- 仪表盘地址:localhost:端口号/hystrix
- 如何看仪表盘:
Gateway
Gateway概述:
- Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5, Spring Boot 2和Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一 些强大的过滤器功能,例如:熔断、限流、重试等。
- SpringCloud Gateway是Spring Cloud的一个全新项目,纡Spring 5.0+ Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一 种简单有效的统一 的API路由管理方式。
- SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul, 在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上 最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能, SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
- Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
Gateway简介:
- 是什么: ①Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一 直跳票, SpringCloud最后自2研发了一个网关替代Zuul,那就是SpringCloud Gateway-句话: gateway是原zuul1 .x版的替代。
- 能干嘛: ①反向代理 ②鉴权 ③流量控制 ④熔断 ⑤日志监控
- 微服务架构中网关在哪里
有了Zuul了怎么又出来了gateway:
-
我们为什么选择Gatway? ①neflix不太靠谱,zuul2.0一直跳票,迟迟不发布: <1>一方面因为Zuul1.0已经进入 了维护阶段,而且Gateway是SpringCloud团队研发的, 儿子产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。 <2>Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布 了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何? <3>多方面综合考虑Gateway是很理想的网关选择。 ②Spring Cloud Gateway具有如下特性: <1>基于Spring Framework 5, Project Relactor和Spring Boot 2.0进行构建; <2>动态路由:能够匹配任何请求属性; <3>可以对路由指定Predicate (断言)和Filter (过滤器) ; <4>集成Hystrix的断路器功能; <5>集成Spring Cloud服务发现功能; <6>易于编写的Predicate (断言)和Filter (过滤器) ; <7>请求限流功能; <8>支持路径重写。 ③Spring Cloud Gateway与Zuul的区别: <1>Zuul 1.x,是一个基于阻塞|/ 0的API Gateway <2>Zuul 1.x基于Servlet 2. 5使用阻塞架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次|/ O操作都是从工作线程中选择一 个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。 <3> Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较Zuul 1.x有较大提升在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS (每秒请求数)是Zuul的1.6倍。 <4>Spring Cloud Gateway建立在Spring Framework <5>Project Reactor和Spring Boot2之上,使用非阻塞API。 <6>Sprina Cloud Gatewav还支持WebSocket. 并目与Sprina紧宓集成拥有更好的开发体验
-
Zuul1.x模型: ①Springcloud中所集成的Zuu|版本,采用的是Tomcat容器,使用的是传统的Servlet I0处理模型。Servlet的生命周期?servlet由servlet container进行生命周期管理。 <1>container启动时构造servlet对象并调用servlet init()进行初始化; <2>container运行时接受请求,并为每个请求分配一个线程 (一 般从线程池中获取空闲线程) 然后调用service()。 <3>container关闭时调用servlet destory0销毁servlet;
②上述模式的缺点: <1>servlet是一 个简单的网络IO模型,当请求进入servlet container时, servlet container就会为其绑定一 个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一 个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。 <2>所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet (DispatcherServlet) 并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端。
-
GateWay模型: ①传统的Web框架,比如说: struts2, springmvc等 都是基于Servlet API与Servlet容器基础之上运行的。但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一 个 典型非阻塞异步的框架,它的核心是基于Reactor的相关 API实现的。相对于传统的web框架来说,它可以运行在诸如Netty, Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8) ②Spring WebFlux是Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
三大核心概念:
- Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
- Predicate(断言):参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
- Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
- 总体:
Gateway工作流程:
- 官网总结: ①客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。 ②Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前( "pre" )或之后( "post" )执行业务逻辑。 ③Filter在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
- 核心逻辑:
路由转发+执行过滤器链。
入门配置:
- pom:
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- application.yml:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
配置完后即可通过9527端口访问映射的路径。- 此外也可以通过编码来配置:
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_rote_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
通过微服务名实现动态路由:
- 默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
- 注意: ①需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 ②lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri。
- application.yml:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
Predicate的使用:
- Predicate简介: ①Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。 多个Route Predicate工厂 可以进行组合 ②Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给 Route。 Spring Cloud Gateway包含许多内置的Route Predicate Factories。 ③所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
- 常用的Route Predicate: ①After Route Predicate ②Before Route Predicate ③Between Route Predicate ④Cookie Route Predicate ⑤Header Route Predicate ⑥Host Route Predicate ⑦Method Route Predicate ⑧Path Route Predicate ⑨Query Route Predicate
- 所有的Predicate示例:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
Filter的使用:
- 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。 Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
- Spring Cloud Gateway的Filter划分: ①生命周期,Only Two: <1>pre:在业务逻辑之前 <2>post:在业务逻辑之后 ②种类,Only Two: <1>GatewayFilter:单一 <2>GlobalFilter:全局
- 自定义过滤器: ①两个主要接口介绍:GlobalFilter ,Ordered
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
Config
分布式系统面临的配置问题:
- 微服务意味着要将单体应用中的业务拆分成一个个子服务, 每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
- SpringCloud提供了ConfigServer来解决这个问题, 我们每个微服务自己带着-个application.yml,上百个配置文件的管理。
Config简介:
- 是什么:
①SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
- 怎么玩: ①SpringCloud Config分为服务端和客户端两部分。 <1>服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。 <2>客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
- 能干嘛: ①集中管理配置文件 ②不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release ③运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息 ④当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置 ⑤将配置信息以REST接口的形式暴露:post、curl访问刷新均可....
- 与Github整合配置:
由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持svn和本地文件,但最推荐的还是Git,而且使用的是http/https访问的形式)
Config服务端配置:
- 用自己的账号在Github上新建一个Repository,并在Repository中创建配置文件,
命名规则为{application-name}-{profiles}.yml。 - pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
- application.yml: ①uri使用SSL好一点 ②使用SSL地址的话要加多一个配置:skip-ssl-validation: true
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: 填写你自己的github路径
search-paths:
- springcloud-config
label: master
- 主启动类上加@EnableConfigServer
- http读取配置项: ①/{label}/{application-name}-{profile}.yml(最推荐使用这种方式) ②/{application-name}-{profiles}.yml ③/{label}-{application-name}-{profiles}.ym|
- 详解: ①label:分支(branch) ②name :服务名 ③profiles:环境(dev/test/prod)
Config客户端配置:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
bootstrap.yml与applicaiton.yml:①applicaiton. yml是用户级的资源配置项,bootstrap.yml是系统级的,优先级更加高。 ②Spring Cloud会创建一个"Bootstrap Context",作为Spring应用的Application Context的父上下文。初始化的时候,BootstrapContext'负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment'。 ③Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context'和Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。 ④要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.ym是比application.yml先加载的。bootstrap.yml优先级高于application.yml。- bootstrap.yml:
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master
name: config
profile: dev
uri: http://localhost:3344
- 业务类:
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
- Linux运维修改GitHub上的配置文件内容做调整,发现ConfigServer配置中心立刻响应,发现ConfigClient客户端没有任何响应,ConfigClient没有变化除非自己重启或者重新加载。
- 问题随时而来,分布式配置的动态刷新。
Config客户端之动态刷新:
- pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- bootstrap.yml:
management:
endpoints:
web:
exposure:
include: "*"
- 业务类:
@RefreshScope
@RestController
public class ConfigClientController {
}
- 需要运维人员发送Post请求刷新到最新配置内容:
curl -X POST "http://localhost:3355/actuator/refresh"
Bus
Bus简介:
- Bus可以实现分布式自动刷新配置功能。
- Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新。
Bus概述:
- 是什么: ①Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新。 ②Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。 Spring Clud Bus目前支持RabbitMQ和Kafka。
- 能干嘛:
①Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
- 什么是总线: ①在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题, 并让系统中所有微服务实例都连接上来。由于该注题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播-些需要让其他连接在该主题 上的实例都知道的消息。
- 基本原理: ①ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。 当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
设计思想:
- 设计方案: ①利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置。 ②利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置(更加推荐)。
- 原因: ①打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新职责。 ②破坏了微服务各节点的对等性。 ③有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改。
Config客户端:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- bootstrap.yml:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
Config服务端:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- application.ym:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
动态刷新:
- 发送Post请求:
curl -X POST "http://localhost:3344/actuator/bus-refresh",一次修改,广播通知,处处生效。 - 不想全部通知,只想定点通知: ①公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
Stream
Stream概述:
- 什么是SpringCloudStream:
①官方定义Spring Cloud Stream是一 个构建消息驱动微服务的框架。
②应用程序通过inputs或者outputs与Spring Cloud Stream中binder对象交互。通过我们配置来binding(绑定) ,而Spring Cloud Stream的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。
③通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream为些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
④
目前仅支持RabbitMQ、 Kafka。⑤一句话:屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型。
设计思想:
- 标准MQ: ①Message:生产者/消费者之间靠消息媒介传递信息内容。 ②消息通道MessageChannel:消息必须走特定的通道。 ③消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅:消息通道里的消息如何被消费呢,谁负责收发处理。
- 为什么用Cloud Stream: ①比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange, kafka有Topic和Partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中-种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的, 一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了-种解耦合的方式。
- stream凭什么可以统一底层差异:
①在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
②
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。 - Binder:
①在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,于各消息中间件构建的初衷不同,它们的实现细节_上会有较大的差异性.通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一 步封装可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。
②通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
③其中INPUT对应于消费者,OUTPUT对应于生产者。
- Stream中的消息通信方式遵循了发布-订阅模式,通过Topic主题进行广播。 ①在RabbitMQ就是Exchange。 ②在kafka中就是Topic。
- Spring Cloud Stream标准流程套路:
①Binder:很方便的连接中间件,屏蔽差异
②Channel:通道是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置。
③Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
- 编码API和常用注解:
消息驱动之生产者:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- application.yml:
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
- 发送消息接口:
public interface IMessageProvider
{
public String send();
}
- 发送消息接口实现类:
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider
{
@Resource
private MessageChannel output; // 消息发送管道
@Override
public String send()
{
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: "+serial);
return null;
}
}
消息驱动之消费者:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- application.yml:
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
- 业务类:
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController
{
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message)
{
System.out.println("消费者2号,----->接受到的消息: "+message.getPayload()+"\t port: "+serverPort);
}
}
分组消费与持久化:
- 运行后两个问题: ①有重复消费问题 ②消息持久化问题
- 实际生产场景:
①比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中的消息分组来解决。
②注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以全面消费的(重复消费)。
③同一组内会发生竞争关系,只有其中一个可以消费。
原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。- 修改application.yml:
group: atguiguA
同样分配了分组后也可进行持久化。
Sleuth
Sleuth概述:
- 为什么: ①在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
- 是什么: ①Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案。 ②在分布式系统中提供追踪解决方案并且兼容支持了zipkin。
搭建zipkin:
- 下载:SpringCloud从F版起已不需要自己构建Zipkin server了,只需要调用jar包即可。
- 运行:java -jar zipkin-server-2.12.9-exec.jar。
- 运行控制台:http://localhost:9411/zipkin/。
- 完整的调用链路:表示一请求链路,一条链路通过TraceId唯一标识, Span标识发起的请求信息,各span通过parent id关联起来。
- 名词解释: ①Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识。 ②span:表示调用链路来源,通俗的理解span就是一次请求信息。
服务提供者与消费者:
- pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- application.yml:
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
- 消费者调用提供者的接口。
zipkin控制台:
- 打开浏览器访问:http:localhost:9411
- 会出现以下界面: