🎨领域:Java后端开发
🔥收录专栏: 系统设计与实战
🐒个人主页:BreezAm
💖Gitee:https://gitee.com/BreezAm
✨个人标签:【后端】【大数据】【前端】【运维】
文章目录
- 前言
- 二、配置步骤
- 2.1 自定义负载均衡器
- 2.2 编写配置类
- 2.3 启动类添加注解
- 三、扩展
- 3.1 如何操作nacos中的配置文件
- 3.2 如何操作nacos中的服务
- 四、相关依赖
前言
在很多时候,我们需要根据自己的业务实现自定义的负载均衡,例如在灰度发布场景中(金丝雀发布),需要通过灰度策略实现负载均衡,这时候默认的负载均衡器就无法满足需求。下文主要介绍如何实现自定义负载均衡器以及相关源码解析,需要说明一下,不同的版本配置略有区别,以下是本文案例介绍的版本要求。
springboot | springloud |
---|---|
2.7.4 | 2021.0.3 |
二、配置步骤
2.1 自定义负载均衡器
- 通过查看源码可知要实现自己的负载均衡器,需要实现
ReactorServiceInstanceLoadBalancer
接口,下面的代码中,是从自带的负载均衡器RoundRobinLoadBalancer
中拷贝的,因为大部分都是一样的,我们只需要关注choose(Request request)
这个方法,在这里可以通过ServiceInstanceListSupplier
从注册中心拿到当前访问服务的所有实例,方法要求返回的是一个服务实例
,因此就可以按照自己指定的规则返回符合要求的一个实例。
/*** 灰度发布负载均衡器*/
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);final AtomicInteger position;final String serviceId;ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId) {this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));}public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId, int seedPosition) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.position = new AtomicInteger(seedPosition);}@SuppressWarnings("rawtypes")@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();}//此处编写自己的负载均衡策略int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}
}
- 在这个版本中,我们无需增加其他配置就可以拿到HTTP请求的内容,下面选择实例的方法有个参数
Request
,里面有一个上下文,从里面我们可以拿到请求的数据,在这个版本中是封装在ResponseData
实体类中的。需要说明一下,在比较老的loadbalancer
版本中,如果没有做其他配置,这个Request 是空的,没有任何请求数据。
@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {
}
通过查看负载均衡客户端过滤器ReactiveLoadBalancerClientFilter
源码可知,这个Request实例化的的类是DefaultRequest
,里面有一个上下文类RequestDataContext
,请求相关数据封装在RequestData里后放入了上下文对象里,供负载均衡器使用,相关代码如下:(注:如果需要放入自定义的数据,可以重写ReactiveLoadBalancerClientFilter类)。
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {
...
知道了实现原理,我们就可以从自定义负载均衡器里面拿到请求数据了,案例代码如下:
@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {DefaultRequest req = (DefaultRequest) request;RequestDataContext context = (RequestDataContext) req.getContext();RequestData requestData = context.getClientRequest();...
}
ResponseData实体类的结构如下所示。
public class ResponseData {private final HttpStatus httpStatus;private final HttpHeaders headers;private final MultiValueMap<String, ResponseCookie> cookies;private final RequestData requestData;private final Integer rawHttpStatus;...
}
2.2 编写配置类
自定义负载均衡器编写好了以后,我们就需要将其注入到spring容器中,下面是配置代码,这里会有一个坑,就是不能加注解@Configuration
,因为服务是懒加载的,如果加上注解就会导致容器启动时拿不到该服务实例,出现问题,从下面可以看出,ServiceInstanceListSupplier
(服务实例)是通过服务的名字拿到的。
//@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class GrayLoadBalancerConfiguration {@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new GrayLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
}
2.3 启动类添加注解
最后一部就是在启动类上面添加注解@LoadBalancerClients
,并把配置类配置上去。
@SpringBootApplication
@LoadBalancerClients(defaultConfiguration = GrayLoadBalancerConfiguration.class)
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
三、扩展
3.1 如何操作nacos中的配置文件
首先我们还是先来看下源码,部分源码如下,配置中心的配置文件是通过配置管理器NacosConfigManager
管理的,从代码中我们可以看到NacosConfigManager 是一个bean,也就是放在spring容器中管理了,因此,我们就可以在自己的业务代码中通过@Autowired
将其注入就可以使用了。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigAutoConfiguration {...@Beanpublic NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);}...
}
当我们通过以下代码注入配置管理器以后,就可以拿到nacos配置服务ConfigService
了。
@Autowired
public NacosConfigManager nacosConfigManager;
在ConfigService里有对操作nacos配置文件的CRUD方法,部分接口代码如下,感兴趣的读者可以去尝试一下。
public interface ConfigService {String getConfig(String var1, String var2, long var3) throws NacosException;boolean publishConfig(String var1, String var2, String var3) throws NacosException;boolean publishConfig(String var1, String var2, String var3, String var4) throws NacosException;boolean publishConfigCas(String var1, String var2, String var3, String var4, String var5) throws NacosException;boolean removeConfig(String var1, String var2) throws NacosException;
}
3.2 如何操作nacos中的服务
有时在业务场景中,我们需要从配置中心拿到存活的服务实例。和配置管理一样,需要拿到nacos服务管理器,从以下代码可以看出NacosServiceManager
也是一个bean,操作和3.1中介绍的大同小异。
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {@Beanpublic NacosServiceManager nacosServiceManager() {return new NacosServiceManager();}
}
四、相关依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
🔥收录专栏:系统设计与实战