OfferCampus前期构建简单介绍: 搭建完整的模板Spring Cloud项目
内容管理
基于SpringCloud搭建模板项目
Cfeng在构建OfferCampus项目时,最开始选用Dubbo搭建该微服务项目,最终落入单体陷阱: 微服务之间的依赖过多,形成了一个分崩离析的单体项目,效果甚至不如单体项目; 不符合微服务项目的设计理念,因此选用Spring Cloud完整的生态进行微服务项目的构建
这里只是会简单的提及各个组件的功能和相关的使用,搭建一个微服务项目的模板供以后的服务搭建使用【 当然每个微服务都会采用Middleware进行性能提升】
分布式系统对应者微服务,分布式系统是建立在网络上的软件系统(比如部署Redis节点、RabbitMQ节点,部署数据库节点)而数据的传输就需要依赖网络(网络IO Netty框架为典型代表)
分布式微服务
集群: 将几台机器统一部署统一应用程序,实现统一业务,组成集群【比如redis集群、rabbitMQ集群、Tomcat集群) 集群可以是分布式的,也可以不是分布式的(redis的哨兵一样一台机器上开多个端口运行不同的哨兵是一样的)
随着大数据量和三高要求,现在的应用大多会采用分布式架构,采用微服务的思想进行应用的开发
RPC
: remote process call 进程间通信方式之一,通常是跨机器的(通过网络)典型的RPC框架就是Dubbo — 非常纯粹的进行服务的远程调用
服务之间的 相互调用除了以Dubbo为典型的RPC(Netty + 自定义序列化); 还有就是通过RestAPI的方式; SpringCloud主要就是使用Rest方式进行服务之间的交互, HTTP + JSON
在具体代码中,就是使用spring提供的访问Rest服务的客户端模板工具RestTemplate
直接调用另外一个服务controller的响应结果,需要注意服务Provider的接口方法需要@RequestBody【JSON】
三个参数为 url, rquestMap,ResponseBean.class 也就是Rest请求地址、请求参数和Http响应转化为的对象类型
restTemplate.postForObject
resetTemplate.getForObject
高并发
用户访问响应慢可以从3个方面思考 ---- 网络问题(带宽低、没有网络)、应用问题(Tomcat的并发有限,代码中有耗时IO同步)、数据库问题(多线程同时操作)
这是多次说过的问题,就是一定时间内承受巨大的流量和请求。这里主要谈论一下Tomcat服务器的高并发:
Tomcat默认采用的BIO通信,也就是阻塞式,需要等待IO完成进行下一步计划,并且不能多路复用,只能一个线程对应一个请求,因此: 并发量受线程数限制
使用NIO非阻塞方式也可以,可以提升性能,并发能力更多高,不需要那么多线程,但是存在问题【如果逻辑复杂,并发能力会下降】
SpringBoot中Tomcat服务器默认配置的最大进程数量200,但是之前分享过如果10000个进程同时访问99%以上的线程都是访问失效的; 所以当并发量超过300以上就要考虑服务器Tomcat集群
Tomcat具体的并发量和服务器的硬件有关系(CPU越多性能越高,分配给JVM的内存越多性能越好,但是GC垃圾回收有负担)
- 操作系统本身对于进程的线程数量就有限制: Windows的每个进程中的线程数量不超过2000, Linux中每一个进程中的线程数量不超过1000
- 在Java中每开启一个线程就需要使用MB左右的JVM空间作为线程栈
tomcat:
threads:
max: 200 //最大的并发数量
min-spare: 10 //初始化时创建的进程数量,默认为10
accept-count: 100 //当所有的可以使用的处理请求的线程都被占用时,可以放入处理队列排队的请求数量,默认100,超过100的都不处理
高并发性能测试通常的标准就是:
- 响应时间RT response time: 请求作出响应的时间,一个http返回响应的时间
- 吞吐量: 系统单位时间内处理的请求的数量
- QPS: query/request per second) 、 TPS(transaction per second)每秒查询、事务数量
- 并发用户数量
高可用
分布式讲究CAP (一致性,可用性、分区容错性),zookeeper为典型CP模型,数据一致性好,而Redis主从集群为AP模型,可用性更好,一致性可能不好
高可用的方式:
- 服务集群部署
- 数据库主从 + 双机热备
主-备方式Active-standby: 一台服务器处于某种业务激活状态active,另外一台服务器就处于该业务的备用状态standby
双主机方式Active-Active: 两种不同业务分别在两台服务器上互为主备状态 【互相备份】
高可用还可以应用弹性云 Elastic Compute Service : 动态扩容,压榨服务器的闲时能力 【比如云计算的物理设备服务器在高峰期就多部署几台,而平时就不需要部署很多】
负载均衡
负载均衡针对的是集群部署,当服务集群部署时,动态将请求委派给比较空闲的服务器
负载均衡的策略: 轮询Round Robin; 加权轮询 Weight Round Robin、 随机Random、 哈希Hash
当然还有最小的连接数量LC,最短的响应时间LRT
API网关
API网关作为系统的后端总入口,承载着所有服务的组合路由转换等工作
同时会把安全、限流、缓存、日志、监控、重试、熔断
都会放在API网关做
服务跟踪
微服务就存在服务的调用分配,而服务跟踪就是追踪服务的调用链,记录整个系统的请求过程【请求的响应时间、链中哪些服务属于慢服务 — 需要改善,不然可能服务雪崩】
服务雪崩 — 服务复杂调用,一个不可用=>大面积不可用
缓存雪崩指的是大量的热Key同时expire导致数据库瞬时大流量造成相关严重问题。
服务雪崩则是 因为服务之间复杂调用,一个服务不可用,导致大量的服务都不可用, 导致整个系统受影响
比如这里就是一个应用系统由多个微服务组成,每个服务独立演化,如果扇出链路某个微服务响应时间过长,不可用,那么A调用就需要更多的系统资源,引起系统崩溃、雪崩
限流 – 限制某个服务调用频率(降级方式)
限流之前提过,单机应用可以使用令牌桶算法,限制某个服务的访问的频率,比如访问该服务 每秒只允许10个用户访问 【可以防止Ddos洪水攻击】
熔断 – 服务频繁超时,直接短路,快速返回mock(降级方式)
微服务的熔断降级机制就是为了应对服务雪崩,在微服务生态中熔断器的作用就是 电路中的保险丝
(CircuitBreaker)
当扇出链路中某个微服务响应时间长或者不可用, 那么就会进行服务的降级,熔断对于该节点的调用,快速返回mock的响应信息, 恢复后再重新调用链路, SpringCloud生态中熔断器使用Hystrix(后续介绍)
通常的关闭状态请求都是放行的,熔断器打开之后,短路,所有的请求不放行,直接返回mock
降级 — 服务响应慢或不可用,下游主动放弃或者上游操作
降级 主要就是为了解决资源不足但是访问量增加的矛盾, 在资源有限情况下,为了应对高并发,那么就舍弃一些不重要的服务(针对mirco),保证核心业务
服务流量增加时,因为整体的CPU资源有限【JVM的内存资源 、CPU资源】,如果某些业务占据了资源(线程内存),如果CPU慢就响应慢、影响核心业务,所以就将非核心的业务降级
服务降级有很多方式,包括开关降级(如下)、限流降级、熔断降级, 服务熔断只是服务降级的一种处理方式
降级的方式很easy, 在配置中心设置一个降级开关,需要降级的时候,更改这个配置(操作开关)
熔断和降级都是为了保证高并发情况下系统的正常运行,保证可用性
1. 触发方式: 熔断是配置的策略自动触发, 而降级需要人工干预配置中心
2. 触发原因: 熔断是扇出链路节点故障引起,而降级则是从整体的负荷访问量考虑
3. 管理目标: 熔断是框架处理如Hystyix,而降级则是考虑业务层级,一般都是从最外围开始降级
SpringCloud (Netifix)
SpringCloud这里采用的是其优秀子项目SpringCloud Alibaba,当然原生的Netflix也会提一下(二者主要就是相关的组件选择上不同)
spirngCloud生态由很多组件构成,相关的breaker、config、注册中心、各种中间件综合为微服务保驾护航
在项目中,使用SpringCloud需要和SpringBoot的版本对应,因为升级伴随着class的CRUD,具体情况查看 https://start.spring.io/actuator/info
cfeng使用的SpringBoot版本高到了2.7.2…所以cloud只能选择用尽可能新的,幸好cfeng导入依赖时都是导入的时间相近的,目前几乎没有发生过版本冲突问题,但是每次总会碰到很多过时的问题…特别是Security那一次探索花费时间较长
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
微服务建议分离彻底,也就是不要因为都用了一个实体就建立一个公共的模块进行引用,因为可能其中一个需要修改而另外一个不需要
当然简单的小型项目you want 使用通用api模块也可以,但是还是分离的好,虽然产生了冗余【实际项目中不同的服务应该很少出现Class相同】
下面简易带过原生SpringCloud各组件的使用(使用方便)
Eureka注册中心 – 服务注册发现 (心跳)
之前Dubbo常用的注册中心为Zookeeper,而SpringCloud原生注册中心为Euraka,实现服务调用、负载均衡、容错、注册发现,Eureka是CS结构,Eureka Server为注册中心,Client需要引入到每一个服务,(所以使用Euraka注册中心需要手动再创建一个Server服务)
Eureka Client 是java客户端,简化和Server的交互,其内部具备一个使用轮询round-robin负载算法的负载均衡器
, 某个任务启动后,server会发送心跳,默认T = 30s,如果某个应用节点长时间未回复,则Server会将该节点从服务注册表移除
Eureka注册中心项目
需要引入spring-cloud-starter-netflix-eureka-server依赖
当然,需要在父pom中引入cloud的依赖: org.springframework.cloud: spring-cloud-dependencies ; 版本就对应boot的版本即可
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
< dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编写配置文件
server:
port: 9014
servlet:
context-path: /eureka
#eureka配置 主机名, client的配置
eureka:
instance:
hostname: localhost
#客户端client配置
client:
register-with-eureka: false # server项目不需要注册,所以false
fetchRegistry: false #server不需要注册
service-url: #server服务地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类上面加上注解**@EnableEurekaServer**,表示这个项目为Eureka Server
@SpringBootAppplication
@EnableEurekaServer
public class EurekaServerApp {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class,args);
}
}
这样只需要经过【改pom、编写yml、编写主启动类】,就可以完成Eureka Server的搭建
其余需要注册项目Eureka-client
其余需要注册的项目都是Eureka-client,而client服务除了自己本身需要完成的功能的jar,还需要引入client依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置文件除基本功能外,配置Eureka-client的config
server:
......
#将当前微服务注册到注册中心Eureka
eureka:
client:
register-with-eureka: true # 使用eureka注册中心
fetchRegistry: true #注册到Eureka,是否从Eureka抓取已经注册的信息,集群为了利用ribbon继续负载均衡
service-url: #server服务地址
defaultZone: http://localhost:9014/eureka #集群版本用, 分隔书写多个注册中心节点
同时主启动类上面也需要加上注解**@EnableEurekaClient**, 表示当前微服务为一个Eureka-Client(CS架构)
@SpringBootAppplication
@EnableEurekaClient
public class XXXApplication {
public static void main(String[] args) {
SpringApplication.run(XXXApplication.class,args);
}
}
这样微服务项目就使用了Eureka作为注册中心(操作easy),当然zookeeper操作也很easy; 只是需要相关服务编写时加入相关的yml配置和pom依赖同时启动类加一个注解就可以了 【直接访问server端可以查看注册表】
启动的时候先启动Server,再启动Client,避免找不到Server
Ribbon 负载均衡 (enabled = false)
ribbon作为进程内LB已经很少单独使用了【spring.cloud.loadbalancer.ribbon.enabled = false],一般就直接使用OpenFeign的时候会内置Ribbon】下面简单介绍其使用
Ribbon能够实现LB负载均衡,将用户的请求平均分配到多个服务器上面,避免某台服务器的访问量过大,造成宕机等,Tomcat等容器是扛不住的,从而达到高可用, 常用的负载均衡的方案还有Nginx
Ribbon本地负载均衡 Nginx服务端负载均衡
- Ribbon是本地负载均衡,调用微服务接口时,在注册中心上获取注册信息服务列表并缓存到JVM本地, 之后本地实现RPC的远程调用 是一种进程内LB 【LB集成到消费方,从注册中心获取地址,进行选择; Ribbon只是一个类库,在消费方进程中,通过Ribbon选择恰当的服务方】
- Nginx是服务器端负载均衡(单独的Nginx服务器):访问时,客户端所有的请求交给Nginx,Nginx再转发均衡,LB在服务端完成, 是一种集中式LB 【独立的LB设施在provider和consumer之间】
Spring Cloud Ribbon是 基于Netfix Ribbon实现的客户端负载均衡工具, 主要就是提供软件的负载均衡算法和服务调用
Ribbon客户端组件提供完善的配置: 连接超时,重试, 使用Ribbon也很简单,直接在Load Balancer(LB)后列出相关的服务主机地址, Ribbon就可以基于某种规则(轮询round robin、随机连接)去进行服务的分配,未来的替换方案为 LoadBalancer
Ribbon 也就是 RestTemplate调用 + 负载均衡LB ; 软负载均衡客户端组件(Ribbon典型的实例就是与Eureka注册中心结合)
Ribbon的工作流程:
- 选择EurekaServer,优先选择区域内负载较少的Server
- 根据用户指定的策略(hash,random,round robin…),在server获取到的注册列表中选择一个地址
Ribbon负载rule
Ribbon的核心组件就是Irule : 根据特定算法从服务列表中选取一个要访问的服务
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
其下有一个抽象的实现类AbstactLoadBalancerRule,该抽象类就有很多的具体的负载均衡算法的子类: 比如RoundRobinRule(WeightedResponseTimeRule)、RandomRule、TRetryRule、BestAvailableRule、AvalibalityFilteringRule
Retry策略: 先按照轮询方式获得服务,如果获取服务失败,则等待重试
WeightedResponseRule: 轮询策略的空战,响应速度越快的实例比重越大
BestAvailadbleRule: 先过滤调由于多次访问故障处于熔断器跳闸的服务,选择一个并发量最小的服务
AvailabilityFilteringRule: 先过滤故障的实例,再选择并发量小的实例
ZoneAvoidanceRule: 默认,复合判断server所在区域的性能和serer的可用性选择服务器
默认使用的就是RoundRobin轮询策略
如果需要替换其他的策略,再自定义配置类中实现;Ribbon配置类不能放在@ComponentScan扫描的当前包以及子包下,否则被所有的Ribbon-Client共享
轮询Round Robin负载均衡的算法: rest接口第几次请求数量 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启rest接口从1开始count
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-XXX-SERVICE") //在项目中就是ribbonClient注解配置的Service的name, 获取所有的实例列表
eg:
list[0] instances = localhost:8082
list[1] instances = localhost:8083
8082 + 8083组合成为集群,2台机器,按照轮询算法:
当前request总数1: 1 % 2 = 1; 对应8083主机
..... 2: 2 % 2 = 0; 对应第0台: 8082主机
..... 3: 3 % 2 = 1: 对应第1台: 8083主机
.......
这样就可以保证请求均衡到达每一个服务器
进程内LB使用
Ribbon作为一个典型的进程内LB,其依赖为
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
不需要手动引入,其包含在Eureka-client中; 所以ribbon和eureka配合最well
如果需要修改rule,那么创建Ribbon配置类修改, 如果为定制化那么就不能放在启动类所在包@ComponentScan
@Configuration
public class RibbonConfig {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机
}
}
同时也需要在主启动类加上注解**@RibbonClient(name = “CLOUD-XXX-SERVICE”,configuration = RibbonConfig.class) **
@EnableEurekaClient
@SpringBootApplication
@RibbonClient(name = "CLOUD-XXX-SERVICE",configuration = MySelfRule.class)
public class XXXApplication {
public static void main(String[] args) {
SpringApplication.run(XXXApplication.class,args);
}
}
OpenFeign 服务接口调用 【内置Ribbon】
OpenFeign 是原生SpringCloud体系中实现服务接口调用的组件; 是一个声明式的web服务客户端, 编写web客户端easy,只要创建接口加上注解,类似Dubbo; SpringCloud封装Feign,支持SpirngMVC标准注解和HttpMessageConverters; Feign可以和Eureka和Ribbon组合使用支持负载均衡
Feign可以让编写Java的http客户端更容易,使用Ribbon + RestTemplate可以方便进行服务的调用,但是实际开发中,服务的依赖调用不只一处,Feign进一步封装
Feign的使用和mybatis的动态代理类似,直接在微服务接口上面加上Feign的注解即可,简化使用Template的方式
Feign集成了Ribbon,利用Ribbon维护服务列表信息,轮询实现LB,但是和Ribbon不同,使用Feign就只需要定义服务绑定接口并且是声明式的方法调用服务
OpenFeign就是在Feign的基础上添加了对于SpringMVC的支持,可以通过动态代理的方式产生实现类,LB调用其他的服务
OpenFeign使用
使用OpenFign非常的简单,就是动态代理的方式,只需要一个**@FeignClient**就可以搞定
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</arifactId>
</dependency>
涉及到服务的调用,那么在提交注册中心的时候,就需要标识当前服务,【dubbo中也需要】,在spring生态中,直接使用原生提供的spring.application.name就可以标注当前的服务名称
server:
port: 8081
spring:
application:
name: cloud-XXX-feign-XXX
#注册中心eureka的配置
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:9014/eureka
和之前的Euraka类似,需要在主启动类加上**@EnableFeignClients **注解表明当前服务开启Feign远程调用
@SpringBootApplication
@EnableEurekaClient //当前微服务作为Eureka的client注册到Eureka
@EnableFeignClients //开启Feign远程调用
public class XXXApplication {
public static void main(String[] args) {
SpringApplication.run(XXXApplication.class,args);
}
}
而之前进行服务的调用需要引入RestTemplate对象进行XXObject(),显得非常的笨重和不易维护,使用Feign就直接定义一个server接口统一引入所有的服务; 使用==@Feign使用value属性指定注册到Eureka的服务的name==,就会直接远程调用该服务的相关的处理器
下面eg: 【在消费方定义一个service接口调用其他的服务】
@FeignClient(value = "provider01-depart")//注解作用:声明当前为Feign客户端接口,value就指定需要调用的微服务的name
@RequestMapping("/provider/depart")// 参数为要调用的提供者相应的uri,抽取所有方法的公有uri地址
public interface DepartService {//更加符合面向接口api调用习惯
@PostMapping("/save")
boolean saveDepart(@RequestBody Depart depart);
@DeleteMapping("/del/{id}")
boolean removeDepartById(@PathVariable("id") int id);
@PutMapping("/update")
boolean modifyDepart(@RequestBody Depart depart);
@GetMapping("/get/{id}")
Depart getDepartById(@PathVariable("id") int id);
@GetMapping("/list")
List<Depart> listAllDeparts();
}
其实和之前的手动的RestTemplate是一样的,之前的Get和Post体现在@GetMapping上面,之前的uri体现在RequestMapping + XX Mapping上, 之前的requestMap 体现在 方法的参数上
值得注意的是,这里的所有的处理器方法和provider中的controller中的方法的声明是完全相同的,这里的@FeignClient 和之前的@DubboReference一样,添加之后代表远程调用,不会本地查找, 这里就是动态代理
OpenFeign超时
OpenFeign就涉及到服务的调用,那么下游的服务如果超时, OpenFeign的默认处理 为等待1s, 超过后报错 , 为了避免下游服务的执行时间长导致直接报错,需要设置Ribbon的超时时间控制(集成了的)
OpenFeign的超时时间 = ribbon超时时间 + hystrix的超时时间
总重试次数 = MaxAutoRetries + MaxAutoRetriesNextServer + (MaxAutoRetries * MaxAutoRetriesNextServer )
在重试期间,时间如果超过了hystrix的timeout,那么就会立刻熔断,fallback,因此要根据参数计算hystrix的超时时间,保证重试工作的顺利完成
hystrix的超时时间 = (1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeOut
当ribbon超时,但是hystrix还没有超时,就会重试,当OkToRetryOnAllOperations为false,只是对get请求重试, true才会对所有类型的请求重试,因为put或者post会修改服务器的数据,如果接口没有做幂等性处理,那么就可能出现问题,如果没有配置ribbon的重试次数,默认1次
默认情况下false
GET方式请求无论连接异常 还是读取异常都会重试
非GET请求,只有连接异常的时候,才会重试
设置超时时间的方式就是修改yml配置文件
#设置Feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
OkToRetryOnAllOperations: false #是否所有操作都重试
#hystrix的超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 9000
这里一般都是将ribbon的超时时间 < hystrix的超时时间 【 因为ribbon均衡需要重试MaxAutoRetries次】
Feign的重试机制和Feign的重试机制是有重入的,源码中默认就是关闭了Feign的重试机制的
public interface Retryer extends Cloneable {
Retryer NEVER_RETRY = new Retryer() { //NERVER_RETRY,默认关闭Feign的重试机制
.......
}
}
想要开启,那么单独在配置类中配置一个Retryer对象即可( 采用默认Default的重试5次的重试器即可)
@Bean
public Retryer feignRetryer() {
return new Retryer.Default();
}
OpenFeign日志
Feign除了代替RestTemplate进行服务调用之外,还可以进行日志的处理,也就是Feign的接口的调用情况的日志
日志的级别有NONE、BASIC、HEADERS、FULL
- NONE: 默认不显示日志
- BASIC: 记录请求方法、响应状态码和执行时间
- HEADERS: 基础上新增请求和响应的头信息
- FULL: HEADERS基础上新增 请求和响应的正文和元数据
Feign的日志实现依靠的是Logger.Level对象, 默认就是NONE, 如果需要修改级别,就在Feign的配置类中配置
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
同时需要在yml中配置开启日志,也就是logging的级别在@FeignClient标注的RPC接口类设置为debug
logging:
level:
xxxxx.XXXX.XXService: debug
开启日志之后就可以在日志中查看OpenFeign每一次调用的响应情况
Hystrix熔断器 – 服务熔断降级(熔断、监控)
一个比较复杂的分布式体系有很多依赖关系,一些微服务会出现不可避免的问题,为了避免服务雪崩(下游服务响应慢或者不可用,导致其上游服务崩溃,引发一些列崩溃导致系统崩溃); 对于高流量的项目,单一的后端依赖会导致所有服务器的所有资源在几秒钟内饱和,导致服务之间的延迟增加(响应等待)、备份队列,线程和其他资源也紧张,导致级联故障, 当一个模块某个实例失败后,该模块还在接受流量,那么大概率就会雪崩
比如JMeter发起20000个请求压死其中一个微服务,如果同时模拟消费者消费该服务,那么就会一直转圈卡死【Tomcat默认工作线程有限,所有的线程都被打满了,没有多余的线程来分解处理压力,Tomcat拥有一个accept-count,就是会有N个线程放入等待队列等待工作线程处理完成,每开启一个线程都需要占用CPU和内存,比如一个线程占用1MB,4G内存可以开2000个(只占一半,否则出问题),如果2000个线程跑起来,CPU就废了,因为应用的功能都需要使用CPU,比如计算,并且线程切换很耗时,会占用大量的CPU时间,导致CPU打满废掉】
Spring Cloud Hystrix 是一个用于处理分布式系统延迟和容错的组件,避免微服务调用失败(超时、异常)导致雪崩
熔断器就类似一个电路中的保险丝、开关,当某个服务单元发生故障后,通过熔断器的监控,向调用方返回一个复合预期的mock响应,避免长时间等待(重试)和抛出无法处理的异常,避免故障在分布式系统中的蔓延、雪崩
hystrix能够解决超时导致服务器变慢(转圈)以及出错(宕机、出错)
降级Fallback和熔断breaker、限流FlowLimit
- Fallback: 服务器忙,请稍后重试, 不让用户无意义等待, 降级触发的情况: 【程序运行异常、 超时自动降级、 服务熔断触发服务降级、 线程池/信号量打满触发降级、 手工开关降级】
- Breadker: 类似保险丝,当到达最大访问后,拒绝访问,拉闸限电,调用服务降级的方法返回友好提示 服务降级----> 熔断 —> 恢复; 请求不直接调用服务,而是通过线程池调用,没有空闲线程时,就直接返回mock
- FlowLimit: 高并发操作,严谨一窝蜂操作,有序放行请求,1s处理N个
熔断是降级的一种,是应对雪崩的链路保护机制,当下游服务出现问题,会降级该服务,熔断对于该服务的调用,快速返回信息,当检测到该节点服务响应正常,恢复链路
熔断的类型
- 熔断打开: 请求不调用该服务,直接fallback,内部设置是指MTTR平均故障处理时间
- 熔断关闭: cruit健康,正常访问
- 熔断半开: 部分请求根据规则调用该服务,请求成功且复合规则认为服务正常
- 关闭熔断: 没有熔断器, 卡死没办法
Hystrix会监控微服务的调用状态,当失败的调用达到一个阈值,默认5s20次调用失败,就会启用熔断机制,使用就是@HystrixCommand
可以使用@HystrixProperty进行配置相关属性,其中@HystrixProperty(name = “circuitBreaker.enabled”,value = “true”) 是开启短路器,持续监控cruit的健康状态,默认就是打开的
常见的所有的属性
hystrix.command.default和hystrix.threadpool.default中的default为默认CommandKey
Command Properties
Execution相关的属性的配置:
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
semaphore应该占整个容器(tomcat)的线程池的一小部分。
Fallback相关的属性
这些参数可以应用于Hystrix的THREAD和SEMAPHORE策略
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10
hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true
Circuit Breaker相关的属性
hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage
Metrics相关参数
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
Request Context 相关参数
hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true
Collapser Properties 相关参数
hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true
ThreadPool 相关参数
线程数默认值10适用于大部分情况(有时可以设置得更小),如果需要设置得更大,那有个基本得公式可以follow:
requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
每秒最大支撑的请求数 (99%平均响应时间 + 缓存值)
比如:每秒能处理1000个请求,99%的请求响应时间是60ms,那么公式是:
(0.060+0.012)
基本得原则时保持线程池尽可能小,他主要是为了释放压力,防止资源被阻塞。
当一切都是正常的时候,线程池一般仅会有1到2个线程激活来提供服务
hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默认10000
hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个buckets,默认10
最重要的三个参数:快照时间窗 请求总数阈值circuitBreaker.requestVolumeThreshold, 错误百分比阈值circuitBreaker.errorThresholdPercentage
- 快照时间窗: 熔断器统计各阈值的范围,默认最近10s
- 请求总数阈值: 在快照时间窗内,请求达到阈值才会熔断,如果不足20次,即便所有的请求都超时,或者失败,都不会进行降级,默认20
- 错误百分比阈值: 请求总数超过阈值,比如30次,统计这30次调用,如果超过一定百分比,就熔断打开,默认50%
circuitBreaker.sleepWindowInMilliseconds 为熔断打开后多久开始重试,默认5s; 【休眠时间窗,fallback成为临时主逻辑,到期后放行一个请求尝试验证下游服务是否正常】
当达到阈值,满足失败率之后,就会熔断打开,请求不转发,直接fallback,一段时间后,熔断器半开,会让其中一个请求转发,如果陈功,那么就关闭熔断器, 失败则继续等会重试、继续熔断打开
模拟故障
微服务A 作为consumer需要消费 微服务B,【OpenFeign远程调用】,开启服务A、B后,先JMeter 2W请求请求B,这个时候如果A调用B,会发现访问非常慢,一直转圈卡死
reason: 微服务B 因为应对巨大流量,首先Tomcat就扛不住了,工作线程占满,等待队列占满,没有空闲的线程能够处理该请求,什么接口都访问不了
wish: 微服务B超时之后, consumer服务A不能一直卡死等待, 必须降级 微服务B如果宕机了,A也不能卡死等待,必须降级; 如果B没有问题,但自身出现问题,也要自己处理降级
Hystrix使用Fallback熔断 【@HystixCommand】 降级一般在消费者侧
降级主要就是使用注解标注某个服务Service,如果超时,会设置替代的mock响应,可以在provider侧,也可以在consumer侧
要在一个微服务中使用Hystrix,需要引入hystrix的cloud starter依赖【springCloud的相关组件使用都很easy】
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在消费者或者生产者侧的使用都是使用@HystrixCommand指定发生异常或者响应超时时走的mock方法
//eg: producer
@Service
public class ProducerxxServiceImpl implements ProducerxxService {
//超时降级演示,fallbackMethod指定mock方法,commandProperites中配置HystrixProperty,这里配置的就是thread的超时时间> 5走
@HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") //5秒钟以内就是正常的业务逻辑
})
@Override
public String payment_Timeout(Integer id) {
//int timeNumber = 3; //小于等于3秒算是正常情况
int timeNumber = 15; //模拟非正常情况
//int i = 1/0 ; //模拟非正常情况
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" payment_Timeout,id="+id+" \t o(╥﹏╥)o 耗时:"+timeNumber;
}
//兜底方法,上面方法出问题,我来处理,返回一个出错信息
public String payment_TimeoutHandler(Integer id) {
return "线程池:"+Thread.currentThread().getName()+" payment_TimeoutHandler,系统繁忙,请稍后再试\t o(╥﹏╥)o ";
}
}
在消费者端使用是相似的,当然这里既可以放在controller层,也可以放在service层
//eg: consumer
@RestController
public class MessageController2 {
@Autowired
private MessageService messageService;
@GetMapping("/msg2/list")
// 使用HystrixCommand
@HystrixCommand(fallbackMethod = "listByHystirx",commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //请求次数
}) //如果请求次数达到5次都是失败,那么直接调用listByHystirx
public Map<String, String> list() {
return messageService.list();
}
public Map<String, String> listByHystirx() {
Map<String, String> map = new HashMap<>();
map.put("msg","服务端已停止服务");
return map;
}
}
一旦服务调用失败,就会调用fallbackMethod的方法【如果是服务熔断、或者超时不等待,可以部署在producer或者consumer, 但是如果是服务降级,因为producer可能都关闭了,所以只能部署在consumer】
同时主启动类上面要加上==@EnableHystrix== 激活服务【@EnableHystrix复合注解,引用了@EnableCircuitBreaker】,也就是能够扫描激活服务中的@HystrixCommand, 测试超时和服务的异常,都会进行服务降级,走模拟mock方法
加入后,微服务就会使用熔断机制,使用@HystixCommand即可, 需要设置自身调用超时峰值,峰值时需要正常运行,超时需要fallback
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix //使用Hystrix降级
public class UserConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(UserConsumer80Application.class, args);
}
}
可以看到存在问题,每一个方法都对应一个兜底的方法 — 代码膨胀、耦合
这里就可以像之前的全局异常处理一样,定义一个default的mock方法,当没有明确fallback都会走fallback,除了核心业务,普通业务都可以统一跳转
@DefaultProperties(defaultFallback = “”), 放在类上面,类中所有加@HystrixCommand且没有指定fallback的都走该mock
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全局的
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
//下面是全局fallback方法
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,(┬_┬)";
}
}
当没有在@HystrixCommand中指定fallback,都会走全局的fallback,通用的和独享和分开,避免了代码膨胀
但是还有问题,mock代码属于非业务逻辑,和业务逻辑耦合在一起,十分混乱,解耦的方式就是在消费者端Feign调用的时候进行Hystrix处理;
Feign整合Hystrix使用Fallback 【调用时的mock】
Hystrix可以和Feign整合在一起,类似于部署在消费者侧,整合还是导入上面的hystrix依赖
之后在配置文件的Feign配置中开启Hystrix的功能
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
compression:
request:
enabled: true
response:
enabled: true
hystrix:
enabled: true #开启feign中的hystrix,整合在feign中
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
而使用Feign中的hystix,就可以在进行Feign调用时,@FeignClient中的属性fallback指定调用失败时相关的处理逻辑
//比如这里服务A远程调用provider01-depart的服务,在使用注解时使用fallback指定失败mock方法 value = "provider01-depart"
// 使用fallback指定一个类,这个类实现了MessageService,发生服务不可用的时候就会调用这个类中方法
@FeignClient(value = "provider01-depart",fallback = MessageServiceHystrix.class)
public interface MessageService {
// 这里使用RequestMapping将服务提供者的方法与本地Service方法建立映射
@RequestMapping(value = "/msg/list", method = RequestMethod.GET)
Map<String, String> list();
}
--------------------mock类----------------
@Component
public class MessageServiceHystrix implements MessageService {
@Override
public Map<String, String> list() {
Map<String, String> map = new HashMap<>();
map.put("msg","服务端已停止服务");
return map;
}
}
当调用失败时,Feign就不再等待,直接查找fallback的类,在容器中找到对象,调用其继承实现的方法【这里就不需要在启动类加上hystrix,因为Feign中enable了】 Hystrix处理后,下游服务宕机,上游服务不会挂起耗死服务器,直接获得提示信息
因为配置了yaml的enable,不需要@EnableHystrix在启动类
Hystrix服务监控Dashboard
除了隔离熔断服务的调用,Hystrix还提供准实时调用监控Dashboard,持续记录所有通过hystrix发起的执行信息,以图形页面展示【包括多少请求成功、失败】,通过hystrix-metrics-event-stram实现监控
要使用HystrixDashboard组件,需要引入依赖
<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>
可以将该项目像eureka-server一样单独部署监控; 配置server.port = 9003
同时在该项目主启动类上面加上==@EnableHystrixDashboard==
@SpringBootApplication
@EnableHystrixDashboard
public class SpringCloudHystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudHystrixDashboardApplication.class, args);
}
}
启动之后,访问该端口下面的 /hystrix 路径可以看到该图形页面
需要注意的是,所有的微服务提供类,都需要添加监控starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
同时新版本的Hystrix需要指定监控路径,也就是配置ServletRegistrationBean,因为springboot的默认的路径不是/hystrix.stream,服务提供方就不能够监控, 这里就在服务提供方添加下面这个Servlet对象【比如Provider的port为8001】
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@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;
}
那么就可以使用9003监控8001
http://localhost:9003/hystrix http://localhost:8001/hystrix.stream
图形化界面:
- 色: 绿色succes
s、 蓝色short-Circuited、青色Bad Request、黄色Timeout、紫色Rejected、红色Failure、黑色Error% - 圈: 实心圆: 颜色代表健康程度,大小代表流量,流量越大圈越大
- 线: 曲线 — 记录2分钟内流量的相对变化,观察流量的趋势
Gateway 网关 — 反向代理、鉴权、流量控制、日志…
SpringCloud Gateway 是cloud生态中的网关, 在1.x版本中使用的Zuul网关,2.x版本使用Gateway代替网关Zuul, 使用Webflux中的reactor-netty响应式编程组件,底层使用Netty通讯框架
Gateway是基于webflux、spring5、springBoot2、project Reactor、Netty等技术研发的API网关服务,通过简单的方式进行API路由、还有安全【单机模式的Spring Security显然不能每一个微服务都各自设防,麻烦不容易管理】、监控/指标,同时提供过滤器功能 ---- 熔断、限流、重试
外部请求(移动设备、Html、Open接口)
|
负载均衡(Nginx)
|
网关(路由、代理、鉴权)
| |
微服务A 微服务B .....
这里如果不做处理,确实可以绕过网关直接访问微服务,毕竟网关只是做了一个外衣,微服务本身就是提供的controller
想要防止用户绕过网关,常用的解决方案:
- 网络防火墙: 微服务部署的机器开启防火墙,只是给Gateway服务机器开启白名单
- jwt校验: 【如果想简化就是经过网关时标记(生成标识放入redis)之后在到达后台服务时判断redis中value是否相等】; jwt方式可以加入业务校验: 网关鉴权之后,生成jwt令牌,放在请求头中,下游服务都会校验jwt,没有令牌拒绝; 而后面微服务的链路上继续调用时, 使用aop拦截,将jwt插入请求头,链路上的所有的下游服务都可以正常访问
网关的三大核心: Route路由、Perdicate断言、Filter过滤
- 路由: 路由是构建网关的基本模块, ID、目标URL和断言过滤器组成,断言成功 匹配该路由
- Predicate: 断言可以匹配HTTP请求的所有内容,如果和Perdicate匹配则挑战到相关的路由
- Filter: GatewayFilter,过滤器可以在请求被路由之前和路由之后对请求进行修改; Pre过滤器可以进行参数校验、权限校验、流量监控、日志输出、协议转换; Post过滤器可以得出相应内容、响应头修改、日志、流量控制
Netty Server接受请求,转给Predicate断言,匹配Route,之后通过filter chain,经由Netty client请求相应的微服务 【路由校验转发、执行过滤器链】
Predicate 使用 匹配规则【help请求找到对应的route】
Gateway的routes配置有id标识路由规则,uri指定服务主机,但是具体的路径就需要根据路由规则进行匹配了,当然Predicate是可以组合的,最常用的就是Path规则
Gateway中将路由匹配作为HandlerMapper的基础部分,gateway包含很多内置的Route Predicate工厂,这些Predicate和HTTP请求不同的属性匹配,可以相互组合
创建Route对象时,会使用RoutePredicateFacorty创建Predicate对象,可以赋值给Route
常用的Route Predicate:
- After Route Predicate: After路由断言携带的参数为datetime【java的ZoneDateTime类型】,如果请求的时间早于该datetime,就会返回Error Page
spring:
cloud:
routes:
- id: after_route
uri: lb://serviceName
predicates:
- After=2022-10-10T19:59:34.102+08:00[Asia/Shanghai] #这里时间如果比19:59早那么就会返回Error Page,只有晚于这个时间才会正常路由
除此之外,还有Before Route Predicate和Between Route Predicate,都是匹配的datetime, 其中before是必须在datetime之前的请求才会路由,Between是之间的请求才会正常转发
predicates:
- Before=2022-10-10T19:59:34.102+08:00[Asia/Shanghai] #早于该时间才会路由
- Between = 2022-10-10T19:59:34.102+08:00[Asia/Shanghai],2022-10-11T19:59:34.102+08:00[Asia/Shanghai] #在该段时间之间才会路由
- Cookie Route Predicate: Cookie断言携带的参数为name和regexp(regular expression),只有请求中包含name类型的cookie并且该cookie的value为regexp才会断言成功
predicates:
- Cookie=username,cfeng #请求携带username类型的Cookie并且username=cfeng才会断言通过
- Header Route Predicate: Header断言也带有name和regexp, 也就是请求头必须包含name类型的参数并且value为regexp
predicates:
- Header=X-Request-Id,\d+ #请求头要包含X-Request-Id并且值为整数的正则表达式
- Host Route Predicate: Host断言 会判断访问的host主机地址,支持模糊匹配,比如*,**
predicates:
- Host=**.cfeng.com
- Method Route Predicate: Method断言 请求方式,比如GET、POST等
predicates:
- Method=GET #GET类型的请求才会断言通过
- Path Route Predicate: Path断言, 请求路径,也就是符合该Path的请求才会断言成功
predicates:
- Path=/user/get/**
- Query Route Predicate: Query携带的为name和value, 也就是请求要有name请求参数,并且值为value【正则匹配】
predicates:
- Query=username,\d+ #要有参数username且为正整数才能断言成功
Filter使用 修改HTTP请求和响应
路由Filter可以修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用, Gateway内置许多过滤器,由GatewayFilter工厂进行产生
Filter的种类只有两种: Pre和Post【GatewayFilter有31 ++种】
Spring Cloud内置很多filter,比如AddRequestHeader、AddRequestParameter、AddResponsePHeader、RedirectTo…等
除此之外,还包括Global Filters: Foward Routing、 Netty Routing等
而filter在gateway项目中,配置的位置也是在routes下面:
spring:
cloud:
routes:
- id: after_route
uri: lb://serviceName
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头上加上一对请求头,也就是X-Request-Id=1024
predicates:
- Path=/user/lb/**
其余的filter使用的方式类似,可以看到Gateway的基础使用很简单,直接在yaml中配置routes即可,指定id、uri和相关的predicates和filters即可
- 自定义全局GlobalFilter 【GlobalFilter、Ordered】】
可以实现日志记录和统一网关鉴权【spring security就是一个filter chain】
要实现自定义的GlobalFilter,只需要实现GlobalFilter和Ordered,重写相关的方法即可
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
//可以通过exchange获得请求和响应,进行相关的判断,利用filterChain决定是否filter
@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;
}
}
实际生产中使用的就是内置的filter或者自定义相关的filter进行过滤,根本上和之前单机的filter类似
Gateway基础使用 配置routes 绝对uri
Gateway和Eureka一样单独部署,作为一个单独的服务(上游),引入依赖时只需要eureka-client的依赖和hystrix的依赖,以及新添加的gateway依赖即可(不需要web和actuator)
<dependencies>
<!--新增gateway,不需要引入web和actuator模块-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- hystrix依赖,熔断器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka监控的依赖,包含了ribbon在内-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 工具类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写配置文件,假设port为9527
假设不想暴露下游微服务B【localhost: 8080】,也就是由网关来调配,gateway挡在microService之前
那么就需要在spirng.cloud.gateway.routes下面配置路由规则【id、uri、predicates】
server:
port: 9527
spring:
application:
name: cloud-gateway #作为微服务也需要注册到eureka
#配置网关
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/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service #eureka标识
client:
register-with-eureka: true #注册到eureka
fetch-registry: true
service-url:
defaultZone: http://localhost:XXX/eureka
启动类没有什么特别的,最开始就只需要@EnableEurekaClient即可, 这样启动之后,访问9527就可以访问到微服务的服务
gateway 微服务名动态路由 discovery
默认情况下Gateway会根据注册中心的服务列表,以注册中心上的微服务名为路径创建动态路由进行转发,实现动态路由
要想使用动态路由,那么就需要开启从注册中心动态创建路由的功能,利用微服务名称进行路由,通过cloud.gateway.discovery.locator.enabled开启
该种类型的协议使用lb,表明使用Gateway负载均衡
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
filters:
- AddRequestParameter=username,cfeng
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
lb://serviceName 就是spirng cloud gateway在微服务中自动创建的负载均衡的uri, lb之后会从选择合适的服务器主机
Sleuth 分布式链路追踪 (zipkin)
微服务项目中,一个请求往往是在后端系统经过多个不同的服务节点协同产生最后的请求结果, 形成一个服务咋的分布式服务调用链路,一旦链路任何一环出现问题,都会引起请求最终失败 【特别是下游服务可能引发雪崩】
Sleuth(侦探)是Spring Cloud提供的分布式服务跟踪的解决方案,兼容支持zipkin【zipkin dashboard提供了监控面板,就像hystrix dashboard可视化】
Spring Cloud从F版本开始不需要自己构建zipkin Server,直接下载调用jar即可,https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
运行jar: java -jar zipkin-server-2.12.9-exec.jar , 之后就可以在机器上的locahost:9411/zipkin/ 查看面板
链路: 一条完整的请求链路通过Trace Id唯一标识,Span标识请求信息, 各个span通过 parent id 关联
一条链路通过Trace Id唯一标识,Span标识发起的请求信息(上游服务请求下游服务)【Trace: 类似树结构的span集合,标识一条调用链路; span: 标识调用链路的来源,就是一次请求】
Sleuth使用
使用sleth需要在微服务中引入依赖,也就是zipkin(是包含了sleuth在内)
也就是重要需要进行链路追踪的微服务都需要下面的依赖并且进行配置【主要就是指定zipkin的地址和sleuth的采样频率
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
yaml中也需要配置zipkin和sleuth
spring:
zipkin:
base-url: http://localhost:9411 #之前运行的zipkin
sleuth:
sampler:
#采样率值介于0~1之间,1表示全部采样
probability: 1
配置之后如果进行服务的使用,那么zipkin所在的9417端口的dash board就可以监控该服务调用链路【查看调用的信息和耗时以及spans…】
Spring Cloud (Alibaba)
上面分享了netfix的各种组件包括注册中心Eureka、进程内lb Ribbon、远程调用OpenFeign、熔断器Hystirx、网关Gateway、链路追踪Sleuth + zipkin, 可以发现功能还是够用并且使用比较简单, 但是,Netfix项目进入维护模式,之前的zuul、ribbon、 hystrix都是只是维护不再更新
Netfix不再开放新的组件,所以这个时候就出现了Spring Cloud Alibaba — Spring Cloud 下一个子项目, 和Spring Cloud Netfix处于同等地位
Spring Cloud的活力更强,提供多种功能: 服务限流降级、服务注册发现、分布式配置管理、消息驱动能力、分布式事务、阿里云对象存储、分布式任务调用、阿里云短信服务…
需要注意的是,和Netfix一样,Alibaba的版本也需要和SpringBoot的版本相对应,和之前的cloud netfix,直接在父项目的dependencyManagement中引入该依赖即可
<!-- 需要和Spring Boot版本适配 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Alibaba就是一整套解决方案,可以替换Netfix那一套
Sentinel: 以流量为切入点,进行流量控制、熔断降级、系统负载保护等多维度保护服务稳定性
Nacos: 注册中心: 服务发现、配置管理、服务管理
RocketMQ: 分布式消息系统 【cfeng之前使用的middleware是RabbitMQ】
Dubbo: Appache Dubbo可以作为服务的RPC(是直接传递的服务Service和OpenFeign的Rest不同)
Seata: 分布式事务解决方案
Alibaba Cloud ACM: 分布式架构环境对应用配置进行集中管理和推送的应用配置中心
Alibaba Cloud OSS: 阿里云云存储服务(Object Storage Service) 高可靠的云存储服务 【cfeng之前的项目使用的MinIO】
Alibaba Cloud SchedulerX: 中间件团队开发的任务调度参评,提供秒级、高可靠、高可用的任务调度服务
Alibaba Cloud SMS: 覆盖全球的短信服务
Nacos 配置中心 + 配置中心( Netfix中Eureka + Config + Bus)
Nacos :Dynamic Naming and Configuration Service 更易于构建云原生应用的动态服务发现,配置管理和服务管理中心
所以Nacos比Eureka的功能更强大,除了可以做注册中心之外,还可以取代Config做注册中心,使用更加方便【服务Service是Nacos的核心,Nacos支持多种类型的服务发现,包括Dubbo的RPC Service和Spring Cloud Rest Service】
各配置中心比较:
CAP模型 控制台管理 社区活跃度
Eureka: AP 支持 低
Zookeeper CP 不支持 中
Nacos AP 支持 高
CAP定理: 一个分布式系统中,一致性Consitency、可用性Availavality、分区容错性Partition tolerance 最多同时实现两点, 一般都是 一致性和可用性不可兼得; Zookeeper是典型的CP一致性好,满足强一致性,但是系统网络发生了宕机,那么可能导致数据不能访问,所以做分布式锁更好,但是一般Redisson做分布式锁就可以, 而像Eureka、、Redis都是AP模型,保证可靠性;
当前一般的架构都是AP,通过分布式缓存个节点达成最终一致性提高性能,多节点的数据异步复制技术实现
- C: 一致性,分布式系统所有的数据备份,同一时刻是一样的值
- A: 集群中一部分节点故障后,集群整体还能够响应客户端读写请求
- P: 分区相当于时限要求,如果不能在时限内达到数据一致性,就意味着分区,必须在C和A之间选择; CAP就是不能同时满足3个
Nacos是可以选择AP或者CP的,支持雪崩保护、自动注销实例、支持监听、多数据中心、跨注册中心、同时可以集成SpringCloud、Dubbo、K8s,支持AP和CP模式切换 ----- serverMode = AP
Nacos安装
可以从官网之间下载Nacos【和zookeeper一样】,安装之后,进入bin中运行startup.cmd接口(zookeeper不支持控制台管理,一般都需要配置Dubbo Admin)
需要注意Nacos的版本需要和Alibaba的版本适配,也就是最终要和SpringBoot版本适配
Release 1.4.4 (Aug 8th, 2022) · alibaba/nacos · GitHub Cfeng下载的是1.4.4版本,下载之后解压安装包
修改conf\application.properties 中的配置mysql数据库,就将db位置的url和username和password改为本机的数据库地址
并且把startup.cmd中MODE改为standalone
之后点击startup.cmd就可以访问nacos,访问http://localhost:8848/nacos 即可,默认账号和密码都是nacos
- Linux版本的Nacos安装
还是在官网下载Linux版本的Nacos,下载之后修改properties,同时启动mysql数据库,创建数据库,use之后source /xx/nacos-mysql.sql
Cluster部署: 在cluster.conf
192.168.204.100:8888
192.168.204.100:7777
192.168.204.100:6666
#这里的IP不能写127.0.0.1, 要hostname -i识别的IP 【也就是配置的内网或者外网IP】
要能够在不同的端口启动,所以修改一下shell脚本,也就是sh文件
s)
SERVER=$OPTARG;;
p)
PORT=$OPTARG #要能够.sh -port,这里需要新增PORT
c)
MEMBER_LIST=$OPTARG;;
nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 & #这里需要将Port加入
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" #JVM内存管理,需要修改内存大小,否则可能内存不够用
之后就可以通过 ./startup.sh -p XXX 启动不同的服务
同时需要使用集中式LB— nginx进行负载均衡
首先需要修改nginx的配置文件 vim /usr/local/nginx/conf/nginx.conf
upstream cluster{
server 192.168.204.100:8888;
server 192.168.204.100:7777;
server 192.168.204.100:6666;
}
server{
listen 1111; //nginx服务nacos端口
server_name 192.168.204.100; //nginx主机
location / {
proxy_pass http://cluster;
}
.......
以该文件启动nginx ./nginx -c /usr/local/nginx/conf/nginx.conf 就启动了nginx并且提供服务对3个nacos负载均衡 通过nginx访问nacos: http:192.168.204.100:1111/nacos/#/login
这个时候,注册中心和配置中心的server-addr就是访问的nginx集群入口 也就是192.168.204.100:1111 【client访问nginx的1111端口,由nginx负载到3个nacos,共同作用在数据库3306
Nacos注册中心discovery使用
在父pom中需要引入alibaba-dependencies,在所有的子服务(需要注册到nacos)中,加入alibaba-nacos的依赖
该依赖中包含netfix的ribbon, 也就是兼容使用了ribbon进程内LB
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
再配置yaml即可,需要配置nacos.discovery.server-addr,也就是nacos注册中心的位置【机器和端口】
spring:
application:
name: nacos-xxx-provider #cloud 微服务中服务的唯一标识
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos注册中心地址
#actuator检测当前项目的情况
management:
endpoints:
web:
exposure:
include: '*' #默认只公开了/health和/info端点,要想暴露所有端点只需设置成星号
要想启动nacos,还需要在项目的主启动类上面及加上发现Discovery的注解==@EnableDiscoveryClient==
@EnableDiscoveryClient //代表为nacos的client,注册到nacos
@SpringBootApplication
public class xxxService {
public static void main(String[] args) {
SpringApplication.run(xxxService.class,args);
}
}
之后运行微服务,就可以在页面的服务列表中查看该服务; 服务的相关信息
不管是提供者还是消费者,都是一样的,加入相关的依赖,alibaba-nacos-discovery;actuator
Nacos配置中心config使用
在不使用配置中心时,比如静态配置config.conf,各种参数配置、应用配置、环境配置、安全配置、业务配置都写在该文件中, 【但是参数修改不灵活,甚至还需要重启项目】
并且一些配置文件都不能区分环境,比如dev、test等,且偶尔u照顾微博华北的修改不能追溯,如果部分服务的被修改,不好控制
配置中心就是i将项目的各种配置、开关,全部放到一个集中的地方进行统一管理【同步到配置中心】,当服务需要配置的时候,就到配置中心的接口拉取,当配置中心的参数更新的时候,可以进行动态更新
Nacos除了注册中心,最主要的功能就是配置中心,相当于Netfix中的Config,注册中心为nacos-discovery,配置中心为nacos-config, 需要引入依赖
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
Nacos和Config一样,在项目初始化时,保证先从配置中心进行配置拉取,拉取配置之后,才保证项目的正常启动
微服务项目中,配置文件的加载的优先级是bootstrap > application( properties > yml)
所以就在bootstrap.yml中配置配置中心的信息,yml后加载,会覆盖bootsrap中的同名的key
要在项目中使用配置中心,那么就建立bootsrap.yml,进行配置
####### bootstrap.yml ######
server:
port: 8081
spring:
application:
name: nacos-config-client #注册中心和配置中心识别服务
cloud:
nacos:
discovery:
server-addr: localhost:8848 #注册中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yml #指定服务配置文件格式为yml
同时在application.yml中配置spring.profiles.active,指定当前启用的环境
spring:
profiles:
active: dev #开发环境 ,当然可以使用maven来进行管理
在启动类上面还是只需要==@EnableDiscoveryClient==即可,因为nacos启动之后配置中心和注册中心功能都启动了
@RefreshScope
: 属性动态刷新(放在class上),一旦属性刷新,那么该注解就会使用代理对象清理并且get重新创建对象放入容器,实现更新
项目中添加了配置,还需要在Nacos中添加配置信息,匹配规则: dataid
${prefix}-${spring.profile.active}.${file-extension}
- 其中prefix默认就是spring.applcation.name,也可以手动配置spring.cloud.nacos.config.prefix
- spring.profile.active就是当前环境对应的profile,当spring.profile.active为空,对应的连接符-也不存在
- file-extension为配置内容的数据格式,在bootstrap.yml中配置spring.cloud.nacos.config.file-extension即可 【识别bootstrp.yaml】
在Nacos中的操作,就是配置管理,配置列表中新建配置,指定DataID为上面公式配置之后的具体string和Group,同时指定配置格式即可,配置内容初始化为:
添加在该配置内容的配置项都会被服务加载
config:
info: "config info for dev,from nacos config center."
设置DataID为 nacos-config-dev.yaml; 和项目中的配置对应【bootsrap.ym中的spring.application.name 和 spring.cloud.nacos.config.file-extension; 和application.yml中的spring.profile.active】 Nacos会记录配置文件的历史版本默认保留30天
这里可以通过一个简单的controller验证
@RestController
@RefreshScope //通过SpringCould原生注解@RefreshScope实现配置自动更新
public class ConfigClientController{
@Value("${config.info}") //对应nacos配置:nacos-config-client-dev.yaml
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
配置在nacos中的配置都会加载到微服务中,所以这里就可以加载到config.info这个配置项
当config.info改变时,微服务也会刷新改变,这里就对应着属性的改变,加上@RefreshScope就可以让属性改变的时候动态刷新对象,也就是controller对象也就动态变化
在配置好nacos方面的配置文件之后就开始使用配置中心,修改Nacos的yaml配置文件,可以发现微服务的配置刷新
Nacos配置中心 — 多环境分类配置
实际开发中开发环境需要准备dev、test、prod等多个环境,要保证指定环境启动能读取Nacos上面的环境文件; 同时一个系统由多个微服务组成,众多微服务对应众多的微服务如何分类管理
这里就可以看到区分的方式: DataID、Group、Namespace
namespace用于区分部署环境,Goup + DataID逻辑区分两个目标对象
默认情况下,Namespace = public, Group = DEFAULT_GROUP,默认Cluster集群为Default
- 不同的环境可以对应不同的Namespace实现隔离
- Group可以把不同的微服务划分到不同的组中,Service就是微服务,一个微服务对应多个Cluster集群【为了容灾,将Service微服务部署在两个不同的机房,每个机房很多台机器Instance,一个机房就是一个Cluster】
- 使用DataID方案实现分类
指定spring.profile.active配合DataID使不同环境读取不同的配置文件
默认空间 + 默认分组 + 新建dev、test、prod多个DataID
这里初始化的config.info改为config info for test/prod,以和dev区分
这样,当启动项目的时候,只要项目中spring.profile.active = test就会加载nacos中对应的test的配置文件,而不会加载dev的
- 通过Group实现分类
Nacos中新建配置时,DataID就只是普通的nacos-config-info.yaml, 而Group指定不同的TEST_GROUP、DEV_GROUP、PROD_GROUP等
与此对应,bootstrap还是配置spring.application.name和nacos配置中心和注册中心的配置,同时在config下面新增group 指定nacos中的组,applicaiton.yml中的spring.profiles.active变为info, 也就是不区分
config下面新增配置group,同时spring.profiles.active不再标识,直接info即可
- Namespace实现分类
Nacos在命名空间位置新建敏敏空间,比如dev、test、prod,不同的命名空间对应不同的微服务
之后在bootstrap.yml中的config下面新增namespace配置项,值为对应的命名空间的ID(Nacos查看)
这样就可以直接定位到nacos下面的该命名空间中的group中的该DataID的配置文件加载到微服务中
Nacos集群
Nacos支持3中部署模式: 单机模式、 集群模式(高可用)、多集群模式(多数据中心)
Nacos的集群部署,推荐将所有的服务放在一个VIP下面,挂载到同一个域名下,也就是直接http://nacos.com:port/openAPI, 域名 + VIP模式,换IP方便
请求到达nginx集群之后,负载均衡到nacos集群,nacos集群将数据写入到高可用的mysql集群中; Nacos采用集中存储的方式支持集群化部署【使用mysql】
安装nacos的时候就可以修改conf下面的application.properties, 就可以使用mysql数据库【默认不持久化的时候使用derby嵌入式数据库】
Sentinel 可用性保障【 服务熔断、限流…】
Alibaba环境的熔断器,类似之前的hystrix; 官网给出的解释: 分布式系统流量防卫兵, 流量控制、熔断降级、系统负载保护
Sentinel和zipkin一样具有实时监控,可以看到接入的机器的秒级数据,开箱即用,支持Spring Cloud的整合,配置使用简单【java编写】
Sentinel包括核心库,不依赖任何框架,对于spring cloud和dubbo都提供友好支持,同时还有一个基于spring boot开发的控制台dashboard
https://github.com/alibaba/Sentinel/releases 下载dashboard, 之后java -jar启动即可,8080端口不能被占用,当然也可以修改jar,访问本机8080端口就可以看到前端dashboard页面,默认的登录密码都是sentinel
Sentinel主要可以配置很多规则(限流、热点、系统…),如果没有配置持久化,和nacos一样,一旦重启应用,Sentinel规则消失,那么之前配置的规则就不见了,素以需要将规则持久化到nacos中保存【nacos持久化到mysql】
引入依赖就是sentinel-datasource-nacos,在需要sentinel监控并且需要持久化规则的项目的配置文件中添加datasource的配置
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
指定nacos的dataId,goupId和data-type为jason,就可以将规则持久化到nacos中
之后在nacos的配置中新建配置,dataID就是上面的sentinel.datasource.ds1.naocos.dataId
新建JSON格式的配置,比如这里就直接在nacos的配置管理中新增配置内容
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
之后就可以在sentinel的dashboard看到该条规则, 这里的几个参数对应的就是上面的流控规则中的resource资源名、limitApp调用来源、grade限流阈值类型(1代表QPS模式,0为线程数模式)、count限流阈值、staregy流控模式(直接、链路、关联)、controlBehavior流控效果(直接拒绝、预热慢启动、排队等待)、clusterMode(是否集群限流)
Sentinel配合Nacos一起使用,启动Nacos,也可以访问8848dashboard
Sentinel流控规则 ----- 限流FlowLimit
查看Sentinel控制面板,可以看到提供很多选项,包括实时监控、流控规则、降级规则、热点规则…
点击流控规则,可以选择新增流控规则,指定资源名、来源、阈值、流控模式、效果…
- 资源名: 唯一标识、默认的请求路径
- 针对来源: Sentinel可以针对调用者进行限流,填写微服务名称,默认default(不区分来源)
- 阈值类型/单机阈值: QPS:当QPS到达阈值,进行限流,线程数量: 当调用该api的线程数量达到阈值,进行限流
- 是否集群: 服务是否集群
- 流控模式:
- 直接: 当api达到限流条件,直接限流
- 关联: 当关联的资源达到阈值,限流自己
- 链路: 只是记录链路上的流量(资源入口的流量,如果达到阈值,限流),前面的api级别是针对的局部的来源
- 流控效果:
- 快速失败: 流量增加后,多余的直接失败,抛出异常
- Warm up: 根据codeFactor冷加载银子的值,从阈值,金国预热时长,才达到设置的QPS阈值
- 排队等待: 匀速排队,让请求匀速通过,阈值类型必须为QPS,否则无效【类似单机的令牌桶FlowLimit】
Sentinel热点规则
新增热点规则和新增流控规则类似,需要配置参数例外项;热点就是经常访问呢的数据,Top k数据访问限制,热点参数限流就是一种特殊的流量控制,只是包含热点参数的资源调用生效
参数例外项,就是监控的方法的参数为特殊的值时,阈值和总体设置的阈值不同,热点参数类型必须是基本类型或者String
Snetinel系统规则
系统保护规则是从应用级别的入口流量控制,单台机器的load、CPU使用率、平均RT、入口QPS、并发线程数 进行监控,保证系统整体稳定性; 系统规则是应用整体维度,不是资源维度,仅对入口流量生效, 进入应用的流量,比如Web服务接受的请求, 直接在控制台增加配置规则即可
Sentinel 限流规则使用
Sentinel的限流对应的就是流控规则,在新增流控流控规则中,可以选择流控模式,默认就是直接方式【如果自己流量过大,直接限制自己】
引入sentinel依赖,配置dashboard之后,就可以测试不同的流控模式和阈值模式
@GetMapping("/testA")
public String testA {
try {
//Thead.sleep(800);//800ms
} catch(InterruptedException e) {
e.printStackTrace();
}
return "cfeng----TestA";
}
- 直接模式 + QPS阈值模式: 比如配置1s查询超过1ci,就快速失败 ---- 资源名写访问路径,比如/testA, 来源default,阈值类型选择QPS,单机阈值设置为1, 选择流控模式,效果选择快速失败 【当快速点击的时候,出现blocked by sentinel】
- 直接模式 + 线程数: 配置线程数的单机阈值为1, 【快速点击,不会出现FlowLimit,因为线程处理请求很快,达不到阈值】 但是当加上Thread.sleep(800)的时候,快速点击,就会出现FlowLimit,同1s的线程数量达到了阈值
- 关联模式 : 当关联的资源,比如/testB的QPS阈值超过1,那么就会限流/testA的REST访问地址, 在sentinel控制台选择关联模式后,需要配置关联的资源(比如请求路径/testB), 这个时候就会监控/testB的流量,超过阈值,限流当前的资源 【测试可以采用postman的多线程集合组】 /testB超过阈值, A被限流,此时如果访问B,因为效果选择的直接失败,那么就会发现访问A失败
- 链路模式: 同个请求调用同一个微服务,这个时候选择链路模式,限流请求入口
Sentinel的监控位置就是流控模式,而指标就是阈值模式,限流的效果就是流控效果,默认的流控效果就是直接-> 快速失败,当流量过大,请求不处理,直接失败
秒杀系统开启瞬间,很多流量到来,可能将系统直接打死,预热方式可以保护系统,慢慢放入流量(因为阈值可能设置的比较高),效果就是最开始点击可能直接失败了,后期就慢慢可以访问
- 预热warmUp: 阈值 ÷ coldFactor(默认值3),预热的是阈值,也就是最开始的阈值很低,在coldFactor的加速度下经过warmUp到达最终阈值经过预热时长后达到阈值,预热/冷启动方式,当系统长期处于低水位,流量瞬时增加可能将系统压垮,这个时候可以预热,缓慢增加,让系统逐步达到阈值
默认的coldFactor为3,也就是QPS从threshold/3开始,经过预热时长到达QPS阈值,eg: 阈值 = 10,预热时长5s; 那么系统初始化阈值为10/3= 3, 阈值刚开始为3,经过5s才慢慢到到达10
- 排队等待: 匀速排队,让请求匀速通过,阈值模式必须是QPS,否则无效, /testB每s一次请求,超过就排队等待,等待超时时长为20000ms
Sentinel降级规则使用
限流和降级都是在sentinel控制台进行新增相关的流控、降级规则即可; 【都是对应的微服务加入nacos和sentinel的依赖,主动让sentinel监控,就可以在sentinel中直接配置规则来操控微服务的运行】
Sentinel控制台最主要的还是降级的功能,处理流[量控制(Limit)外,还可以对调用链路中不稳定的资源进行熔断降级【稳定健康状态指标: 平均响应时间,异常比例,异常数量】
降级规则配置也需要指定资源名称、降级的策略(RT、异常比例、异常数量),时间窗口
RT(平均响应时间,秒级): 超过阈值并且时间窗口内的请求>=5, 两个条件同时满足触发降级,窗口期过后就会关闭断路器, RT最大4900ms,更大需要-Dcsp.sentinel.max.rt = xx配置;
异常比例(秒级): QPS>5(阈值)且异常超过阈值,时间窗口结束,关闭降级【每s异常数占通过量的比值】
异常数(分钟级): 异常数超过阈值,触发降级,时间窗口结束,关闭,【资源近1分钟的一场数目】,timeWindow如果小于60,那么熔断结束后可能再进入熔断, 时间窗口一定大于60s
Sentinel熔断降级会在链路某个资源不稳定(超时、异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响其余资源, 降级后,接下来的降级时间窗口内,自动熔断;抛出DegradeException异常,Sentinel的断路器没有半开状态的,Hystrix半开状态让一个请求去重试,而sentinel是直接划定一个降级时间窗口
Sentinel服务代码中配合规则 @SentinelResource
上面的流控规则、热点规则、限流规则都是直接在Sentinel控制台直接配置,以限流为例,当出现问题后,都是系统默认的Blocked by Sentinel(flow limiting),在代码中可以像hystrix一样直接fallback兜底 【资源名称既可以写路径,也可以写注解的value参数】
Sentinel默认限流时默认抛出BlockException
在项目中,使用@SentinelResource就可以进行代码级别的监控,其中value指明资源,和sentinel中的资源对应,blockHandler就是达到Sentinel控制台设置的限流规则阈值后抛出异常进行捕获的监听方法,类似之前的hystrix的fallback兜底
@RestController
public class RateLimitController{
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}
需要注意RuntimeException运行时异常和@SentinelResource无关,不会管理,只是负责控制台配置的限流规则不满足时抛出的block异常
但是这里还是存在hystrix的代码膨胀、和业务逻辑耦合的问题
自定义限流处理逻辑
上面的限流规则都是在sentinel中配置的,如果要自定义限流规则,可以直接自定义限流处理类,方法public static修饰; 之后在@SentinelResource中通过blockHandlerClass指定处理的限流逻辑
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException exception){
//自定义限流逻辑
}
//通过blockHandlerClass指定自定义限流规则
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
其中还有fallback参数,但是这里的fallback参数管理的是运行异常, fallback就是当监控的方法出现非配置的限流规则异常之外,发生其余的异常的fallback方法, 其实也就是之前的异常处理【spring集中在一起处理】
所以最好fallback和blockhandler都配置,不超过降级规则执行fallback兜底处理;超过降级规则抛BlockException异常,被blockHandler处理
还有一个常用属性为excepitonsToIngore,也就是对fallback的补充,fallback默认对于所有的异常都会进行兜底,该参数可以指定一些Exception不走fallback的兜底方法,直接抛出 : exceptionsToIgnore ={IllegalArgumentException.class}
Sentinel 监控功能使用
要在微服务中使用sentinel,同时配合nacos,需要引入sentinel和sentinel-datasource-nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
这里cfeng还将nacos注册中心和openfeign都引入
接下来就是改写application.yml配置文件,需要配置sentinel的信息: dashboard的位置,以及应用与dashboard交互端口,本地启用一个端口占用HttpServer
port会启动一个HttpServer,该Server和控制台交互,eg: Sentinel控制台添加一个限流规则,会将规则push给这个Server接受,Http Server再将规则注册到Sentinel【类似一个中间代理】
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
# sentinel配置,包括dashboard和交互port
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默认8719,应用和Sentinel控制台交互的端口,应用本地会启用该端口占用HttpServer
management:
endpoints:
web:
exposure:
include: '*' # * 代表所有的端点
之后主启动类加上==@EnableDiscoveryClient==,将服务注册到Nacos
之后正常编写相关的业务类, 打开控制台会发现什么都没有,但是一旦访问,就会监控当前服务的相关指标(通过QPS,拒绝QPS、响应时间)Sentinel使用的懒加载规则, 会监控所有服务中添加依赖以及配置sentinel的服务
Sentinel配合OpenFeign、Ribbon
项目中引入openFeign和nacos、sentinel的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置的时候,需要配置nacos和sentinel
Feign中除了可以使用hystrix,也可以使用sentinel,开启之后就可以像hystrix一样处理兜底、解耦合, 不需要再单独使用@SentinelResource的blockHandler处理兜底,配置feign.sentinel.enabled =true即可,和hystrix类似
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
#对Feign的支持
feign:
sentinel:
enabled: true
主启动类加上@EnableDiscoveryClient ,@EnableFeignClients 两个注解开启nacos和feign的功能【sentinel不需要注解开启】
这样在Feign远程调用的Service类中,fallback兜底类即可【抽象化了,和hystrix处理是一样的】
@Component
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentFeignService{
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
Sentinel作为后继者,功能比hystrix更强大,除了有更完备的dashboard之外,还支持限流,支持流量整形(预热模式、匀速其模式、预热排队模式)…,主要就是dashboard能够让项目更好运维🎄