gateway集成nacos、loadbalancer实现自定义负载均衡器,带源码解析(cloud版本:2021.0.3|boot版本:2.7.4)[场景1:灰度发布(金丝雀发布)]

🎨领域:Java后端开发



在这里插入图片描述


🔥收录专栏: 系统设计与实战
🐒个人主页:BreezAm
💖Gitee:https://gitee.com/BreezAm
✨个人标签:【后端】【大数据】【前端】【运维】

前言

在很多时候,我们需要根据自己的业务实现自定义的负载均衡,例如在灰度发布场景中(金丝雀发布),需要通过灰度策略实现负载均衡,这时候默认的负载均衡器就无法满足需求。下文主要介绍如何实现自定义负载均衡器以及相关源码解析,需要说明一下,不同的版本配置略有区别,以下是本文案例介绍的版本要求。

springbootspringloud
2.7.42021.0.3

二、配置步骤

2.1 自定义负载均衡器
  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")
    @Override
    public 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);
    }
}
  1. 在这个版本中,我们无需增加其他配置就可以拿到HTTP请求的内容,下面选择实例的方法有个参数Request ,里面有一个上下文,从里面我们可以拿到请求的数据,在这个版本中是封装在ResponseData 实体类中的。需要说明一下,在比较老的loadbalancer版本中,如果没有做其他配置,这个Request 是空的,没有任何请求数据。
@Override
    public 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 -> {
...

知道了实现原理,我们就可以从自定义负载均衡器里面拿到请求数据了,案例代码如下:

 @Override
    public 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
    @ConditionalOnMissingBean
    public 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 {
    ...
    @Bean
	public 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 {

	@Bean
	public 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>

🔥收录专栏:系统设计与实战
在这里插入图片描述

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Gateway集成Nacos之后,可以使用Spring Cloud LoadBalancer实现自定义负载均衡。 首先,需要在项目中引入Spring Cloud LoadBalancer的依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> ``` 然后,可以通过实现LoadBalancerClient来实现自定义负载均衡算法。具体步骤如下: 1. 创建一个实现LoadBalancerClient接口的类,例如: ```java @Component public class MyLoadBalancerClient implements LoadBalancerClient { @Override public ServiceInstance choose(String serviceId) { // 实现自定义负载均衡算法 return null; } @Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { return null; } @Override public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { return null; } } ``` 2. 在该类中实现choose方法,该方法可以根据自定义负载均衡算法选择一个服务实例,并返回该实例的ServiceInstance对象。 3. 在Gateway的配置文件中,指定使用自定义LoadBalancerClient,例如: ```yaml spring: cloud: gateway: routes: - id: myservice uri: lb://myservice predicates: - Path=/myservice/** filters: - StripPrefix=1 # 使用自定义LoadBalancerClient lb: client: name: myservice # 指定自定义LoadBalancerClient custom: classname: com.example.MyLoadBalancerClient ``` 通过上述步骤,就可以实现自定义负载均衡算法了。需要注意的是,自定义LoadBalancerClient必须实现LoadBalancerClient接口的choose方法,其他两个方法可以不用实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星牛君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值