现状
原理
网关的大致实现原理如下图:
问题
当某一天,服务1
扩展了一个新功能, 但这一部分功能只针对某一部分用户开发,抽象来说就是:请求方直接圈定了具体的服务节点
,该如何实现呢?
基于 LoadBalancerClientFilter +Ribbon 实现
集成
maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-nacos-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!--"Hoxton.SR3": "Spring Boot >=2.2.0.M4 and <2.3.0.BUILD-SNAPSHOT" -->
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
properties配置
spring:
application:
name: my-gateway
cloud:
nacos:
discovery:
server-addr: YOUR_IP:8848
register-enabled: false
group: DEFAULT_GROUP
config:
server-addr: YOUR_IP:8848
file-extension: yaml
group: DEFAULT_GROUP
gateway:
enabled: true
discovery:
locator:
## 注册发现
enabled: true
lower-case-service-id: true
配置NacosRule
@Bean
@Scope(value = "prototype")
public IRule nacosRule() {
return new NacosRule();
}
注册发现源码分析
xxxAutoConfiguration
//org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration
public class GatewayLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClient.class)
@ConditionalOnMissingBean({ LoadBalancerClientFilter.class,
ReactiveLoadBalancerClientFilter.class })
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
}
//org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
public class RibbonAutoConfiguration {
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
}
源码分析
//org.springframework.cloud.gateway.filter.LoadBalancerClientFilter
//1.
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
//如果url前缀不是 lb:// 直接返回
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
//1.1
final ServiceInstance instance = choose(exchange);
URI uri = exchange.getRequest().getURI();
//替换url
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
//略.....
return chain.filter(exchange);
}
//1.1 从注册中心选择 实例
//loadBalancer 为: org.springframework.cloud.client.loadbalancer.LoadBalancerClient 实例: RibbonLoadBalancerClient
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
}
//2.
public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
//2.1
//2.2
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
//2.1 返回 com.netflix.loadbalancer.ILoadBalancer 的实现类 DynamicServerListLoadBalancer, 并设置serviceId
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
//2.2 通过DynamicServerListLoadBalancer 获取服务实例
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
}
//3. DynamicServerListLoadBalancer.chooseServer
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
//public Server chooseServer(Object key) 由父类实现
}
//4. com.netflix.loadbalancer.BaseLoadBalancer
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//rule 由容器注入NacosRule
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
//5. com.alibaba.cloud.nacos.ribbon.NacosRule(略)
// 根据NameService查找服务实例,再通过自身的负载均衡算法 选取节点
扩展
扩展LoadBalancerClientFilter
拦截请求头
中的accept_version_id
,并封装为自定义对象,由后续的负载均衡策略使用。
public class GrayReleaseRibbonLoadBalancerClientFilter extends LoadBalancerClientFilter {
private static final String ACCEPT_VERSION_ID_HEADER_KEY = "accept_version_id";
private boolean support;
public GrayReleaseRibbonLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
support = loadBalancer instanceof RibbonLoadBalancerClient;
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
if (support) {
List<String> acceptVersionIds = exchange.getRequest().getHeaders().get(ACCEPT_VERSION_ID_HEADER_KEY);
return ((RibbonLoadBalancerClient) loadBalancer).choose(serviceId, new GrayReleaseKey(acceptVersionIds));
}
return loadBalancer.choose(serviceId);
}
public static class GrayReleaseKey {
private static final List<String> EMPTY_VERSION_LIST = Collections.emptyList();
@Getter
private List<String> acceptVersionIds;
public GrayReleaseKey(List<String> acceptVersionIds) {
this.acceptVersionIds = acceptVersionIds == null ? EMPTY_VERSION_LIST : acceptVersionIds;
}
}
}
重写NacosRule
通过key
携带的版本信息,进一步筛选服务实例,从而达到灰度发布
的效果。
public class GrayReleaseNacosRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties
.namingServiceInstance();
List<Instance> instances = namingService.selectInstances(name, true);
if (CollectionUtils.isEmpty(instances)) {
return null;
}
List<Instance> instancesToChoose = instances;
//根据clusterName筛选服务(略...)
//自定义实现: 根据传递来的版本号
instancesToChoose = chooseInternel(key, instancesToChoose);
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
protected List<Instance> chooseInternel(Object key, List<Instance> instancesToChoose) {
List<Instance> versionInstanceToChoose = instancesToChoose;
List<String> candidateVersionIds;
if (key instanceof GrayReleaseRibbonLoadBalancerClientFilter.GrayReleaseKey && !(candidateVersionIds = ((GrayReleaseRibbonLoadBalancerClientFilter.GrayReleaseKey) key).getAcceptVersionIds()).isEmpty()) {
List<Instance> sameVersionInstances = instancesToChoose
.stream()
.filter(item -> {
String versionId = item.getMetadata().get(VERSION_ID_METADATA_KEY);
return candidateVersionIds.contains(versionId);
})
.collect(Collectors.toList());
if (!sameVersionInstances.isEmpty()) {
versionInstanceToChoose = sameVersionInstances;
}
}
return versionInstanceToChoose;
}
}
注入扩展实现类
@Bean
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new GrayReleaseRibbonLoadBalancerClientFilter(client, properties);
}
@Bean
@Scope(value = "prototype")
public IRule nacosRule() {
//重写 NacosRule
return new GrayReleaseNacosRule ();
}
服务注册-设置metadata
在服务注册到nacos注册中心时,添加metadata._version_id
用来标识版本号。
spring:
application:
name: authentication-center-service
cloud:
nacos:
discovery:
metadata:
_version_id: v_20210114
请求
请求头中指定版本信息。
基于 ReactiveLoadBalancerClientFilter 实现(待续)
springcloud gateway 基于WebFlux
实现,推荐使用ReactiveLoadBalancerClientFilter
来实现负载均衡策略。