文章目录
SpringCloud五大组件分别对应(1)服务注册与发现(2)客服端负载均衡(3)断路器(4)服务网关(5)分布式配置
Gateway
https://blog.csdn.net/weixin_42073629/category_9940386.html
动态路由
RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter
启动时获取路由信息
注入CacheingRouteLocator
org.springframework.cloud.gateway.config.GatewayAutoConfiguration
内会向容器内注入CachingRouteLocator
对象,它是RouteLocator的实现类,其中包含了
名为delegate的属性,值为CompositeRouteLocator
类的对象, CompositeRouteLocator内包含了所有RouteLocator接口的实现类,RouteLocator接口内包含getRoutes接口,获取路由信息
@Bean
@Primary
@ConditionalOnMissingBean(
name = {"cachedCompositeRouteLocator"}
)
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
监听RefreshRouteEvent事件
CachingRouteLocator类实现了ApplicationListener接口,监听RefreshRouteEvent事件,在监听到该事件后会通过delegate.getRoutes方法获取当前所有的路由定义,并放入缓存内,后续请求到来时就通过该路由缓存进行请求转发
public void onApplicationEvent(RefreshRoutesEvent event) {
try {
this.fetch().collect(Collectors.toList()).subscribe((list) -> {
Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe((signals) -> {
this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
this.cache.put("routes", signals);
}, (throwable) -> {
this.handleRefreshError(throwable);
});
});
} catch (Throwable var3) {
this.handleRefreshError(var3);
}
}
RouteRefreshListener
RouteRefreshListener
会监听某些事件,然后发送RefreshRoutesEvent
事件,其中就包括监听ContextRefreshedEvent
和HeartbeatEvent
, 他们分别为项目启动容器上下文刷新事件,微服务实例向注册中心上报心跳事件
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent)event;
if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {
this.reset();
}
} else if (!(event instanceof RefreshScopeRefreshedEvent) && !(event instanceof InstanceRegisteredEvent)) {
if (event instanceof ParentHeartbeatEvent) {
ParentHeartbeatEvent e = (ParentHeartbeatEvent)event;
this.resetIfNeeded(e.getValue());
} else if (event instanceof HeartbeatEvent) {
HeartbeatEvent e = (HeartbeatEvent)event;
this.resetIfNeeded(e.getValue());
}
} else {
this.reset();
}
}
NacosWatch
我们项目内注册中心使用的是Nacos,当使用时需要在服务中引入spring-cloud-starter-alibaba-nacos-discovery
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
其中包含一个NacosWatch监听器,它会从Nacos服务端同步并更新当前节点的metadata元数据信息,同时定时30S发送一个HearbeatEvent事件
动态路由
为什么要实现动态路由?
通常情况下微服务路由信息是通过在spring-cloud-gateway项目配置文件内指定,但是在实际运行过程中可能需要修改路由信息,此时就需要修改gateway配置内容进行重启,导致整个微服务请求转发都有问题,因此需要实现动态路由
实现思路
-
RouteDefinitionLocator
接口存在DiscoveryClientRouteDefinitionLocator
、PropertiesRouteDefinitionLocator
、CompositeRouteDefinitionLocator
等实现类,分布用于从服务注册中心获取服务信息作为路由定义、 从配置文件中获取数据作为路由定义
CompositeRouteDefinitionLocator内包含了其他所有RouteDefinitionLocator接口的实现类,通过getRouteDefinitions接口获取路由定义 -
RouteLocator
接口存在实现类RouteDefinitionRouteLocator
, 包含CompositeRouteDefinitionLocator类型的内部属性,可获取路由定义并转化为路由数据 -
gateway内存在
RouteDefinitionRepository
接口实现了RouteDefinitionLocator
和RouteDefinitionWriter
接口,提供路由定义的增删改查操作方法;可实现RouteDefinitionRepository接口提供路由定义信息的增删改查操作, 将路由定义存储至缓存或数据库中,页面调用RouteDefinitionRepository实现类接口进行操作, 当执行了增删改操作后发布RefreshRoutesEvent
事件,通知CachingRouteLocator接口刷新gateway路由信息
灰度策略
基于nacos, 定义元数据信息
ReactiveLoadBalancerClientFilter + 配置微服务实例的元数据信息
为什么要做灰度路由?
本地开发或者新功能上线灰度验证时,可能仅有部分微服务实例拥有新功能特性,发送请求时,当请求header内包含灰度标识则转发到灰度服务实例进行接口处理
实现方式
本项目内注册中使用的是Nacos,nacos支持对微服务下实例配置元数据信息,我们可以将灰度标识配置在包含新特性的微服务项目实例的元数据之内,当转发请求时,判断请求header内是否包含灰度标识,若包含,则遍历微服务实例,若服务实例元数据内也包含灰度标识则进行转发
微服务内请求转发有两种场景:
- 前端请求通过网关进行转发
- feign接口调用通过ribbon 进行负载策略计算后进行转发,且通过feign接口调用会创建新的request对象,原request请求头内容不会传递
因此需要在此两种转发场景下实现微服务实例的选择
-
GateWay转发请求,实现
org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter
-
feign接口调用 实现
com.netflix.loadbalancer.AbstractLoadBalancerRule
-
实现
feign.RequestInterceptor
支持feign调用时header中灰度标识传递
限流
提供KeyResolver实现类,指定限流标识,根据不同的实现,可以针对不同url限流、对不同客户端限流或所有请求进行统一限流
@Bean(“ipKeyResolver”)
KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
@Bean(value = "apiKeyResolver")
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
在配置文件内对不同路由指定限流策略,底层是通过redis lua脚本实现令牌桶策略
spring:
cloud:
gateway:
default-filters:
- StripPrefix=1
routes:
- id: o001
uri: lb://order-service
predicates:
- Path=/api-a/**, /api-b/**
filters:
- name: RequestRateLimiter
args:
#每秒允许用户执行的请求数,而不丢弃任何请求。这是令牌桶的填充速率。
redis-rate-limiter.replenishRate: 1
#允许用户在一秒钟内完成的最大请求数。这是令牌桶可以容纳的令牌数。将此值设置为零将阻止所有请求。
redis-rate-limiter.burstCapacity: 2
#一个请求需要多少令牌。这是每个请求从存储桶中获取的令牌数,默认为1。
redis-rate-limiter.requestedTokens: 1
#使用的限流key生成策略
keyResolver: "#{@ipKeyResolver}"
#自定义状态码
#statusCode: INTERNAL_SERVER_ERROR
实现原理
- 实现
org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
接口,指定限流标识 - 在配置文件内对路由配置名为RequestRateLimiter的限流过滤器,在配置内指定令牌生成速率、 令牌桶大小、 请求每次消耗令牌数量、 KeyResolver接口实现类等信息
- 底层通过
RequestRateLimiterGatewayFilterFactory
对请求进行处理,通过keyResolver获取本次请求的限流标识,利用RedisRateLimter 通过lua脚本实现限流计算逻辑
Feign
微服务内想调用其他微服务的方法,则需要定义接口并用@FeignClient标注, 在注解内指明目标微服务名,同时在该接口内声明想要调用目标微服务下方法url
一般是服务提供方提供feign接口,供调用方引入
Feign是如何实现微服务间接口调用
JDK动态代理,对标注了@FeignCLient注解的接口在项目启动时都会为其声明一个代理对象注入上下文中
创建JDK代理对象需要实现InvocationHandler接口,可用于对方法实现代理增强操作,feign默认给出了FeignInvocationHandler,通过该代理对象完成接口调用,当在feign内开启hystrix时使用的是HystrixInvocationHandler
InvocationHandler#invoke方法入参包括了被代理接口当前请求的的目标方法,而FeignInvocationHandler内包含了一个Map类型属性叫dispatch, key是method 、value是MethodHandler接口的实现类;MethodHandler就是feign定义的一个普通接口,包含一个invoke方法,在MethodHandler.invoke方法内寻找服务提供方微服务实例列表,通过了负载均衡选择合适的远程server,然后通过client建了连接发送请求获取返回结果的
HystrixInvocationHandler和FeignInvocationHandler有什么区别?
1)在feign中开启了hystrix时为@FeignCLient注解标注的接口创建代理对象时才会使用HystrixInvocationHandler作为InvocationHandler的实现类
2)与FeignInvocationHandler相比其实就是在invoke方法内创建了一个抽象类HystrixCommad的实现类对象,需重新HystrixCommad的run和getFallback两个方法,在其run方法内从dispatch中通过method获取MethodHandler后调用invoke方法执行接口调用流程,在fallback方法中调用目标method的降级方法,最终通过HystrixCommad.execute完成接口调用
Feign请求时添加header?
feign接口调用时,会生成新的http请求,是不包含原始请求的请求header的,如果想要将请求中的header传递下去
可通过实现feign.RequestInterceptor
向新request内添加请求头
Hystrix
- 微服务请求间的熔断隔离有两种级别:线程池、信号量
默认为线程池隔离,为当前请求中的每一类feign调用创建一个单独的线程池,通过线程池中线程异步来调用下游接口,支持配置超时时间
为每一类feign调用设置信号量,每有一个请求到来信号量减一,信号量隔离是通过主线程完成对下游接口的调用,不支持配置超时及异步
-
引入Hystrix后每一次请求会生成AbstractCommand对象,该对象在hystrix内有哪两种实现?: HystrixCommand和HystrixObservableCommand
-
HystrixCommand和HystrixObservableCommand区别?
-
微服务调用引入hystrix后调用流程
Ribbon
可通过实现AbstractLoadBalancerRule抽象类,自定义feign调用时的负载均衡算法
AbstractLoadBalancerRule