• SpringCloud Naocs 自定义负载均衡配置


    SpringCloud Naocs 自定义负载均衡配置

    场景:本地开发中,所有开发人员都启动了多个微服务,在进行远程调用时,可能请求的是其他开发人员的服务,导致本地联调debug效率低下。

    SpringCloud Naocs 提供的负载均衡策略

    SpringCloud新版本中负载均衡器由原来的ribbon替换为Spring自己开发的Loadbalancer,默认只提供了2中负载均衡策略:RandomLoadBalancerRoundRobinLoadBalancer。SpringCloud Alibaba Nacos 2021.1版本提供了基于Nacos注册中心的轮询策略 NacosLoadBalancer 是基于权重的策略。

    NacosLoadBalancer的权重策略默认是关闭的,如果要使用基于权重的负载策略要手动开启。

    如果未给服务器设置权重,建议不要使用基于权重的策略,因为如果微服务的权重都相同,相当于随机

    修改配置文件配置

    #开启nacos的负载均衡策略
    spring.cloud.loadbalancer.nacos.enabled=true
    
    • 1
    • 2

    基于NacosLoadBalancer扩展功能

    /**
     * 修改Nacos的负载均衡策略,主要是为了将请求路由到本地服务中,方便开发
     * @author guojunwang
     * @date 2022-05-18 14:17
     */
    public class CustomNacosLoadBalancer extends NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    
        @Value("${spring.profiles.active}")
        private String profilesActive;
    
        private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class);
    
        private final String serviceId;
    
        private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    
        private final NacosDiscoveryProperties nacosDiscoveryProperties;
    
        public CustomNacosLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                       String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
            super(serviceInstanceListSupplierProvider,serviceId,nacosDiscoveryProperties);
            this.serviceId = serviceId;
            this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
            this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        }
    
        @Override
        public Mono<Response<ServiceInstance>> choose(Request request) {
            ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                    .getIfAvailable(NoopServiceInstanceListSupplier::new);
            return supplier.get().next().map(this::getInstanceResponse);
        }
    
        private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
            if (serviceInstances.isEmpty()) {
                log.warn("No servers available for service: " + this.serviceId);
                return new EmptyResponse();
            }
    
            try {
                String clusterName = this.nacosDiscoveryProperties.getClusterName();
    
                List<ServiceInstance> instancesToChoose = serviceInstances;
    
                if (StringUtils.isNotBlank(clusterName)) {
                    List<ServiceInstance> sameClusterInstances = serviceInstances.stream()
                            .filter(serviceInstance -> {
                                String cluster = serviceInstance.getMetadata()
                                        .get("nacos.cluster");
                                return StringUtils.equals(cluster, clusterName);
                            }).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                        instancesToChoose = sameClusterInstances;
                    }
                } else {
                    log.warn(
                            "A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
                            serviceId, clusterName, serviceInstances);
                }
    
                //本地开发模式路由会本机
                if(StrUtil.equals(SpringUtil.getProperty("spring.profiles.active"),"dev")){
    
                    //如果有本机的服务,优先选择本地服务
                    List<ServiceInstance> localServiceInstance = instancesToChoose.stream().filter(i -> IpUtil.isLocal(i.getHost())).collect(Collectors.toList());
                    //使用本地服务进行选举
                    if (CollUtil.isNotEmpty(localServiceInstance)) {
                        instancesToChoose = localServiceInstance;
                        log.info("开发模式,负载均衡器优先选择本机服务调用==>{}", instancesToChoose.stream().map( is ->{
                            String msg =" serviceId=%s,host=%s,port=%s ";
                            return String.format(msg, is.getServiceId(), is.getHost(), is.getPort());
                        }).collect(Collectors.toList()));
                    }
                }
    
                //权重选取
                ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);
                return new DefaultResponse(instance);
            }
            catch (Exception e) {
                log.warn("NacosLoadBalancer error", e);
                return null;
            }
    
        }
    
    }
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    用到的IpUtil工具类

    需要在pom加入依赖

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <optional>true</optional>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <optional>true</optional>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @Slf4j
    public class IpUtil {
    
        /**
         * localAddresses 减少运行耗时,初始化时吧本机地址缓存起来
         * 缺点,网络改变时,需要重启
         */
        private static List<String> localAddresses = new ArrayList<>();
        static {
            try {
                Enumeration<NetworkInterface> enumNI = NetworkInterface.getNetworkInterfaces();
                while ( enumNI.hasMoreElements() ){
                    NetworkInterface ifc = enumNI.nextElement();
                    if( ifc.isUp() ){
                        Enumeration<InetAddress> enumAdds = ifc.getInetAddresses();
                        while ( enumAdds.hasMoreElements() ){
                            InetAddress addr = enumAdds.nextElement();
                            localAddresses.add(addr.getHostAddress());
                        }
                    }
                }
                localAddresses.add("localhost");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 判断是否同一个地址,无论是主机名还是ip地址
         * @param host1
         * @param host2
         * @return
         */
        public static boolean sameAddress(String host1,String host2){
            if(StrUtil.isBlank(host1) || StrUtil.isBlank(host2)){
                return false;
            }
            try {
                if(isLocal(host1)){
                    host1 = InetAddress.getLocalHost().getHostName();
                }
                if(isLocal(host2)){
                    host2 = InetAddress.getLocalHost().getHostName();
                }
                InetAddress addr1 = InetAddress.getByName(host1);
                InetAddress addr2 = InetAddress.getByName(host2);
                return addr1.getHostAddress().equals(addr2.getHostAddress());
            } catch (UnknownHostException e) {
                e.printStackTrace();
                return false;
            }
        }
    
        public static boolean isLocal(String host){
            return localAddresses.contains(host);
        }
    
        /***
         * 获取客户端ip地址(可以穿透代理)
         * @param request
         * @return
         */
        public static String getClientIpAddr(HttpServletRequest request) {
            String ip = request.getHeader("X-Forwarded-For");
            if (!isValidIp(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_FORWARDED_FOR");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_FORWARDED");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("HTTP_VIA");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeader("REMOTE_ADDR");
            }
            if (!isValidIp(ip)) {
                ip = request.getRemoteAddr();
            }
            return ip;
        }
    
        public static String getClientIpAddr(ServerHttpRequest request) {
            String ip = request.getHeaders().getFirst("X-Forwarded-For");
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("Proxy-Client-IP");
            }
            if (!isValidIp(ip)) {
                ip =request.getHeaders().getFirst("WL-Proxy-Client-IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_X_FORWARDED");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_X_CLUSTER_CLIENT_IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_FORWARDED_FOR");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_FORWARDED");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("HTTP_VIA");
            }
            if (!isValidIp(ip)) {
                ip = request.getHeaders().getFirst("REMOTE_ADDR");
            }
            if (!isValidIp(ip)) {
                ip = request.getRemoteAddress().getHostName();
            }
            return ip;
        }
    
        private static boolean isValidIp(String ip){
            return !(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip));
        }
    
        /**
         * 获取tomcat内置服务监听端口
         * @return
         */
        public static int getTomcatPort() {
            try{
                MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
                Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
                        Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
                String port = objectNames.iterator().next().getKeyProperty("port");
                return Integer.valueOf(port);
            }catch (Exception e){
                log.error("get tomcat port fail", e);
            }
            return 0;
        }
    
    }
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160

    还需要编写配置类:

    @LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
    public class NacosLoadBalancerConfig {
    
        @Bean
        public ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment,
                LoadBalancerClientFactory loadBalancerClientFactory,NacosDiscoveryProperties configProperties)
        {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            return new CustomNacosLoadBalancer(loadBalancerClientFactory
                    .getLazyProvider(name, ServiceInstanceListSupplier.class), name,configProperties);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    【开源讲解】
    [附源码]java毕业设计心理问题咨询预约系统
    【问题记录与解决】jupyter notebook 无法重命名,无法运行测试代码 || jupyter notebook 中常用的两个快捷键。
    PHPer 开始使用 Java
    美的、格力逆境求生
    论文阅读:SOLOv2: Dynamic, Faster and Stronger
    Java8实战-总结33
    30.2.1 使用CREATE USER语句创建用户
    Python使用.NET开发的类库来提高你的程序执行效率
    六.方法与接口
  • 原文地址:https://blog.csdn.net/dndndnnffj/article/details/125493695