当前公司内微服务开发一般有两种模式
1.开发启动所有服务,注册中心,网关,认证服务等都在自己机器启动
优点:可以保证自己发送的请求(经过网关的请求或者通过fegin调用的请求)都能达到自己机器的服务
缺点:如果服务过多,电脑有可能会承受不住
2.开发只启动自己需要开发的服务,注册中心,网关,认证服务等都在一个公用的服务器启动,所有人启动的服务都注册到了这个公用服务上
优点:只需要启动自己开发的服务,方便快捷,节省电脑资源
缺点:由于自己开发的服务不一定只有自己启动了,有可能有别人同时启动了该服务,所以当从前端请求或者通过fegin请求时,可能请求会发送到了别人机器启动的服务上
模式2会有请求发送到别人机器对应服务的情况,具体情况明细图如下
图1
说明:
图1 通过网关请求后台服务A ,由于两个开发人员都启动了服务A并且注册到了公用注册中心上,使用默认负载均衡策略得话,此时请求到的不一定是小李的还是小张的电脑的服务A
图2
说明:
图二 通过fegin请求后台服务A ,由于两个开发人员都启动了服务A并且注册到了公用注册中心上,使用默认负载均衡策略得话,所以此时请求到的不一定是小李的还是小张的电脑的服务A
解决方案,由于上面问题都是在负载均衡获取实例时随机去到了一个实例,所以解决方式就是重写负载均衡路由指定获取特定的实例
网关端重写路由解决方案解决图1问题
代码,添加如下这几个类
CustomRibbonRule.java
public class CustomRibbonRule extends RoundRobinRule { private String version; public void setVersion(String version) { this.version = version; } @Override public Server choose(ILoadBalancer lb, Object key) { List<Server> targetList = null; List<Server> upList = lb.getReachableServers(); if (StrUtil.isEmpty(version)) { version = ReqeustHeaderContextHolder.getVersion(); } if (StrUtil.isNotEmpty(version)) { //取指定版本号的实例 targetList = upList.stream().filter( server -> version.equals( ((NacosServer) server).getMetadata().get("version") ) ).collect(Collectors.toList()); } if (CollUtil.isEmpty(targetList)) { //只取无版本号的实例 targetList = upList.stream().filter( server -> { String metadataVersion = ((NacosServer) server).getMetadata().get("version"); return StrUtil.isEmpty(metadataVersion); } ).collect(Collectors.toList()); } if (CollUtil.isNotEmpty(targetList)) { return getServer(targetList); } return super.choose(lb, key); } private Server getServer(List<Server> upList) { int nextInt = RandomUtil.randomInt(upList.size()); return upList.get(nextInt); } }
RibbonConfiguration.java
@Configuration public class RibbonConfiguration { @Value("${spring.cloud.nacos.discovery.metadata.version:#{null}}") private String version; @Bean public IRule defaultLBStrategy() { CustomRibbonRule customIsolationRule = new CustomRibbonRule(); customIsolationRule.setVersion(version); return customIsolationRule; } }
ReqeustHeaderContextHolder.java
public class ReqeustHeaderContextHolder { private static final FastThreadLocal fastThreadLocal = new FastThreadLocal(); public static void putVersion(String version){ fastThreadLocal.set(version); } public static String getVersion(){ return Objects.isNull(fastThreadLocal.get())?null:(String) fastThreadLocal.get(); } public static void clear(){ fastThreadLocal.remove(); } }
OrderedGatewayFilter.java
public class OrderedGatewayFilter implements GatewayFilter, Ordered { private final GatewayFilter delegate; private final int order; public OrderedGatewayFilter(GatewayFilter delegate, int order) { this.delegate = delegate; this.order = order; } public GatewayFilter getDelegate() { return delegate; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { try{ if(CollectionUtil.isNotEmpty(exchange.getRequest().getHeaders().get("version"))){ String version = exchange.getRequest().getHeaders().get("version").get(0); ReqeustHeaderContextHolder.putVersion(version); } return this.delegate.filter(exchange, chain); }finally { ReqeustHeaderContextHolder.clear(); } } @Override public int getOrder() { return this.order; } @Override public String toString() { return new StringBuilder("[").append(delegate).append(", order = ").append(order) .append("]").toString(); } }
fegin端重写路由解决方案解决图2问题
服务fegin的负载均衡路由,注册时将自己机器的服务A加上特定key属性注册到注册中心内,获取时优先获取有指定key的实例,没有找到再随机获取一个实例
代码:
各个应用部分可以添加如下类或者抽取出共通类
CustomRibbonRule.java
public class CustomRibbonRule extends RoundRobinRule { private String version; public void setVersion(String version) { this.version = version; } @Override public Server choose(ILoadBalancer lb, Object key) { List<Server> targetList = null; List<Server> upList = lb.getReachableServers(); if (StrUtil.isNotEmpty(version)) { //取指定版本号的实例 targetList = upList.stream().filter( server -> version.equals( ((NacosServer) server).getMetadata().get("version") ) ).collect(Collectors.toList()); } if (CollUtil.isEmpty(targetList)) { //只取无版本号的实例 targetList = upList.stream().filter( server -> { String metadataVersion = ((NacosServer) server).getMetadata().get("version"); return StrUtil.isEmpty(metadataVersion); } ).collect(Collectors.toList()); } if (CollUtil.isNotEmpty(targetList)) { return getServer(targetList); } return super.choose(lb, key); } private Server getServer(List<Server> upList) { int nextInt = RandomUtil.randomInt(upList.size()); return upList.get(nextInt); } }
RibbonConfiguration.java
@Configuration public class RibbonConfiguration { @Value("${spring.cloud.nacos.discovery.metadata.version:#{null}}") private String version; @Bean public IRule defaultLBStrategy() { CustomRibbonRule customIsolationRule = new CustomRibbonRule(); customIsolationRule.setVersion(version); return customIsolationRule; } }
然后对应yml里面添加自己对应的version
大功告成,看看效果
首先在需要走自己机器的服务上加上对应version,都启动后如下图
我们通过postman 模拟前端请求网关访问看看是不是走的我们指定的xiaoli 的实例
可以看到我们网关自定义的负载路由去到了version=xiaoli的实例
再来看看fegin调用的时候是不是也是走的我们指定version的实例
可以看order调用商品服务的时候也是找了商品服务version为xiaoli的实例,整明两个问题都解决了