前言:
本篇是上一篇《从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(七) 开发环境使用轻量级在线文档解决知识分享问题》的优化篇
原来是基于下图,分散处理的,分散在各个代码层并且不是统一获取version对应的instance
本篇将集成为两个共通,统一通过client 端设置version 依次传递 下去,然后各个层通过lb获取version对应的instance,该优化是借鉴了其他一些架构,觉得不错,引入进来了,如下图
源码部分
新增两个共通模块mini-cloud-common-gateaway,mini-cloud-balancer ,目录结构如下
mini-cloud-gateaway 源码
mini-cloud-common-gateaway 代码结构以及代码明细,将作为共通被mini-cloud-gateaway 使用
pom.xml dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--gateway 网关依赖,内置webflux 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
EnableMiniCloudRoute.java 主要作为开启引用MiniCloudRouteAutoConfiguration 自定义路由的开关,不然是无法扫描到 MiniCloudRouteAutoConfiguration 类的
package com.minicloud.common.gateaway.annotation;
import com.minicloud.common.gateaway.config.MiniCloudRouteAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(MiniCloudRouteAutoConfiguration.class)
public @interface EnableMiniCloudRoute {
}
MiniCloudRouteAutoConfiguration.java 主要是注入后续源码的扫描包,便于引用项目扫面到
package com.minicloud.common.gateaway.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.minicloud.common.gateaway")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MiniCloudRouteAutoConfiguration {
}
MiniCloudLoadBalancerClientConfiguration.java如果是使用 REACTIVE 作为web引擎,则可以初始化
MiniCloudReactiveLoadBalancerClientFilter和 MiniCloudLoadBalancer 两个类MiniCloudReactiveLoadBalancerClientFilter :主要是重写 ReactiveLoadBalancerClientFilter 获取request中的version
VersionMiniCloudLoadBalancer: 主要是获取到version 后获取version 对应lb中的instance
import com.minicloud.common.gateaway.filter.MiniCloudReactiveLoadBalancerClientFilter;
import com.minicloud.common.gateaway.rule.MiniCloudLoadBalancer;
import com.minicloud.common.gateaway.rule.VersionMiniCloudLoadBalancer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(LoadBalancerProperties.class)
@AutoConfigureBefore(GatewayReactiveLoadBalancerClientAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MiniCloudLoadBalancerClientConfiguration {
@Bean
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(MiniCloudLoadBalancer miniCloudLoadBalancer,
LoadBalancerProperties properties) {
return new MiniCloudReactiveLoadBalancerClientFilter(properties, miniCloudLoadBalancer);
}
@Bean
public MiniCloudLoadBalancer grayLoadBalancer(DiscoveryClient discoveryClient) {
return new VersionMiniCloudLoadBalancer(discoveryClient);
}
}
MiniCloudReactiveLoadBalancerClientFilter.java
@Slf4j
public class MiniCloudReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private LoadBalancerProperties properties;
private MiniCloudLoadBalancer miniCloudLoadBalancer;
public MiniCloudReactiveLoadBalancerClientFilter(LoadBalancerProperties properties, MiniCloudLoadBalancer miniCloudLoadBalancer) {
super(null, properties);
this.properties = properties;
this.miniCloudLoadBalancer = miniCloudLoadBalancer;
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName()
+ " url before: " + url);
}
return choose(exchange).doOnNext(response -> {
if (!response.hasServer()) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = null;
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(
response.getServer(), overrideScheme);
URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
}).then(chain.filter(exchange));
}
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
ServiceInstance serviceInstance = miniCloudLoadBalancer.choose(uri.getHost(),exchange.getRequest());
return Mono.just(new DefaultResponse(serviceInstance));
}
MiniCloudLoadBalancer
package com.minicloud.common.gateaway.rule;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.server.reactive.ServerHttpRequest;
public interface MiniCloudLoadBalancer {
ServiceInstance choose(String serviceId, ServerHttpRequest request);
}
VersionMiniCloudLoadBalancer.java
@Slf4j
@AllArgsConstructor
public class VersionMiniCloudLoadBalancer implements MiniCloudLoadBalancer {
private DiscoveryClient discoveryClient;
@Override
public ServiceInstance choose(String serviceId, ServerHttpRequest request) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
//注册中心无实例 抛出异常
if (CollUtil.isEmpty(instances)) {
log.warn("No instance available for {}", serviceId);
throw new NotFoundException("No instance available for " + serviceId);
}
// 获取请求version,无则随机返回可用实例
String reqVersion = request.getHeaders().getFirst("version");
if (StrUtil.isBlank(reqVersion)) {
return instances.get(RandomUtil.randomInt(instances.size()));
}
// 遍历可以实例元数据,若匹配则返回此实例
for (ServiceInstance instance : instances) {
Map<String, String> metadata = instance.getMetadata();
String targetVersion = MapUtil.getStr(metadata, "version");
if (reqVersion.equalsIgnoreCase(targetVersion)) {
log.info("gray requst match success :{} {}", reqVersion, instance);
return instance;
}
}
return instances.get(RandomUtil.randomInt(instances.size()));
}
}
mini-cloud-common-balancer 源码
mini-cloud-common-balancer 代码结构以及代码明细,将作为共通被各个业务端使用
pom.xml
<dependencies>
<dependency>
<groupId>org.mini-cloud</groupId>
<artifactId>mini-cloud-common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
</dependencies>
spring.factories spring-boot 约定文件,也就是传说的“约定大于配置”
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.minicloud.common.balancer.config.MiniCloudRibbonLoadBalancerConfiguration
MiniCloudRibbonLoadBalancerConfiguration.java主要为了注入bean: MiniCloudRibbonLoadBalancerRule ,RequestInterceptor
import com.minicloud.common.balancer.fegin.MiniCloudFeignRequestInterceptor;
import com.minicloud.common.balancer.rule.MiniCloudRibbonLoadBalancerRule;
import feign.RequestInterceptor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class MiniCloudRibbonLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MiniCloudRibbonLoadBalancerRule ribbonLoadBalancerRule() {
return new MiniCloudRibbonLoadBalancerRule();
}
@Bean
public RequestInterceptor grayFeignRequestInterceptor() {
return new MiniCloudFeignRequestInterceptor();
}
}
MiniCloudFeignRequestInterceptor.java 为了拦截fegin请求并设置上游发来的 version
@Slf4j
public class MiniCloudFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String reqVersion = WebUtils.getRequest() != null
? WebUtils.getRequest().getHeader("version") : null;
if (StrUtil.isNotBlank(reqVersion)) {
log.debug("feign gray add header version :{}", reqVersion);
template.header("version", reqVersion);
}
}
MiniCloudRibbonLoadBalancerRule.java 自定义使用端ribbon loadbalance 路由
@Slf4j
public class MiniCloudRibbonLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
List<Server> reachableServers = lb.getReachableServers();
//注册中心无可用实例 抛出异常
if (CollUtil.isEmpty(reachableServers)) {
log.warn("No instance available for {}", key);
return null;
}
// 获取请求version,无则随机返回可用实例
String reqVersion = WebUtils.getRequest() != null
? WebUtils.getRequest().getHeader("version") : null;
if (StrUtil.isBlank(reqVersion)) {
return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
}
// 遍历可以实例元数据,若匹配则返回此实例
for (Server server : reachableServers) {
NacosServer nacosServer = (NacosServer) server;
Map<String, String> metadata = nacosServer.getMetadata();
String targetVersion = MapUtil.getStr(metadata,"version");
if (reqVersion.equalsIgnoreCase(targetVersion)) {
log.debug("gray requst match success :{} {}", reqVersion, nacosServer);
return nacosServer;
}
}
return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
}
}