Gateway 是异步非阻塞的模型,底层使用的Netty框架,可和springwebFlux、springMvc结合使用。
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。 Predicate 就是我们的匹配条件;而 Filter 就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标 URI,就可以实现一个具体的 Route (路由)了。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出**,流量监控**等有着非常重要的作用。
gateway包含了许多内置的路由断言工厂。所有的这些断言匹配不同的HTTP请求属性。你能组合这些路由断言工厂。我们一般是在配置文件中配置Predicates,当然我们也可以自定义Predicate,如下:
@Component
public class CustomeRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
public CustomeRoutePredicateFactory() {
super(PathRoutePredicateFactory.Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(PathRoutePredicateFactory.Config config) {
System.out.println("TokenRoutePredicateFactory Start...");
return exchange -> {
ServerHttpRequest request = exchange.getRequest();
//take information from the request to see if it
//matches configuration.
return matches(config, request);
};
}
private boolean matches(PathRoutePredicateFactory.Config config, ServerHttpRequest request) {
System.out.println(request.getBody());
RequestPath path = request.getPath();
if(path.toString().contains("gateway")) {
return false;
}
return true;
}
}
配置文件properties:
spring:
cloud:
gateway:
routes:
- id: path_route
uri: lb://PROVIDER # lb的意思是负载均衡
predicates:
- Custome=/gateway/** # 这个Custome就是自定义类CustomeRoutePredicateFactory的前缀,仿照PathRoutePredicateFactory写的
filters:
- name: Hystrix # 表示filter中使用Hystrix来做熔断降级
args:
name: fallbackcmd #名字唯一即可
fallbackUri: forward:/defaultfallback # 在本gateway中产生的异常或超时将调用本gateway中的control层中的defaultfallback
网关路由可以配置的内容包括:
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。
像这样的断言工厂在SpringCloudGateway还有十几个:
Gateway 的 Filter 有三种:全局Filter、默认 Filter 和自定义 Filter。
全局Filter一般是我们定义,实现implements GlobalFilter, Ordered两个接口,然后重写两个方法即可。一个是filter方法,一个是getOrder方法。全局过滤器可以存在多个,多个的时候根据getOrder方法的返回值大小进行排序执行,请求服务前的优先级越高,服务返回后执行的优先级越低。如果 order 值相同,则按照如下顺序执行:
@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("time:" + new Date() + "\t 执行了自定义的全局过滤器: " + "MyLogGateWayFilter" + "hello");
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
System.out.println("****用户名为null,无法登录");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 这个就是继续执行的意思
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
以下示例分别显示了如何设置全局前置和后置过滤器:其实还是基于全局过滤器,只不过展现的方式不一样了,这里是直接通过bean注解来注入到容器,然后使用的是匿名类。注释掉的代码是官网给的案例!
@Configuration
public class GateWayFilter {
@Bean
public GlobalFilter customGlobalFilter() {
// return (exchange, chain) -> exchange.getPrincipal()
// .map(Principal::getName)
// .defaultIfEmpty("Default User")
// .map(userName -> {
// //adds header to proxied request
// exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", userName).build();
// return exchange;
// })
// .flatMap(chain::filter);
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("请求前执行,这里可以放请求前的逻辑");
exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", "lisi").build();
return chain.filter(exchange);
}
};
}
@Bean
public GlobalFilter customGlobalPostFilter() {
// return (exchange, chain) -> chain.filter(exchange)
// .then(Mono.just(exchange))
// .map(serverWebExchange -> {
// //adds header to response
// serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
// HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked" : "It did not work");
// return serverWebExchange;
// })
// .then();
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.just(exchange)).map(serverWebExchange -> {
System.out.println("请求后执行,这里是当网关拿到转发服务的请求响应后会执行");
//adds header to response
serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked" : "It did not work");
return serverWebExchange;
}).then();
}
};
}
}
Gateway也为我们提供了很多的内置Filter,即默认的Filter,像AddRequestHeaderGatewayFilter,AddRequestParameterGatewayFilter等等。在yml中配置也只需要写前缀即可,如下:
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
下面讲几个常用的gateway内置的Filter:ForwardRoutingFilter和ReactiveLoadBalancerClientFilter。
spring:
cloud:
gateway:
routes:
- id: myRoute
uri: lb://service
predicates:
- Path=/service/**
注意:gateway支持所有的loadbalancer特性。
gateway作为网关,与其他网关技术不同的是它能实现限流。
gateway使用的是令牌桶算法实现限流。常见的限流算法有:
计数器算法:以QPS为100举例,如果1秒内,前200ms请求数量到达了100,后面800ms中的请求都会被拒绝,这种情况称为"突刺现象“;
漏桶算法:可以解决突刺现象。比如创建一个很大的队列来接收请求,一个较小的线程池来处理请求。但是也有极限情况,当队列满了时, 请求也会拒绝掉。固定生产速率和消费速率。
令牌桶算法:可以说是漏桶算法的改进。在桶中放令牌,请求获取令牌后才能继续执行。如果桶中无令牌,请求可以选择进行等待或直接拒绝。固定生产令牌速率,但是消费速率看桶中令牌情况。等同于生产熟虑=消费速率。
在项目中使用gateway网关做限流一般结合的redis,使用令牌桶算法。
或者结合sential, 用滑动窗口的计数器的方式限流。
Nacos是阿里巴巴开源的一个对微服务架构中服务发现,配置管理和服务管理平台。
nacos服务之间通过distro协议互相同步数据。微服务只需要和其中一个nacos集群中一个节点连接即可。
nacos心跳机制
nacos和client之间采取推拉结合的交互方式:
服务的健康检查分为两种模式:
客户端上报模式:客户端通过心跳上报的方式告知nacos 注册中心健康状态(默认心跳间隔5s,nacos将超过超过15s未收到心跳的实例设置为不健康,超过30s将实例删除)
服务端主动检测:nacos主动检查客户端的健康状态(默认时间间隔20s,健康检查失败后会设置为不健康,不会立即删除)。
nacos 目前的instance有一个ephemeral字段属性,该字段表示实例是否是临时实例还是持久化实例。如果是临时实例则不会在nacos中持久化,需要通过心跳上报,如果一段时间没有上报心跳,则会被nacos服务端删除。删除后如果又重新开始上报,则会重新实例注册。而持久化实例会被nacos服务端持久化,此时即使注册实例的进程不存在,这个实例也不会删除,只会将健康状态设置成不健康。
这里就涉及到了nacos的AP和CP模式 ,默认是AP,即nacos的client的节点注册时ephemeral=true,那么nacos集群中这个client节点就是AP,采用的是distro 协议,而ephemeral=false时就是CP采用的是raft协议实现。
nacos也有自我保护机制(当前健康实例数/当前服务总实例数),值为0-1之间的浮点类型。正常情况下nacos 只会健康的实例。单在高并发场景,如果只返回健康实例的话,流量洪峰到来可能直接打垮剩下的健康实例,产生雪崩效应。
保护阈值存在的意义在于当服务A健康实例数/总实例数 < 保护阈值时,Nacos会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样远比造成雪崩要好。牺牲了请求,保证了整个系统的可用。
Nacos采用的是长轮询的方式:
其实主要是靠Nacos—config包下的NacosContextRefresher、NacosConfigManager这两个核心。
NacosContextRefresher主要是做桥梁的作用,通过ApplicationContext获取上下文信息,通过ApplicationListener来通知事件的发布更新Spring容器。
NacosConfigManager作为配置的启动,创建长轮询的定时任务,定时执行任务获取更新的配置。
资源名:唯一名称,默认请求路径
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)
阈值类型/单机阈值:
○ QPS(每秒钟的请求数量):当调用该接口的QPS达到了阈值的时候,进行限流;
○ 线程数:当调用该接口的线程数达到阈值时,进行限流
是否集群:不需要集群
流控模式:
○直接:接口达到限流条件时,直接限流
○关联:当关联的资源达到阈值时,就限流自己
○链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流)[api级别的针对来源]
流控效果
○快速失败:直接失败,就异常
○Warm Up:根据codeFactor(冷加载因子,默认为3)的值,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值
注意:针对url制定的流控规则会走系统默认的规则,而针对SentinelResource中的资源value制定的流控规则会走自定义的blockHandler方法。
RT:平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值(以ms为单位),那么接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会出现熔断(抛出DegradeException)。注意Sentinel默认统计的RT上限为4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
异常比例:异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule中的count)之后,资源进入降级状态,即在接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比例的阈值范围是[0.0,1.0],代表0%~100%。
异常数:
异常数(DEGRADE_GRADE_EXCEPTION_COUNT):当资源近1分钟的异常数目超过阈值之后,会进行熔断。注意通过时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态。
负载算法 | 描述 |
---|---|
RandomRule | 随机 |
RoundRobinRule | 轮询 |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略; |
RetryRule | 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务; |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务; |
Feign是Spring Cloud组件中的一个轻量级 RESTful 的 HTTP 服务客户端 Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
客户端接口类
/**
* POST请求
*/
@Body("{person}")
@RequestLine("POST /feign/provider/body1")
@Headers({"content-type:application/json"})
String consumer1(@Param("person") User person);
/**
* GET请求
*/
@RequestLine("GET /feign/provider/param3?names={names}")
public String testCollection(@Param("names") List<String> names);
feign:
hystrix:
enabled: true
client:
refresh-enabled: true
httpclient:
connection-timeout: 500
启动类加上@EnableFeignClients注解
定义客户端接口类
@FeignClient(value = "provider",fallbackFactory = TestFallbackFactory.class)
public interface OpenFeignService {
@RequestMapping("/open")
@LoadBalanced
public String getName();
}
作为微服务间的链路追踪解决方案,需要做到3点:
大型分布式系统的追踪数据分为实时数据和全量数据:
Spring Cloud Sleuth 为服务间调用提供了链路追踪,通过查看整个调用链路,可以方便的知道一个服务请求经过了哪些服务,每个服务处理花费了多长以及定位调用异常点,除此以外,通过Sleuth,还可以确定:
Sleuth需要与Zipkin做集成,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。
Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。
我们通过在各个服务上做链路追踪后,将数据传输到Zipkin做存储,Zipkin提供了友好的UI通过使用TraceId或者服务名称等作为查询条件查询调用链路和服务间的依赖关系。
如上图为zipkin与服务集群集成的基础架构图,图中主要由4个组件构成:
Spring Boot Admin 是一个开源的社区项目,用于管理和监控 Spring Boot 应用程序。应用程序可以通过 http 的方式,或 Spring Cloud 服务发现机制注册到 SBA 中,然后就可以实现对 Spring Boot 项目的可视化管理.
Spring Boot Admin 可以监控 Spring Boot 单机或集群项目,它提供详细的健康 (Health)信息、内存信息、JVM 系统和环境属性、垃圾回收信息、日志设置和查看、定时任务查看、Spring Boot 缓存查看和管理等功能