前言:
本篇是上一篇《从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())); } }