一、Spring Cloud LoadBalancer介绍

Spring Cloud LoadBalancer是Spring Cloud官网提供的一个客户端负载均衡器,功能类似于Ribbon。在Spring Cloud Nacos 2021移除了中Ribbon组件,Spring Cloud在Spring Cloud Commons项目中,添加了Spring Cloud Loadbalancer作为新的负载均衡器。

org.springframework.cloud

spring-cloud-loadbalancer

二、LoadBalancer负载均衡策略

LoadBalancer的负载均衡策略没有 Ribbon 那么丰富,只提供了RandomLoadBalancer、NacosLoadBalancer、RoundRobinLoadBalancer,在不指定的时候默认使用RoundRobinLoadBalancer轮询负载均衡策略,此三种策略都是对接口ReactiveLoadBalancer 进行实现。

RandomLoadBalancer :基于随机访问的负载均衡策略NacosLoadBalancer:基于Nacos权重的负载均衡策略RoundRobinLoadBalancer:基于轮询的负载均衡策略(默认无需特殊配置)

三、切换RandomLoadBalancer 负载均衡策略

参考: https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#rest-template-loadbalancer-client

https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#custom-loadbalancer-configuration

基于上篇文章Spring Cloud融合Nacos实现服务的注册与发现 | Spring Cloud 4,服务提供者进行沿用,服务调用者进行新增模块。

完整目录结构如下:

在文章下方附完整示例代码。

服务调用者新增CustomRandomLoadBalancerConfiguration.java:

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.core.env.Environment;

@LoadBalancerClients(defaultConfiguration = CustomRandomLoadBalancerConfiguration.class)

public class CustomRandomLoadBalancerConfiguration {

@Bean

ReactorLoadBalancer randomLoadBalancer(Environment environment,

LoadBalancerClientFactory loadBalancerClientFactory) {

String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

return new RandomLoadBalancer(loadBalancerClientFactory

.getLazyProvider(name, ServiceInstanceListSupplier.class),

name);

}

}

改造原有服务提供者的ConsumerController,记录访问次数:

import com.gm.nacos_discovery_http_consumer_randomloadbalancer.service.ProviderServiceFeign;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

import java.util.Optional;

import java.util.concurrent.atomic.AtomicInteger;

@RestController

public class ConsumerController {

@Autowired

ProviderServiceFeign providerServiceFeign;

Map synchronizedHashMap = Collections.synchronizedMap(new HashMap());

@RequestMapping(value = "sayHello", method = RequestMethod.GET)

public String sayHello() {

String result = providerServiceFeign.sayHello("hello world");

AtomicInteger ai = synchronizedHashMap.get(result);

Optional opt = Optional.ofNullable(ai);

if (!opt.isPresent()) {

ai = new AtomicInteger(1);

synchronizedHashMap.put(result, ai);

} else {

ai.getAndIncrement();

}

return synchronizedHashMap.toString();

}

}

多次请求,效果如下:

四、切换NacosLoadBalancer负载均衡策略

4.1 服务分级存储模型

一个服务可以有多个集群,一个集群可以有多个实例

微服务互相访问时,应该尽可能访问同集群实例,当本集群内不可用时,才访问其它集群

为演示方便及后续根据服务选择不同的负载均衡策略,选择新增模块(一个服务调用者,两个服务提供者,其中服务提供者代码逻辑完全相关,配置文件在cluster-name属性略显差异,而实际业务运行中可以通过启动参数-Dspring.cloud.nacos.discovery.cluster-name进行制定)

4.2 服务调用者配置

完整目录结构如下:

服务调用者新增CustomNacosLoadBalancerConfiguration.java:

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;

import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.core.env.Environment;

import javax.annotation.Resource;

@LoadBalancerClients(defaultConfiguration = CustomNacosLoadBalancerConfiguration.class)

public class CustomNacosLoadBalancerConfiguration {

@Resource

NacosDiscoveryProperties nacosDiscoveryProperties;

@Bean

public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {

String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties);

}

}

配置文件bootstrap.yml新增cluster-name配置完整如下:

server:

port: 3100

spring:

application:

name: @artifactId@

cloud:

nacos:

username: @nacos.username@

password: @nacos.password@

discovery:

server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos2kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos3.kc}:${NACOS_PORT:8848}

cluster-name: haerbin

或是通过VM options配置:

-Dspring.cloud.nacos.discovery.cluster-name=haerbin

4.3 服务提供者配置

配置文件bootstrap.yml新增cluster-name配置完整如下:

server:

port: 4000

spring:

application:

name: nacos-discovery-http-provider-cluster

cloud:

nacos:

username: @nacos.username@

password: @nacos.password@

discovery:

server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos2kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos3.kc}:${NACOS_PORT:8848}

cluster-name: haerbin

management:

endpoints:

web:

exposure:

include: '*'

或是通过VM options配置:

-Dspring.cloud.nacos.discovery.cluster-name=beijing

查看nacos集群变化:

4.4 测试效果

同集群优先测试:

多次访问服务调用者,均是请求同一集群下的服务提供者

通过手动停止提供者服务(同集群的),再访问服务调用者:

同集群内不可用时,才访问其它集群的服务提供者

4.5 NacosLoadBalancer源码

package com.alibaba.cloud.nacos.loadbalancer;

import com.alibaba.cloud.commons.lang.StringUtils;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;

import com.alibaba.cloud.nacos.balancer.NacosBalancer;

import com.alibaba.nacos.client.naming.utils.CollectionUtils;

import java.util.List;

import java.util.stream.Collectors;

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.DefaultResponse;

import org.springframework.cloud.client.loadbalancer.EmptyResponse;

import org.springframework.cloud.client.loadbalancer.Request;

import org.springframework.cloud.client.loadbalancer.Response;

import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;

import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

public class NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class);

private final String serviceId;

private ObjectProvider serviceInstanceListSupplierProvider;

private final NacosDiscoveryProperties nacosDiscoveryProperties;

public NacosLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {

this.serviceId = serviceId;

this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;

this.nacosDiscoveryProperties = nacosDiscoveryProperties;

}

public Mono> choose(Request request) {

ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);

return ((Flux)supplier.get()).next().map(this::getInstanceResponse);

}

private Response getInstanceResponse(List serviceInstances) {

if (serviceInstances.isEmpty()) {

log.warn("No servers available for service: " + this.serviceId);

return new EmptyResponse();

} else {

try {

String clusterName = this.nacosDiscoveryProperties.getClusterName();

List instancesToChoose = serviceInstances;

if (StringUtils.isNotBlank(clusterName)) {

List sameClusterInstances = (List)serviceInstances.stream().filter((serviceInstance) -> {

String cluster = (String)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 = {}", new Object[]{this.serviceId, clusterName, serviceInstances});

}

ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);

return new DefaultResponse(instance);

} catch (Exception var5) {

log.warn("NacosLoadBalancer error", var5);

return null;

}

}

}

}

五、灵活控制负载均衡策略

5.1 实现原理

查看@LoadBalancerClients源码:

package org.springframework.cloud.loadbalancer.annotation;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Import;

@Configuration(

proxyBeanMethods = false

)

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE})

@Documented

@Import({LoadBalancerClientConfigurationRegistrar.class})

public @interface LoadBalancerClients {

LoadBalancerClient[] value() default {};

Class[] defaultConfiguration() default {};

}

其中除了上文使用到的defaultConfiguration属性还有LoadBalancerClient数组,我们继续查看@LoadBalancerClient:

package org.springframework.cloud.loadbalancer.annotation;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Import;

import org.springframework.core.annotation.AliasFor;

@Configuration(

proxyBeanMethods = false

)

@Import({LoadBalancerClientConfigurationRegistrar.class})

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface LoadBalancerClient {

@AliasFor("name")

String value() default "";

@AliasFor("value")

String name() default "";

Class[] configuration() default {};

}

通过上述源码可知利用@LoadBalancerClient对调用不同的服务提供者可进行精细化的负载均衡策略控制。

5.2 服务调用者配置

服务提供者利用上篇文章和本文已完成的模块应用

新增服务调用者模块,完整目录结构如下:

新增CustomNacosLoadBalancerConfiguration

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;

import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.core.env.Environment;

import javax.annotation.Resource;

//@LoadBalancerClients(defaultConfiguration = CustomNacosLoadBalancerConfiguration.class)

@LoadBalancerClients(

@LoadBalancerClient(value = "nacos-discovery-http-provider-cluster", configuration = CustomNacosLoadBalancerConfiguration.class)

)

public class CustomNacosLoadBalancerConfiguration {

@Resource

NacosDiscoveryProperties nacosDiscoveryProperties;

@Bean

public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {

String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties);

}

}

新增CustomRandomLoadBalancerConfiguration

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.core.env.Environment;

//@LoadBalancerClients(defaultConfiguration = CustomRandomLoadBalancerConfiguration.class)

@LoadBalancerClients(

@LoadBalancerClient(value = "nacos-discovery-http-provider", configuration = CustomRandomLoadBalancerConfiguration.class)

)

public class CustomRandomLoadBalancerConfiguration {

@Bean

ReactorLoadBalancer randomLoadBalancer(Environment environment,

LoadBalancerClientFactory loadBalancerClientFactory) {

String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

return new RandomLoadBalancer(loadBalancerClientFactory

.getLazyProvider(name, ServiceInstanceListSupplier.class),

name);

}

}

新增ProviderServiceFeign

import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "nacos-discovery-http-provider")

public interface ProviderServiceFeign {

@RequestMapping(value = "sayHello", method = RequestMethod.GET)

String sayHello(@RequestParam("world") String world);

}

新增ProviderClusterServiceFeign

import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "nacos-discovery-http-provider-cluster")

public interface ProviderClusterServiceFeign {

@RequestMapping(value = "sayHello", method = RequestMethod.GET)

String sayHello(@RequestParam("world") String world);

}

新增ConsumerController

import com.gm.nacos_discovery_http_consumer_nimbleloadbalancer.service.ProviderClusterServiceFeign;

import com.gm.nacos_discovery_http_consumer_nimbleloadbalancer.service.ProviderServiceFeign;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

import java.util.Optional;

import java.util.concurrent.atomic.AtomicInteger;

@RestController

public class ConsumerController {

@Autowired

ProviderServiceFeign providerServiceFeign;

@Autowired

ProviderClusterServiceFeign providerClusterServiceFeign;

Map synchronizedHashMapRandom = Collections.synchronizedMap(new HashMap());

Map synchronizedHashMapNacos = Collections.synchronizedMap(new HashMap());

@RequestMapping(value = "sayRandomHello", method = RequestMethod.GET)

public String sayRandomHello() {

String result = providerServiceFeign.sayHello("hello world");

AtomicInteger ai = synchronizedHashMapRandom.get(result);

Optional opt = Optional.ofNullable(ai);

if (!opt.isPresent()) {

ai = new AtomicInteger(1);

synchronizedHashMapRandom.put(result, ai);

} else {

ai.getAndIncrement();

}

return synchronizedHashMapRandom.toString();

}

@RequestMapping(value = "sayNacosHello", method = RequestMethod.GET)

public String sayNacosHello() {

String result = providerClusterServiceFeign.sayHello("hello world");

AtomicInteger ai = synchronizedHashMapNacos.get(result);

Optional opt = Optional.ofNullable(ai);

if (!opt.isPresent()) {

ai = new AtomicInteger(1);

synchronizedHashMapNacos.put(result, ai);

} else {

ai.getAndIncrement();

}

return synchronizedHashMapNacos.toString();

}

}

新增NacosHttpConsumerNimbleLoadBalancerApplication

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication

@EnableDiscoveryClient

@EnableFeignClients("com.gm.nacos_discovery_http_consumer_nimbleloadbalancer.service")

public class NacosHttpConsumerNimbleLoadBalancerApplication {

public static void main(String[] args) {

SpringApplication.run(NacosHttpConsumerNimbleLoadBalancerApplication.class, args);

}

}

bootstrap.yml

server:

port: 7000

spring:

application:

name: @artifactId@

cloud:

nacos:

username: @nacos.username@

password: @nacos.password@

discovery:

server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos2kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos3.kc}:${NACOS_PORT:8848}

cluster-name: haerbin

5.3 测试效果

访问http://127.0.0.1:7000/sayRandomHello,测试RandomLoadBalancer 负载均衡策略:

访问http://127.0.0.1:7000/sayNacosHello,测试NacosLoadBalancer 负载均衡策略:

六、下章预告

源码地址:https://gitee.com/gm19900510/springboot-cloud-example

下章进行nacos融合springcloud,实现配置的动态变更。

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。