springCloudGateway+nacos自定义负载均衡-通过IP隔离开发环境

先说一下想法,小公司开发项目,参考若依框架使用的spring-cloud-starter-gatewayspring-cloud-starter-alibaba-nacos, 用到了nacos的配置中心和注册中心,有多个模块(每个模块都是一个服务)。
想本地开发,且和其他同事不互相影响的情况下,有同事建议 本地部署 完整的环境(nacos、需要的模块、前端等),除了 数据库和redis共用。这种方案也能用,就是占用自己电脑资源太多了(ps 自己电脑摸鱼的硬件资源就少了)。
我通过看nacos相关的教程,发现可以通过 配置中心 和 注册中心 的namespace命名不同进行隔离,这样可以不用部署nacos服务。
继续分析发现,网关模块 默认的 负载均衡 ReactorServiceInstanceLoadBalancer 的实现 RoundRobinLoadBalancer 是通过轮询 访问服务的,可以 改造 负载均衡 来实现 本地开发环境最小化(只允许开发的服务模块),其他服务用 测试环境提供。
当前改造只适用请求通过网关,如果模块服务之间 直连调用,就需要在每个模块增加类似处理。暂不考虑

(ps 本人小白一枚springCloudGateway使用的少)本文章限定在这个项目里面,如果自己项目环境不一样,不保证能拿来直接用。

实现截图-不想看原理的先食用结果

在这里插入图片描述
在这里插入图片描述

参考

本来想通过搜索和AI找到解决方案,结果代码抄下来每一个能实现的。要么没有引用的类,要么无法实现自己的想法。所以就不贴了。
每个项目的依赖和版本情况都不一样,最好的方法是 找到框架的核心实现类,在跟踪调试和找到引用与初始化的地方。想办法自定义实现注入-这种方式最好,毕竟都是框架给留的口子。实在不行,就只能通过魔改或劫持的方式实现了。

原理图-设想

改造前请求流程

在这里插入图片描述
这种情况,适用于 非开发环境,简单高效

改造后请求流程

在这里插入图片描述
改造后的,适用于 本地开发,通过网关只请求自己的服务,或者自己没有只请求测试环境(简单的测试环境所有服务一台机器上)。尽最大可能不影响 其他同事开发。
图中没有画 锁定请求服务IP的情况(这块代码没测试)。

项目框架主要依赖

参考时候请注意 依赖包版本是否差距过大

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    <version>3.1.4</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2021.0.4.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2021.0.4.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2021.0.4.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
    <version>2021.0.4.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
    <version>3.1.5</version>
</dependency>

自定义实现

自定义负载均衡类

根据 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer 轮询策略 改造,这块代码倒还好实现。TestLocalLockIpLoadBalancer直接注入spring中,ObjectProvider<ServiceInstanceListSupplier> 获取不到服务列表,看了spring实现,要完全替换需要注入好几个类,有点麻烦。最后改成运行时替换。

自定义路由负载均衡,负载均衡策略:

  • 先根据请求IP查询IP相同的服务
  • 再找和网关IP相同的服务
  • 最后轮询其他IP服务
package *.*.*.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务
 *
 * @see  org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
 * @author z
 * @date 2025/1/10
 */
public class TestLocalLockIpLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Logger log = LoggerFactory.getLogger(TestLocalLockIpLoadBalancer.class);
    final AtomicInteger position;
    final String serviceId;
    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private String localIp;
    public TestLocalLockIpLoadBalancer setLocalIp(String localIp) { this.localIp = localIp; return this; }

    public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId) {
        this(provide, serviceId, new Random().nextInt(1000));
    }
    public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId, int seedPosition) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = provide;
        this.position = new AtomicInteger(seedPosition);
        System.out.println("自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务");
    }
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        System.out.println("自定义路由负载均衡---->");
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        String ip = getIpAddr(request);
        return supplier.get(request).next().map(services -> processInstanceResponse(supplier, services, ip));
    }
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier
            , List<ServiceInstance> services, String reqIp) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(services, reqIp);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, String reqIp) {
        System.out.println("自定义路由负载均衡--- 服务数量:" + instances.size());
        if (instances.isEmpty()) {
            log.warn("没有可供服务的服务器: {}, 请求: {}" , serviceId, reqIp);
            return new EmptyResponse();
        }
        if (instances.size() == 1) {
            System.out.println("自定义路由负载均衡--- 服务只有一个: " + instances.get(0).getHost());
            return new DefaultResponse(instances.get(0));
        }
        if( null != reqIp && !reqIp.isEmpty()){
            for (ServiceInstance instance : instances) {
                if(instance.getHost().equals(reqIp)){
                    System.out.println("自定义路由负载均衡--- 策略1: 自己处理 " + reqIp);
                    return new DefaultResponse(instance);
                }
            }
        }
        for (ServiceInstance instance : instances) {
            if(instance.getHost().equals(localIp)){
                System.out.println("自定义路由负载均衡--- 策略2: 测试处理 " + localIp);
                return new DefaultResponse(instance);
            }
        }
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = instances.get(pos % instances.size());
        System.out.println("自定义路由负载均衡--- 策略3: 默认轮询:" + instance.getHost());
        return new DefaultResponse(instance);
    }
    /** 获取客户端IP */
    private String getIpAddr(Request request) {
        DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
        RequestData clientRequest = (RequestData) requestContext.getClientRequest();
        HttpHeaders headers = clientRequest.getHeaders();
        if(headers.containsKey("sourceIp")){
            return headers.getFirst("sourceIp");
        }
        return null;
    }
}

自定义配置注入-偷天换日

最麻烦的事,是TestLocalLockIpLoadBalancer注入spring后,ObjectProvider<ServiceInstanceListSupplier> 获取不到服务列表。当前操作是运行时替换。在不考虑性能的情况下,这个只要 重写LoadBalancerClientFactory工厂类,改造获取 负载均衡 实例的方法就行。

注入的全局拦截器LockIpGlobalFilter,主要是获取 当前请求的 来源IP 放到请求头里面,因为 负载均衡的Request好像是获取不到IP信息。
自定义工厂类LockIpLoadBalancerClientFactory实现,重写获取负载均衡方法(替换为自定义类)

package *.*.*.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 路由过滤 优先 请求ip对应的服务实例
 *
 * @see org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter
 * @author z.
 * @date 2025/1/10
 */
@Configuration
@ConditionalOnProperty(value = "spring.cloud.lockIp", havingValue = "true")
public class TestLocalLockIpConfig {
    private static final Logger log = LoggerFactory.getLogger(TestLocalLockIpConfig.class);
    @Autowired
    private InetUtils inetUtils;
    @Bean
    public LockIpGlobalFilter lockIpGlobalFilter(){
        System.out.println(">>>>>>>>>自定义全局过滤器-注册");
        return new LockIpGlobalFilter();
    }
    @Bean
    public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,
                                                               ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
        System.out.println(">>>>>>>>>自定义路由工厂-注册");
        LockIpLoadBalancerClientFactory clientFactory = new LockIpLoadBalancerClientFactory(properties);
        clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
        return clientFactory.setLocalIp(inetUtils.findFirstNonLoopbackHostInfo().getIpAddress());
    }
    public static class LockIpGlobalFilter implements GlobalFilter, Ordered{
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            System.out.println("------->自定义全局过滤器: " + request.getURI().getPath());
            String ip = remoteIp(request);
            if(ip != null && !UNKNOWN.equals(ip)){
                ServerHttpRequest.Builder mutate = request.mutate();
                mutate.header("sourceIp", ip);
            }
            System.out.println("自定义全局过滤器-获取IP: " + ip);
            return chain.filter(exchange);
        }
        @Override
        public int getOrder() { return 0; }

        private static final String UNKNOWN = "unknown";
        public String remoteIp(ServerHttpRequest request){
            HttpHeaders headers = request.getHeaders();
            String def = null != request.getRemoteAddress() ? request.getRemoteAddress().getHostString() : UNKNOWN;
            String[] keys = {"x-forwarded-for","Proxy-Client-IP","X-Forwarded-For","WL-Proxy-Client-IP","X-Real-IP"};
            String ip = Arrays.stream(keys).map(headers::getFirst)
                    .filter(v-> v != null && !v.isEmpty() && !UNKNOWN.equalsIgnoreCase(v))
                    .findFirst().orElse(def);
            //本机请求 负载均衡 第二个策略 优先使用本机服务
            if( "0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip) ){ return null; }
            //从多级反向代理中获得第一个非unknown IP地址
            if (ip.indexOf(",") <= 0) {
                return Arrays.stream(ip.trim().split(","))
                        .filter(subIp -> !UNKNOWN.equalsIgnoreCase(subIp)).findFirst().orElse(ip);
            }
            return ip;
        }
    }
    public static class LockIpLoadBalancerClientFactory extends LoadBalancerClientFactory{
        /** 本网关服务 的内网IP */
        private String localIp;
        public LockIpLoadBalancerClientFactory setLocalIp(String localIp) { this.localIp = localIp; return this; }
        public LockIpLoadBalancerClientFactory(LoadBalancerClientsProperties properties){ super(properties); }
        @Override
        public <T> T getInstance(String name, Class<T> type) {
            AnnotationConfigApplicationContext context = getContext(name);
            try {
                T t = context.getBean(type);
                if(t instanceof TestLocalLockIpLoadBalancer){ return t; }
                if(t instanceof RoundRobinLoadBalancer){
                    System.out.println("自定义路由工厂-路由负载均衡策略: 默认轮询 "+t);
                    String[] beanName = context.getBeanNamesForType(type);
                    System.out.println("自定义路由工厂-路由负载均衡策略: 默认 删除第一个: "+ Arrays.toString(beanName));
                    context.removeBeanDefinition(beanName[0]);
                    System.out.println("自定义路由工厂-路由负载均衡策略: 自定义 创建");
                    TestLocalLockIpLoadBalancer balancer = new TestLocalLockIpLoadBalancer(
                            this.getLazyProvider(name, ServiceInstanceListSupplier.class), name).setLocalIp(localIp);
                    System.out.println("自定义路由工厂-路由负载均衡策略: 自定义 注入");
                    context.getBeanFactory().registerSingleton(beanName[0],balancer);
                    t = context.getBean(type);
                    System.out.println("自定义路由工厂-路由负载均衡策略: 注入后获取 " + t);
                }
                return t;
            } catch (Exception e) {
                log.error("自定义路由工厂", e);
                return null;
            }
        }
    }
}

配置文件-开关

通过配置,控制是否开启 自定义负载均衡

spring:
	cloud:
		lockIp: true

Spring网关源码分析

通过搜索和咨询AI,找到springCloudGateway处理服务负载均衡的拦截器org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter,核心代码是

	private final LoadBalancerClientFactory clientFactory;
	private final GatewayLoadBalancerProperties properties;
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		//判断是不是注册中心的服务地址
		if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		//其他代码略...
		URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		//微服务模块ID
		String serviceId = requestUri.getHost();
		//存活的服务列表
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
				.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
						RequestDataContext.class, ResponseData.class, ServiceInstance.class);
		//创建负载均衡函数choose入参的Request
		DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
				new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));
		//通过 负载均衡客户端工厂 获取 负载均衡配置
		LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);
		return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {
			//通过负载均衡获取到服务后的处理代码略...
		}).then(chain.filter(exchange)).doOnError(/*异常处理略*/).doOnSuccess(/*成功处理略*/);
	}
	private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,
			Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {
		//本次核心代码,通过 负载均衡客户端工厂 获取 负载均衡
		ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,
				ReactorServiceInstanceLoadBalancer.class);
		if (loadBalancer == null) {
			throw new NotFoundException("No loadbalancer available for " + serviceId);
		}
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		return loadBalancer.choose(lbRequest);
	}

拦截器获取 负载均衡ReactorLoadBalancer,是通过LoadBalancerClientFactory类的函数getInstance,这个类是 ReactiveLoadBalancerClientFilter构造入参。找到拦截器初始化的代码org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,是spring的自动注入配置类

	@Bean
	@ConditionalOnBean(LoadBalancerClientFactory.class)
	@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
	@ConditionalOnEnabledGlobalFilter
	public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory factory,
			GatewayLoadBalancerProperties properties) {
		return new ReactiveLoadBalancerClientFilter(factory, properties);
	}

那就找到注入LoadBalancerClientFactory类的代码。也没看到初始化负载均衡ReactorLoadBalancer<ServiceInstance>的代码

	@ConditionalOnMissingBean
	@Bean
	public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
		LoadBalancerClientFactory factory = new LoadBalancerClientFactory(properties);
		factory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
		return factory;
	}

下面就看到,挠头的配置类初始化方式

进入LoadBalancerClientFactory的构造函数,发现LoadBalancerClientConfiguration负载均衡客户端配置类。

	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
		super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
		this.properties = properties;
	}

LoadBalancerClientConfiguration负载均衡客户端配置类,请留意这里,这个配置类不是项目启动自动注入。也就是说,参考reactorServiceInstanceLoadBalancer函数自定义注入spring是不对的。就因为这个情况,卡了好几天,因为它是运行时初始化的。

//当设置为 false 时,Spring 不会为配置类生成代理对象。也就是不会自动注入配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment ev,
			LoadBalancerClientFactory factory) {
		//自定义ReactorLoadBalancer注入spring,name获取为null。实际这个应该是serviceId
		//获取配置参数 loadbalancer.client.name
		String name = ev.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer( factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}
//其他略
}

换个方向,分析函数getInstance,实现是在org.springframework.cloud.context.named.NamedContextFactory类里。看到这里,就能发现有一个map字段contexts,存着每个 微服务模块 的上下文AnnotationConfigApplicationContext。只有请求到对应 微服务模块 才初始化对应的 负载均衡类。
代码做了简化处理

    public <T> T getInstance(String serviceId, Class<T> type) {
        return (T)this.getContext(serviceId).getBean(type);
    }
    protected AnnotationConfigApplicationContext getContext(String serviceId) {
        if (!this.contexts.containsKey(serviceId)) {
            this.contexts.put(serviceId, this.createContext(serviceId));
        }
        return (AnnotationConfigApplicationContext)this.contexts.get(serviceId);
    }
    protected AnnotationConfigApplicationContext createContext(String serviceId) {
        AnnotationConfigApplicationContext context /*上下文获取 代码略...*/;
        //上下文配置注册 代码略...
        //-----核心代码,注入配置类 LoadBalancerClientConfiguration
        context.register(new Class[]{PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType});
        //-----核心代码,注入配置类 需要的 Environment 配置参数 loadbalancer.client.name
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, serviceId)));
        //其他 代码略...
        //上下文 刷新并启动,类似初始化-只能调用一次
        context.refresh();
        return context;
    }

通过上面的代码分析,spring通过运行时注入负载均衡,可能是考虑 网关无法感知到有多少微服务模块。
如果自定义负载均衡,正常的启动注入 每个微服务模块的负载均衡器,代码开发是比较麻烦的,需要明确列出所有的微服务,还需要考虑让负载均衡获取到存活的 服务列表。
所以最后 通过 覆盖函数getInstance,替换为自定义 负载均衡,这种代码量最简单粗暴(在不考虑性能的情况下)

测试日志

>>>>>>>>>自定义全局过滤器-注册
>>>>>>>>>自定义路由工厂-注册

------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: null
自定义路由工厂-路由负载均衡策略: 默认轮询 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer@1fc654d2
自定义路由工厂-路由负载均衡策略: 默认 删除第一个: [reactorServiceInstanceLoadBalancer]
自定义路由工厂-路由负载均衡策略: 自定义 创建
自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务
自定义路由工厂-路由负载均衡策略: 自定义 注入
自定义路由工厂-路由负载均衡策略: 注入后获取 *.*.*.config.TestLocalLockIpLoadBalancer@72e4d1fe
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:1
自定义路由负载均衡--- 服务只有一个: 192.168.1.10

------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: null
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:2
自定义路由负载均衡--- 策略2: 测试处理 192.168.1.17

------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: 192.168.1.10
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:2
自定义路由负载均衡--- 策略1: 自己处理 192.168.1.10

测试截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

END

用的公司项目测试,抱歉内容做了脱敏处理。
仅供参考-请结合实际情况使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/954531.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型训练_硬件微调知识增强

目录 关键硬件 大模型类型 垂域训练技术 微调技术 领域大模型训练trick 知识增强 关键硬件 GPU GPU擅长处理图形渲染和数据并行任务&#xff0c;可以同时处理大量的矩阵运算&#xff0c;在科学计算、人工智能、游戏开发等领域应用广泛。 显卡 显卡是一种完整的硬件设…

linux分配磁盘空间命令

使用命令lsblk查询linux磁盘空间时&#xff0c;发现空间并没有被分配完 如图&#xff0c;600G&#xff0c;但实际分配了一共199G&#xff0c;剩余500G&#xff0c;我们需要通过命令进行剩余存储的分配。 思路&#xff1a;创建新的分区->更新内核分区表->初始化新分区作…

【运维自动化-作业平台】魔法变量到底如何使用之主机列表类型

蓝鲸作业平台&#xff0c;以下简称作业平台或JOB平台 魔法变量&#xff1a;JOB平台执行引擎提供的特有的变量能力用法 脚本中使用&#xff0c;并且需要事先声明&#xff1a;job_import {{变量名}} 声明后&#xff0c;同样是使用 dollar 符 大括号&#xff1a;${变量名}来取值…

Windows重装后NI板卡LabVIEW恢复正常

在重新安装Windows系统后&#xff0c;NI&#xff08;National Instruments&#xff09;板卡能够恢复正常工作&#xff0c;通常是由于操作系统的重新配置解决了之前存在的硬件驱动、兼容性或配置问题。操作系统重装后&#xff0c;系统重新加载驱动程序、清理了潜在的冲突或损坏的…

Docker启动达梦 rman恢复

目录标题 1. 主库备份2. Docker启动备库3. 备库修改属组4. 开始恢复5. 连接数据库配置归档 & Open6. 检查数据 关于达梦数据库&#xff08;DMDBMS&#xff09;的主库备份、Docker启动备库、恢复备份以及配置归档和打开数据库的详细步骤。 1. 主库备份 # 使用达梦数据库备…

【C语言】_字符串拷贝函数strcpy

目录 1. 函数声明及功能 2. 使用示例 3. 注意事项 4. 模拟实现 4.1 第一版&#xff1a;基本功能判空const修饰 4.2 第二版&#xff1a;优化对于\0的单独拷贝 4.3 第三版&#xff1a;仿strcpy的char*返回值 1. 函数声明及功能 char * strcpy ( char * destination, cons…

XML序列化和反序列化的学习

1、基本介绍 在工作中&#xff0c;经常为了调通上游接口&#xff0c;从而对请求第三方的参数进行XML序列化&#xff0c;这里常使用的方式就是使用JAVA扩展包中的相关注解和类来实现xml的序列化和反序列化。 2、自定义工具类 import javax.xml.bind.JAXBContext; import javax.x…

js使用qrcode与canvas生成带logo的二维码

qrcode库 文档 https://www.npmjs.com/package/qrcode 安装 npm i qrcode 使用 errorCorrectionLevel: H // 容错率&#xff08;H是最高&#xff0c;其它看文档&#xff09; width: 200 // 大小 margin: 2 // 边距 import QRCode from qrcodeconst testFn async () > {c…

【计算机网络】lab5 ARP协议

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;计算机网络_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2.…

【React】脚手架进阶

目录 暴露webpack配置package.json的变化修改webpack.config.js配置less修改域名、端口号浏览器兼容处理处理跨域 暴露webpack配置 react-scripts对脚手架中的打包命令进行封装&#xff0c;如何暴露这些打包配置呢&#xff1f;上篇写到在package.json中的scripts配置项中有eje…

java项目之现代企业人力资源管理系统设计与实现(源码+文档)

大家好我是风歌&#xff0c;今天要和大家聊的是一款基于ssm的现代企业人力资源管理系统设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 现代企业人力资源管理系统设计与实现的主要使用者分为管理员、经理和普通员工三个角…

2025.1.15——三、报错注入

一、基本操作&#xff1a;整理已知信息&#xff0c;本题为报错注入&#xff0c;需进一步确认回显方式 二、用updatexml()解题步骤 step 1&#xff1a;依据回显方式判断题目类型 键入&#xff1a;1、1 and 11 、id2-1 得到&#xff1a;查询正确的回显 键入&#xff1a;1’、…

【IDEA 2024】学习笔记--文件选项卡

在我们项目的开发过程中&#xff0c;由于项目涉及的类过多&#xff0c;以至于我们会打开很多的窗口。使用IDEA默认的配置&#xff0c;个人觉得十分不便。 目录 一、设置多个文件选项卡按照文件字母顺序排列 二、设置多个文件选项卡分行显示 一、设置多个文件选项卡按照文件字…

自己动手搭建“接入 AI Agent 的数字人”

前言 本文的实战案例来自于开源项目&#xff1a;https://github.com/wan-h/awesome-digital-human-live2d。该项目可以运用Dify编排框架和Live2D驱动模型搭建智能数字人&#xff0c;实现智能对话并动态交互&#xff0c;大家可以自行部署尝试&#xff0c;项目效果如下。 开源&a…

Flink链接Kafka

一、基于 Flink 的 Kafka 消息生产者 Kafka 生产者的创建与配置&#xff1a; 代码通过 FlinkKafkaProducer 创建 Kafka 生产者&#xff0c;用于向 Kafka 主题发送消息。Flink 执行环境的配置&#xff1a; 配置了 Flink 的检查点机制&#xff0c;确保消息的可靠性&#xff0c;支…

电脑有两张网卡,如何实现同时访问外网和内网?

要是想让一台电脑用两张网卡&#xff0c;既能访问外网又能访问内网&#xff0c;那可以通过设置网络路由还有网卡的 IP 地址来达成。 检查一下网卡的连接 得保证电脑的两张网卡分别连到外网和内网的网络设备上&#xff0c;像路由器或者交换机啥的。 给网卡配上不一样的 IP 地…

dockerfile2.0

dockerfile实现lnmp nginx centos7 mysql centos7 php centos7 自定义镜像来实现整个架构 cd /opt mkdir nginx mysql php cd nginx 拖入nginx和wordpress vim Dockerfile vim nginx.conf ↓ worker_processes 1; events {worker_connections 1024; } http {include …

mysql-5.7.18保姆级详细安装教程

本文主要讲解如何安装mysql-5.7.18数据库&#xff1a; 将绿色版安装包mysql-5.7.18-winx64解压后目录中内容如下图&#xff0c;该例是安装在D盘根目录。 在mysql安装目录中新建my.ini文件&#xff0c;文件内容及各配置项内容如下图&#xff0c;需要先将配置项【skip-grant-tab…

【深度学习实战】kaggle 自动驾驶的假场景分类

本次分享我在kaggle中参与竞赛的历程&#xff0c;这个版本是我的第一版&#xff0c;使用的是vgg。欢迎大家进行建议和交流。 概述 判断自动驾驶场景是真是假&#xff0c;训练神经网络或使用任何算法来分类驾驶场景的图像是真实的还是虚假的。 图像采用 RGB 格式并以 JPEG 格式…

在Linux上如何让ollama在GPU上运行模型

之前一直在 Mac 上使用 ollama 所以没注意&#xff0c;最近在 Ubuntu 上运行发现一直在 CPU 上跑。我一开始以为是超显存了&#xff0c;因为 Mac 上如果超内存的话&#xff0c;那么就只用 CPU&#xff0c;但是我发现 Llama3.2 3B 只占用 3GB&#xff0c;这远没有超。看了一下命…