概述
目前后端用的基本都是springCloud
体系;
平时在dev
环境开发时,会把自己的本地服务也注册上去,但是这样的话,在客户端调用时请求可能会打到自己本地,对客户端测试不太友好.
思路大致就是前端在请求头传入指定ip
,然后后端根据指定的ip
选择测试环境的服务实例
我的springcloud
版本用的loadbalancer
是spring-cloud-starter-loadbalancer
,早期版本可能用的是ribbon
,不过思路大致都是一致的。
jdk:1.8
springcloud版本:2021.0.5
服务实例选择策略简述
当前http
调用服务时大多用的是open feign
,在调用时一路debug
就能看到源码的选择服务实例的过程了;
大致就是请求进来 ->
选择serviceId
对应的负载均衡器 ->
根据负载均衡器的choose
策略选择一个服务实例 ->
处理请求
关键代码
org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute
选择服务实例
选择一个负载均衡器
默认是org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
根据serviceId选择负载均衡器
看到这儿就知道大致是怎么一回事了
自定义服务选择策略
看完上完可以知道springCloud已经给我们留好了扩展口子
实现这个接口然后注册上去就可以使用了
org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer
可以参考默认的选择器
org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
自定义负载均衡选择策略
package org.xxx.xxx.loadbalance.core;import cn.hutool.core.net.NetUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;import java.util.List;
import java.util.concurrent.ThreadLocalRandom;/*** 自定义 SpringCloud 负载均衡算法**/
@Slf4j
@AllArgsConstructor
public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {private final String serviceId;private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + serviceId);}return new EmptyResponse();}for (ServiceInstance instance : instances) {//自定义策略if (NetUtil.localIpv4s().contains(instance.getHost())) {return new DefaultResponse(instance);}}return new DefaultResponse(instances.get(ThreadLocalRandom.current().nextInt(instances.size())));}}
注册自定义负载均衡选择策略class为springBean
package org.xxx.xxx.loadbalance.config;import org.xxx.xxx.loadbalance.core.CustomSpringCloudLoadBalancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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)
public class CustomLoadBalanceClientConfiguration {@Bean@ConditionalOnBean(LoadBalancerClientFactory.class)public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new CustomSpringCloudLoadBalancer(name,loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));}
}
serviceId绑定对应的负载均衡策略
package com.xxx.xxx.security.config;import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;/*** 负载均衡配置*/
@Configuration
@LoadBalancerClients(value = {//指定@LoadBalancerClient(name = "name1", configuration = CustomLoadBalanceClientConfiguration.class),@LoadBalancerClient(name = "name2", configuration = CustomLoadBalanceClientConfiguration.class)},//默认;即没有命中上面的serviceId的指定策略时,默认走下面的配置defaultConfiguration = CustomLoadBalanceClientConfiguration.class
)
//只在测试环境使用
@Profile({"test"})
public class LoadBalancerConfig {}
最后通过org.springframework.boot.autoconfigure.AutoConfiguration.imports
加载LoadBalancerConfig
比如下面截图中所示