使用Spring Cloud开发微服务应用时,开发阶段一般会是开发人员启动一个服务,服务器部署一个服务,这样一个实例会有多个服务并且分布在服务器和开发人员的地址,这样前端调试的时候可能会请求到我们开发人员自己的电脑,有时候还会导致结果不一致。本篇文章主要解决这个问题。
使用不同的命名空间或组 我们公司主要只用Nacos作为注册中心和配置中心,因为Nacos本身是命名空间隔离与组隔离,可以创建不同的命名空间和组,代码推上去后指定不同的命名空间或组即可。如下所示 我们可以在开发时指定为dev环境,当代码推送到服务器编译时指定为debug环境。 首先配置环境为一个变量
1 2 3 4 5 spring: profiles: active: ${profile} application: name: gateway
这样我们就可以将开发和调试隔离了,但是麻烦的地方就在于需要添加不同的配置,有点麻烦。
自定义负载均衡 除了根据nacos的特性,将名称空间或组隔离之外,还可以在网关中自定义负载均衡器。 Spring Cloud Gateway包含两种负载均衡器,RandomLoadBalancer(随机策略)和RoundRobinLoadBalancer(轮询策略),如果需要解决上面的问题。这两种负载均衡都是不合适的。因为都有可能将请求负载到开发人员的节点上。 所以我们需要自定义负载均衡。如何处理呢,首先开发的时候,我们希望通过网关负载到我们自己的电脑,调试的时候我们希望通过网关将节点负载到服务器。那其实就是获取本机IP来进行请求。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Slf4j @Profile("dev") @Component public class DevLoadBalancer { public static final String DOCKER = "docker0" ; @Autowired DiscoveryClient discoveryClient; @SneakyThrows public ServiceInstance choose (String serviceId, ServerHttpRequest request) { List<ServiceInstance> instances = discoveryClient.getInstances(serviceId); if (CollectionUtils.isEmpty(instances)) { log.warn("No instance available for {}" , serviceId); throw new NotFoundException ("No instance available for " + serviceId); } String local = getLocalIp(); for (ServiceInstance instance : instances) { if (instance.getHost().equals(local)){ log.info("负载到本地节点: {}" ,instance.getHost()); return instance; } } ServiceInstance instance = instances.get(RandomUtils.nextInt(instances.size())); log.warn("随机负载到集群,节点地址: {}" ,instance.getHost()); return instance; } public String getLocalIp () { try { for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) { NetworkInterface item = e.nextElement(); if (!item.getName().equals(DOCKER)) { for (InterfaceAddress address : item.getInterfaceAddresses()) { if (item.isLoopback() || !item.isUp()) { continue ; } if (address.getAddress() instanceof Inet4Address) { Inet4Address inet4Address = (Inet4Address) address.getAddress(); return inet4Address.getHostAddress(); } } } } return InetAddress.getLocalHost().getHostAddress(); } catch (SocketException | UnknownHostException e) { throw new RuntimeException (e); } } }
通过Nacos获取到所有的ServiceInstance,然后遍历所有的ServiceInstance判断instance的host是否与本机相同。如果相同则返回当前instance,如果所有服务的host都没有与本机相同的地址,那么直接使用随机负载即可。
然后我们需要继承自ReactiveLoadBalancerClientFilter过滤器,并重写filter方法,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Slf4j @Component @Profile("dev") public class DevReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { @Autowired DevLoadBalancer loadBalancer; public DevReactiveLoadBalancerClientFilter (LoadBalancerClientFactory clientFactory, GatewayLoadBalancerProperties properties) { super (clientFactory, properties); } private Mono<Response<ServiceInstance>> choose (ServerWebExchange exchange) { URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); Assert.notNull(uri,"获取应用失败..." ); if (uri.getHost().equals("localhost" )){ return Mono.just(new DefaultResponse (new DefaultServiceInstance (uri.getHost(),uri.getPath(),uri.getHost(),uri.getPort(),false ))); } ServiceInstance serviceInstance = loadBalancer.choose(uri.getHost(), exchange.getRequest()); return Mono.just(new DefaultResponse (serviceInstance)); } @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { return choose(exchange).doOnNext(response -> { URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); Assert.notNull(url,"暂无服务可用..." ); URI uri = exchange.getRequest().getURI(); String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); ServiceInstance retrievedInstance = response.getServer(); String overrideScheme = retrievedInstance.isSecure() ? "https" : "http" ; if (schemePrefix != null ) { overrideScheme = url.getScheme(); } DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance ( response.getServer(), overrideScheme); URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); }).then(chain.filter(exchange)); } }
当前配置只在dev环境生效。我们看看效果
这种方案不需要多余的配置,唯一的限制就是网关服务需要和业务服务放在一台设备上,开发的时候都是这样部署的,没有影响。