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

基于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;
        }

    }

}

用到的IpUtil工具类

需要在pom加入依赖

<dependency>
    <groupId>javax.servletgroupId>
    <artifactId>servlet-apiartifactId>
    <version>2.5version>
    <optional>trueoptional>
dependency>

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <optional>trueoptional>
dependency>
@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;
    }

}

还需要编写配置类:

@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);
    }
}

你可能感兴趣的:(SpringCloud,spring,cloud,负载均衡,java)