一、Spring Cloud LoadBalancer介绍
Spring Cloud LoadBalancer是Spring Cloud官网提供的一个客户端负载均衡器,功能类似于Ribbon。在Spring Cloud Nacos 2021移除了中Ribbon组件,Spring Cloud在Spring Cloud Commons项目中,添加了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
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
@RequestMapping(value = "sayHello", method = RequestMethod.GET)
public String sayHello() {
String result = providerServiceFeign.sayHello("hello world");
AtomicInteger ai = synchronizedHashMap.get(result);
Optional
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
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
private final NacosDiscoveryProperties nacosDiscoveryProperties;
public NacosLoadBalancer(ObjectProvider
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
}
public Mono
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return ((Flux)supplier.get()).next().map(this::getInstanceResponse);
}
private Response
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
} else {
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
List
if (StringUtils.isNotBlank(clusterName)) {
List
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
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
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
Map
@RequestMapping(value = "sayRandomHello", method = RequestMethod.GET)
public String sayRandomHello() {
String result = providerServiceFeign.sayHello("hello world");
AtomicInteger ai = synchronizedHashMapRandom.get(result);
Optional
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
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,实现配置的动态变更。
好文阅读
发表评论