2020年前SpringCloud是采用Ribbon作为负载均衡实现,但是在2020后采用了LoadBalancer替代
LoadBalancer默认提供了两种负载均衡策略(只能通过配置类来修改负载均衡策略)
1.RandomLoadBalancer-随机分配策略
2.RoundRobinLoadBalancer-轮询分配策略(默认)
添加一个自定义的负载均衡策略CustomLoadBalancerConfiguration 配置类,可以直接复制官网
package com.test.order.config;
import org.springframework.cloud.client.ServiceInstance;
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;
//无需添加@Component
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
注意如果负载均衡策略配置类使用spring注解@ComponentScan或者@Configuration,就要注意如果被SpringBootApplication的ComponentScan扫描到(默认扫描SpringBootApplication目录下的全部包),就会变成全局的负载均衡策略,如果需要局部负载均衡最好不用注解,或者设置不被默认的ComponentScan扫描到
在启动类配置那个微服务使用那种负载均衡策略(如果有Ribbon负载均衡需要在yml负载均衡去掉,一般在2020版本后无需配置去掉,因为默认没有Ribbon)
@SpringBootApplication
//@LoadBalancerClients(defaultConfiguration={CustomLoadBalancerConfiguration.class})
//value的值表示需要负载均衡的微服务,调用指定的负载均衡策略
//@LoadBalancerClient(value = "stock-nacos",configuration = {CustomLoadBalancerConfiguration.class})
//@LoadBalancerClients({@LoadBalancerClient(value = "stock-nacos", configuration = {CustomLoadBalancerConfiguration.class}),@LoadBalancerClient(value = "stock-nacos2", configuration = {CustomLoadBalancerConfiguration2.class})})
@LoadBalancerClients({@LoadBalancerClient(value = "stock-nacos", configuration = {CustomLoadBalancerConfiguration.class})})
public class OrderLoadbalancerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderLoadbalancerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
return restTemplate;
}
}
pom.xml配置(因为spring-cloud-starter-alibaba-nacos-discovery依赖中的spring-cloud-starter-loadbalancer的true,所以不会继承,需要收到继承),如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
父pom.xml配置
<dependencyManagement>
<!--Spring Cloud alibaba的版本管理,通过dependency完成继承-->
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
遇到问题,点击spring-cloud-dependencies进不去查看pom文件,就是下载的pom有问题,可以都删除掉org.springframework.cloud目录下的文件,再重新刷新下载
Loadbalancer实现自定义负载均衡
定义负载均衡方式的枚举
public enum LoadBalancerTypeEnum {
/**
* 开发环境,获取自己的服务
*/
DEV,
/**
* 网关,根据请求地址获取对应的服务
*/
GATEWAY,
/**
* 轮循
*/
ROUND_ROBIN,
/**
* 随机
*/
RANDOM;
}
添加配置类,默认使用轮训方式
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 负载均衡配置项
*/
@Data
@ConfigurationProperties(prefix = "spring.cloud.loadbalancer")
public class LoadBalanceProperties {
private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;
}
在自定义的类中,我们实现了四种负载均衡策略
1、getRoundRobinInstance方法是直接复制的RoundRobinLoadBalancer类中的实现;
2、getRandomInstance方法参考org.springframework.cloud.loadbalancer.core.RandomLoadBalancer类中的实现;
3、getDevelopmentInstance方法是返回所有服务中和当前机器ip一致的服务,如果没有,则轮训返回;
4、getGatewayDevelopmentInstance方法是返回所有服务中和网关请求头中ip一致的服务。
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.loadbalance.config.LoadBalancerTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
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.RequestData;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
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.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义 SpringCloud 负载均衡算法
* 负载均衡算法的默认实现是 {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
*
*/
@Slf4j
public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final AtomicInteger position;
private final LoadBalancerTypeEnum type;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public CustomSpringCloudLoadBalancer(String serviceId,
LoadBalancerTypeEnum type,
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider);
}
public CustomSpringCloudLoadBalancer(String serviceId,
int seedPosition,
LoadBalancerTypeEnum type,
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this.serviceId = serviceId;
this.position = new AtomicInteger(seedPosition);
this.type = type;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(request, supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(Request request,
ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request, serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){
return this.getRoundRobinInstance(instances);
}else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){
return this.getRandomInstance(instances);
}else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){
return this.getDevelopmentInstance(instances);
}else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){
return this.getGatewayDevelopmentInstance(request, instances);
}
return this.getRoundRobinInstance(instances);
}
/**
* 获取网关本机实例
*
* @param instances 实例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 14:22:13
*/
private Response<ServiceInstance> getGatewayDevelopmentInstance(Request request, List<ServiceInstance> instances) {
//把request转为默认的DefaultRequest,从request中拿到请求的ip信息,再选择ip一样的微服务
DefaultRequest<RequestDataContext> defaultRequest = Convert.convert(new TypeReference<DefaultRequest<RequestDataContext>>() {}, request);
RequestDataContext context = defaultRequest.getContext();
RequestData clientRequest = context.getClientRequest();
HttpHeaders headers = clientRequest.getHeaders();
String requestIp = IpUtils.getIpAddressFromHttpHeaders(headers);
log.debug("客户端请求gateway的ip:{}", requestIp);
//先取得和本地ip一样的服务,如果没有则按默认来取
for (ServiceInstance instance : instances) {
String currentServiceId = instance.getServiceId();
String host = instance.getHost();
log.debug("注册服务:{},ip:{}", currentServiceId, host);
if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {
return new DefaultResponse(instance);
}
}
return getRoundRobinInstance(instances);
}
/**
* 获取本机实例
*
* @param instances 实例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 14:22:13
*/
private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
//获取本机ip
String hostIp = IpUtils.getHostIp();
log.debug("本机Ip:{}", hostIp);
//先取得和本地ip一样的服务,如果没有则按默认来取
for (ServiceInstance instance : instances) {
String currentServiceId = instance.getServiceId();
String host = instance.getHost();
log.debug("注册服务:{},ip:{}", currentServiceId, host);
if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {
return new DefaultResponse(instance);
}
}
return getRoundRobinInstance(instances);
}
/**
* 使用随机算法
* 参考{link {@link org.springframework.cloud.loadbalancer.core.RandomLoadBalancer}}
*
* @param instances 实例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 13:32:11
*/
private Response<ServiceInstance> getRandomInstance(List<ServiceInstance> instances) {
int index = ThreadLocalRandom.current().nextInt(instances.size());
ServiceInstance instance = instances.get(index);
return new DefaultResponse(instance);
}
/**
* 使用RoundRobin机制获取节点
*
* @param instances 实例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 13:28:31
*/
private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
// 每一次计数器都自动+1,实现轮询的效果
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
其中的工具类如下
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.URLUtil;
import org.springframework.http.HttpHeaders;
/**
* 获取IP方法
*/
public class IpUtils{
/**
* 获取IP地址
*
* @return 本地IP地址
*/
public static String getHostIp(){
try{
return InetAddress.getLocalHost().getHostAddress();
}catch (UnknownHostException e){
}
return "127.0.0.1";
}
/**
* 获取客户端IP
*
* @param httpHeaders 请求头
* @return IP地址
*/
public static String getIpAddressFromHttpHeaders(HttpHeaders httpHeaders){
if (httpHeaders == null){
return "unknown";
}
//前端请求自定义请求头,转发到哪个服务
List<String> ipList = httpHeaders.get("forward-to");
String ip = CollectionUtil.get(ipList, 0);
//请求自带的请求头
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ipList = httpHeaders.get("x-forwarded-for");
ip = CollectionUtil.get(ipList, 0);
}
//从referer获取请求的ip地址
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
//从referer中获取请求的ip地址
List<String> refererList = httpHeaders.get("referer");
String referer = CollectionUtil.get(refererList, 0);
URL url = URLUtil.url(referer);
if (Objects.nonNull(url)){
ip = url.getHost();
}else {
ip = "unknown";
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ipList = httpHeaders.get("Proxy-Client-IP");
ip = CollectionUtil.get(ipList, 0);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ipList = httpHeaders.get("X-Forwarded-For");
ip = CollectionUtil.get(ipList, 0);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ipList = httpHeaders.get("WL-Proxy-Client-IP");
ip = CollectionUtil.get(ipList, 0);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ipList = httpHeaders.get("X-Real-IP");
ip = CollectionUtil.get(ipList, 0);
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
}
配置负载均衡策略
import com.ruoyi.common.loadbalance.core.CustomSpringCloudLoadBalancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ServiceInstance;
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.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* 自定义负载均衡客户端配置
*
*/
@SuppressWarnings("all")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(LoadBalanceProperties.class)
public class CustomLoadBalanceClientConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(LoadBalanceProperties loadBalanceProperties,
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomSpringCloudLoadBalancer(name, loadBalanceProperties.getType(),
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
}
}
使用LoadBalancerClients注解加载一下配置
@SpringBootApplication
@EnableFeignClients
//全局
//@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
//指定
@LoadBalancerClients({@LoadBalancerClient(value = "stock-nacos", configuration = {CustomLoadBalanceClientConfiguration .class})})
public class OrderOpenFeignApplication {
public static void main(String[] args) {
SpringApplication.run(OrderOpenFeignApplication.class, args);
}
}
在配置文件配置
#调用自己本地开发环境的微服务
spring.cloud.loadbalancer.type=dev
#在网关中配置如下即可实现调用固定某个服务
spring.cloud.loadbalancer.type=gateway