0


【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

前言

  1. spring-cloud-starter-netflix-ribbon已经不再更新了,最新版本是2.2.10.RELEASE,最后更新时间是20211118日,详细信息可以看maven官方仓库:https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon,SpringCloud官方推荐使用spring-cloud-starter-loadbalancer进行负载均衡。我们在开发的时候,多人开发同一个微服务,都注册到同一个nacos,前端请求的时候,网关Gateway默认轮训请求注册中心的服务,OpenFeign也会轮询请求注册中心的服务,这样就会导致前端有时会无法请求到我们本地写的接口,而是请求到别人的服务中。所以我们可以重写Loadbalancer默认的负载均衡策略,实现自定义负载均衡策略,不管是Gateway还是OpenFeign都只能请求到我们自己本地的服务。
  2. 我的版本如下:
  3. <spring-boot.version>2.7.3</spring-boot.version>
  4. <spring-cloud.version>2021.0.4</spring-cloud.version>
  5. <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>

一、添加负载方式配置

  1. 1、定义负载均衡方式的枚举
  1. public enum LoadBalancerTypeEnum {
  2. /**
  3. * 开发环境,获取自己的服务
  4. */
  5. DEV,
  6. /**
  7. * 网关,根据请求地址获取对应的服务
  8. */
  9. GATEWAY,
  10. /**
  11. * 轮循
  12. */
  13. ROUND_ROBIN,
  14. /**
  15. * 随机
  16. */
  17. RANDOM;
  18. }
  1. 2、添加配置类,默认使用轮训方式
  1. import lombok.Data;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. /**
  4. * 负载均衡配置项
  5. */
  6. @Data
  7. @ConfigurationProperties(prefix = "spring.cloud.loadbalancer")
  8. public class LoadBalanceProperties {
  9. private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;
  10. }

二、参考默认实现自定义

  1. 默认的负载均衡策略是这个类:

org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer

  1. 我们参考这个类实现自己的负载均衡策略即可,RoundRobinLoadBalancer实现了ReactorServiceInstanceLoadBalancer这个接口,实现了choose这个方法,如下图:

  1. choose方法中调用了processInstanceResponse方法,processInstanceResponse方法中调用了getInstanceResponse方法,所以我们我们可以复制RoundRobinLoadBalancer整个类,只修改getInstanceResponse这个方法里的内容就可以实现自定义负载均衡策略。
  2. 在自定义的类中,我们实现了四种负载均衡策略
  3. 1getRoundRobinInstance方法是直接复制的RoundRobinLoadBalancer类中的实现;
  4. 2getRandomInstance方法参考org.springframework.cloud.loadbalancer.core.RandomLoadBalancer类中的实现;
  5. 3getDevelopmentInstance方法是返回所有服务中和当前机器ip一致的服务,如果没有,则轮训返回;
  6. 4getGatewayDevelopmentInstance方法是返回所有服务中和网关请求头中ip一致的服务。
  1. import cn.hutool.core.convert.Convert;
  2. import cn.hutool.core.lang.TypeReference;
  3. import com.ruoyi.common.core.utils.StringUtils;
  4. import com.ruoyi.common.core.utils.ip.IpUtils;
  5. import com.ruoyi.common.loadbalance.config.LoadBalancerTypeEnum;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.ObjectProvider;
  8. import org.springframework.cloud.client.ServiceInstance;
  9. import org.springframework.cloud.client.loadbalancer.DefaultRequest;
  10. import org.springframework.cloud.client.loadbalancer.DefaultResponse;
  11. import org.springframework.cloud.client.loadbalancer.EmptyResponse;
  12. import org.springframework.cloud.client.loadbalancer.Request;
  13. import org.springframework.cloud.client.loadbalancer.RequestData;
  14. import org.springframework.cloud.client.loadbalancer.RequestDataContext;
  15. import org.springframework.cloud.client.loadbalancer.Response;
  16. import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
  17. import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
  18. import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
  19. import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
  20. import org.springframework.http.HttpHeaders;
  21. import reactor.core.publisher.Mono;
  22. import java.util.List;
  23. import java.util.Objects;
  24. import java.util.Random;
  25. import java.util.concurrent.ThreadLocalRandom;
  26. import java.util.concurrent.atomic.AtomicInteger;
  27. /**
  28. * 自定义 SpringCloud 负载均衡算法
  29. * 负载均衡算法的默认实现是 {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
  30. *
  31. */
  32. @Slf4j
  33. public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {
  34. private final String serviceId;
  35. private final AtomicInteger position;
  36. private final LoadBalancerTypeEnum type;
  37. private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
  38. public CustomSpringCloudLoadBalancer(String serviceId,
  39. LoadBalancerTypeEnum type,
  40. ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
  41. this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider);
  42. }
  43. public CustomSpringCloudLoadBalancer(String serviceId,
  44. int seedPosition,
  45. LoadBalancerTypeEnum type,
  46. ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
  47. this.serviceId = serviceId;
  48. this.position = new AtomicInteger(seedPosition);
  49. this.type = type;
  50. this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
  51. }
  52. @Override
  53. public Mono<Response<ServiceInstance>> choose(Request request) {
  54. ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
  55. return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(request, supplier, serviceInstances));
  56. }
  57. private Response<ServiceInstance> processInstanceResponse(Request request,
  58. ServiceInstanceListSupplier supplier,
  59. List<ServiceInstance> serviceInstances) {
  60. Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request, serviceInstances);
  61. if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
  62. ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
  63. }
  64. return serviceInstanceResponse;
  65. }
  66. private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
  67. if (instances.isEmpty()) {
  68. if (log.isWarnEnabled()) {
  69. log.warn("No servers available for service: " + serviceId);
  70. }
  71. return new EmptyResponse();
  72. }
  73. if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){
  74. return this.getRoundRobinInstance(instances);
  75. }else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){
  76. return this.getRandomInstance(instances);
  77. }else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){
  78. return this.getDevelopmentInstance(instances);
  79. }else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){
  80. return this.getGatewayDevelopmentInstance(request, instances);
  81. }
  82. return this.getRoundRobinInstance(instances);
  83. }
  84. /**
  85. * 获取网关本机实例
  86. *
  87. * @param instances 实例
  88. * @return {@link Response }<{@link ServiceInstance }>
  89. * @author : lwq
  90. * @date : 2022-12-15 14:22:13
  91. */
  92. private Response<ServiceInstance> getGatewayDevelopmentInstance(Request request, List<ServiceInstance> instances) {
  93. //把request转为默认的DefaultRequest,从request中拿到请求的ip信息,再选择ip一样的微服务
  94. DefaultRequest<RequestDataContext> defaultRequest = Convert.convert(new TypeReference<DefaultRequest<RequestDataContext>>() {}, request);
  95. RequestDataContext context = defaultRequest.getContext();
  96. RequestData clientRequest = context.getClientRequest();
  97. HttpHeaders headers = clientRequest.getHeaders();
  98. String requestIp = IpUtils.getIpAddressFromHttpHeaders(headers);
  99. log.debug("客户端请求gateway的ip:{}", requestIp);
  100. //先取得和本地ip一样的服务,如果没有则按默认来取
  101. for (ServiceInstance instance : instances) {
  102. String currentServiceId = instance.getServiceId();
  103. String host = instance.getHost();
  104. log.debug("注册服务:{},ip:{}", currentServiceId, host);
  105. if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {
  106. return new DefaultResponse(instance);
  107. }
  108. }
  109. return getRoundRobinInstance(instances);
  110. }
  111. /**
  112. * 获取本机实例
  113. *
  114. * @param instances 实例
  115. * @return {@link Response }<{@link ServiceInstance }>
  116. * @author : lwq
  117. * @date : 2022-12-15 14:22:13
  118. */
  119. private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
  120. //获取本机ip
  121. String hostIp = IpUtils.getHostIp();
  122. log.debug("本机Ip:{}", hostIp);
  123. //先取得和本地ip一样的服务,如果没有则按默认来取
  124. for (ServiceInstance instance : instances) {
  125. String currentServiceId = instance.getServiceId();
  126. String host = instance.getHost();
  127. log.debug("注册服务:{},ip:{}", currentServiceId, host);
  128. if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {
  129. return new DefaultResponse(instance);
  130. }
  131. }
  132. return getRoundRobinInstance(instances);
  133. }
  134. /**
  135. * 使用随机算法
  136. * 参考{link {@link org.springframework.cloud.loadbalancer.core.RandomLoadBalancer}}
  137. *
  138. * @param instances 实例
  139. * @return {@link Response }<{@link ServiceInstance }>
  140. * @author : lwq
  141. * @date : 2022-12-15 13:32:11
  142. */
  143. private Response<ServiceInstance> getRandomInstance(List<ServiceInstance> instances) {
  144. int index = ThreadLocalRandom.current().nextInt(instances.size());
  145. ServiceInstance instance = instances.get(index);
  146. return new DefaultResponse(instance);
  147. }
  148. /**
  149. * 使用RoundRobin机制获取节点
  150. *
  151. * @param instances 实例
  152. * @return {@link Response }<{@link ServiceInstance }>
  153. * @author : lwq
  154. * @date : 2022-12-15 13:28:31
  155. */
  156. private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
  157. // 每一次计数器都自动+1,实现轮询的效果
  158. int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
  159. ServiceInstance instance = instances.get(pos % instances.size());
  160. return new DefaultResponse(instance);
  161. }
  162. }
  1. 其中的工具类如下:
  1. import java.net.InetAddress;
  2. import java.net.URL;
  3. import java.net.UnknownHostException;
  4. import java.util.List;
  5. import java.util.Objects;
  6. import javax.servlet.http.HttpServletRequest;
  7. import cn.hutool.core.collection.CollectionUtil;
  8. import cn.hutool.core.util.URLUtil;
  9. import org.springframework.http.HttpHeaders;
  10. /**
  11. * 获取IP方法
  12. */
  13. public class IpUtils{
  14. /**
  15. * 获取IP地址
  16. *
  17. * @return 本地IP地址
  18. */
  19. public static String getHostIp(){
  20. try{
  21. return InetAddress.getLocalHost().getHostAddress();
  22. }catch (UnknownHostException e){
  23. }
  24. return "127.0.0.1";
  25. }
  26. /**
  27. * 获取客户端IP
  28. *
  29. * @param httpHeaders 请求头
  30. * @return IP地址
  31. */
  32. public static String getIpAddressFromHttpHeaders(HttpHeaders httpHeaders){
  33. if (httpHeaders == null){
  34. return "unknown";
  35. }
  36. //前端请求自定义请求头,转发到哪个服务
  37. List<String> ipList = httpHeaders.get("forward-to");
  38. String ip = CollectionUtil.get(ipList, 0);
  39. //请求自带的请求头
  40. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  41. ipList = httpHeaders.get("x-forwarded-for");
  42. ip = CollectionUtil.get(ipList, 0);
  43. }
  44. //从referer获取请求的ip地址
  45. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  46. //从referer中获取请求的ip地址
  47. List<String> refererList = httpHeaders.get("referer");
  48. String referer = CollectionUtil.get(refererList, 0);
  49. URL url = URLUtil.url(referer);
  50. if (Objects.nonNull(url)){
  51. ip = url.getHost();
  52. }else {
  53. ip = "unknown";
  54. }
  55. }
  56. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  57. ipList = httpHeaders.get("Proxy-Client-IP");
  58. ip = CollectionUtil.get(ipList, 0);
  59. }
  60. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  61. ipList = httpHeaders.get("X-Forwarded-For");
  62. ip = CollectionUtil.get(ipList, 0);
  63. }
  64. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  65. ipList = httpHeaders.get("WL-Proxy-Client-IP");
  66. ip = CollectionUtil.get(ipList, 0);
  67. }
  68. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
  69. ipList = httpHeaders.get("X-Real-IP");
  70. ip = CollectionUtil.get(ipList, 0);
  71. }
  72. return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
  73. }
  74. }
  1. getIpAddressFromHttpHeaders方法中,是从请求头总拿到了自定义的请求头forward-to,要想实现此功能,就需要前端发送请求的时候携带这个请求头,例如
  2. 在若依的request.js中可以这么写: config.headers['forward-to'] = '192.168.0.145'

三、配置负载均衡策略

  1. 默认的配置在这里:

org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration

  1. 我们参考这个配置,实现自己的配置。启用上面写好的配置类LoadBalanceProperties,然后传到自定义的负载均衡策略类CustomSpringCloudLoadBalancer,其他的复制就可以。
  1. import com.ruoyi.common.loadbalance.core.CustomSpringCloudLoadBalancer;
  2. import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  3. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  4. import org.springframework.cloud.client.ServiceInstance;
  5. import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
  6. import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
  7. import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.core.env.Environment;
  11. /**
  12. * 自定义负载均衡客户端配置
  13. *
  14. */
  15. @SuppressWarnings("all")
  16. @Configuration(proxyBeanMethods = false)
  17. @EnableConfigurationProperties(LoadBalanceProperties.class)
  18. public class CustomLoadBalanceClientConfiguration {
  19. @Bean
  20. @ConditionalOnBean(LoadBalancerClientFactory.class)
  21. public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(LoadBalanceProperties loadBalanceProperties,
  22. Environment environment,
  23. LoadBalancerClientFactory loadBalancerClientFactory) {
  24. String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  25. return new CustomSpringCloudLoadBalancer(name, loadBalanceProperties.getType(),
  26. loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
  27. }
  28. }
  1. 然后使用LoadBalancerClients注解加载一下配置
  1. import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
  2. /**
  3. * 自定义负载均衡自动配置
  4. *
  5. */
  6. @LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
  7. public class CustomLoadBalanceAutoConfiguration {
  8. }

四、使用

  1. 将以上代码独立成一个模块,然后再其他微服务中的pom文件中引入,然后添加对应的配置就可以实现自定义负载均衡了
  2. 1、在微服务中配置如下即可实现调用其他服务时,调用自己本地开发环境的微服务
  1. spring.cloud.loadbalancer.type=dev
  1. 2、在网关中配置如下即可实现调用固定某个服务

spring.cloud.loadbalancer.type=gateway

写在最后的话

  1. 最开始只有想法,但是不知道怎么实现,百度也没找到合适的方案。所以就开始看源码,研究了一下,然后照着源码写,测试了一下真的就实现了。所以,多看看源码还是有好处的。

本文转载自: https://blog.csdn.net/WayneLee0809/article/details/128557770
版权归原作者 红藕香残玉簟秋 所有, 如有侵权,请联系我们删除。

“【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡”的评论:

还没有评论