前言
? ? ? ? spring-cloud-starter-netflix-ribbon已經(jīng)不再更新了,最新版本是2.2.10.RELEASE,最后更新時間是2021年11月18日,詳細(xì)信息可以看maven官方倉庫:https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon,SpringCloud官方推薦使用spring-cloud-starter-loadbalancer進(jìn)行負(fù)載均衡。我們在開發(fā)的時候,多人開發(fā)同一個微服務(wù),都注冊到同一個nacos,前端請求的時候,網(wǎng)關(guān)Gateway默認(rèn)輪訓(xùn)請求注冊中心的服務(wù),OpenFeign也會輪詢請求注冊中心的服務(wù),這樣就會導(dǎo)致前端有時會無法請求到我們本地寫的接口,而是請求到別人的服務(wù)中。所以我們可以重寫Loadbalancer默認(rèn)的負(fù)載均衡策略,實現(xiàn)自定義負(fù)載均衡策略,不管是Gateway還是OpenFeign都只能請求到我們自己本地的服務(wù)。
? ? ? ? 我的版本如下:
????????<spring-boot.version>2.7.3</spring-boot.version>
? ? ? ? <spring-cloud.version>2021.0.4</spring-cloud.version>
? ? ? ? <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
一、添加負(fù)載方式配置
? ? ? ? 1、定義負(fù)載均衡方式的枚舉
public enum LoadBalancerTypeEnum {
/**
* 開發(fā)環(huán)境,獲取自己的服務(wù)
*/
DEV,
/**
* 網(wǎng)關(guān),根據(jù)請求地址獲取對應(yīng)的服務(wù)
*/
GATEWAY,
/**
* 輪循
*/
ROUND_ROBIN,
/**
* 隨機(jī)
*/
RANDOM;
}
? ? ? ? 2、添加配置類,默認(rèn)使用輪訓(xùn)方式
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 負(fù)載均衡配置項
*/
@Data
@ConfigurationProperties(prefix = "spring.cloud.loadbalancer")
public class LoadBalanceProperties {
private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;
}
二、參考默認(rèn)實現(xiàn)自定義
? ? ? ? 默認(rèn)的負(fù)載均衡策略是這個類:
org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
? ? ? ? 我們參考這個類實現(xiàn)自己的負(fù)載均衡策略即可,RoundRobinLoadBalancer實現(xiàn)了ReactorServiceInstanceLoadBalancer這個接口,實現(xiàn)了choose這個方法,如下圖:
? ? ? ? 在choose方法中調(diào)用了processInstanceResponse方法,processInstanceResponse方法中調(diào)用了getInstanceResponse方法,所以我們我們可以復(fù)制RoundRobinLoadBalancer整個類,只修改getInstanceResponse這個方法里的內(nèi)容就可以實現(xiàn)自定義負(fù)載均衡策略。
? ? ? ? 在自定義的類中,我們實現(xiàn)了四種負(fù)載均衡策略
? ? ? ? 1、getRoundRobinInstance方法是直接復(fù)制的RoundRobinLoadBalancer類中的實現(xiàn);
? ? ? ? 2、getRandomInstance方法參考o(jì)rg.springframework.cloud.loadbalancer.core.RandomLoadBalancer類中的實現(xiàn);
? ? ? ? 3、getDevelopmentInstance方法是返回所有服務(wù)中和當(dāng)前機(jī)器ip一致的服務(wù),如果沒有,則輪訓(xùn)返回;
? ? ? ? 4、getGatewayDevelopmentInstance方法是返回所有服務(wù)中和網(wǎng)關(guān)請求頭中ip一致的服務(wù)。
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 負(fù)載均衡算法
* 負(fù)載均衡算法的默認(rèn)實現(xiàn)是 {@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);
}
/**
* 獲取網(wǎng)關(guān)本機(jī)實例
*
* @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轉(zhuǎn)為默認(rèn)的DefaultRequest,從request中拿到請求的ip信息,再選擇ip一樣的微服務(wù)
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一樣的服務(wù),如果沒有則按默認(rèn)來取
for (ServiceInstance instance : instances) {
String currentServiceId = instance.getServiceId();
String host = instance.getHost();
log.debug("注冊服務(wù):{},ip:{}", currentServiceId, host);
if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {
return new DefaultResponse(instance);
}
}
return getRoundRobinInstance(instances);
}
/**
* 獲取本機(jī)實例
*
* @param instances 實例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 14:22:13
*/
private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
//獲取本機(jī)ip
String hostIp = IpUtils.getHostIp();
log.debug("本機(jī)Ip:{}", hostIp);
//先取得和本地ip一樣的服務(wù),如果沒有則按默認(rèn)來取
for (ServiceInstance instance : instances) {
String currentServiceId = instance.getServiceId();
String host = instance.getHost();
log.debug("注冊服務(wù):{},ip:{}", currentServiceId, host);
if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {
return new DefaultResponse(instance);
}
}
return getRoundRobinInstance(instances);
}
/**
* 使用隨機(jī)算法
* 參考{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機(jī)制獲取節(jié)點(diǎn)
*
* @param instances 實例
* @return {@link Response }<{@link ServiceInstance }>
* @author : lwq
* @date : 2022-12-15 13:28:31
*/
private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
// 每一次計數(shù)器都自動+1,實現(xiàn)輪詢的效果
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";
}
//前端請求自定義請求頭,轉(zhuǎn)發(fā)到哪個服務(wù)
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);
}
}
????????getIpAddressFromHttpHeaders方法中,是從請求頭總拿到了自定義的請求頭forward-to,要想實現(xiàn)此功能,就需要前端發(fā)送請求的時候攜帶這個請求頭,例如
? ? ? ? 在若依的request.js中可以這么寫:?config.headers['forward-to'] = '192.168.0.145'
三、配置負(fù)載均衡策略
? ? ? ?默認(rèn)的配置在這里:
org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration
? ? ? ? 我們參考這個配置,實現(xiàn)自己的配置。啟用上面寫好的配置類LoadBalanceProperties,然后傳到自定義的負(fù)載均衡策略類CustomSpringCloudLoadBalancer,其他的復(fù)制就可以。
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;
/**
* 自定義負(fù)載均衡客戶端配置
*
*/
@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注解加載一下配置
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
/**
* 自定義負(fù)載均衡自動配置
*
*/
@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
public class CustomLoadBalanceAutoConfiguration {
}
四、使用
????????將以上代碼獨(dú)立成一個模塊,然后再其他微服務(wù)中的pom文件中引入,然后添加對應(yīng)的配置就可以實現(xiàn)自定義負(fù)載均衡了
? ? ? ? 1、在微服務(wù)中配置如下即可實現(xiàn)調(diào)用其他服務(wù)時,調(diào)用自己本地開發(fā)環(huán)境的微服務(wù)
? ??spring.cloud.loadbalancer.type=dev
? ? ? ? 2、在網(wǎng)關(guān)中配置如下即可實現(xiàn)調(diào)用固定某個服務(wù)
?spring.cloud.loadbalancer.type=gateway文章來源:http://www.zghlxwxcb.cn/news/detail-727356.html
寫在最后的話
? ? ? ? 最開始只有想法,但是不知道怎么實現(xiàn),百度也沒找到合適的方案。所以就開始看源碼,研究了一下,然后照著源碼寫,測試了一下真的就實現(xiàn)了。所以,多看看源碼還是有好處的。文章來源地址http://www.zghlxwxcb.cn/news/detail-727356.html
到了這里,關(guān)于【SpringCloud系列】開發(fā)環(huán)境下重寫Loadbalancer實現(xiàn)自定義負(fù)載均衡的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!