SpringCloud LoadBalancer
众所周知,SpringCloud体系中负载均衡的组件有SpringCloud LoadBalancer和Ribbon,Ribbon也在逐渐的被替代掉,因为SpringCloud LoadBalancer性能更高,支持响应式
下面通过hard-coded体现一下SpringCloud的负载均衡
首先有一个【say-hello】服务,提供"/","/greeting"接口,同时为了后续观测方便,将此服务的端口号也返回
我们的调用方【user】服务,使用webflux,下面为主要依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2023.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
想要实现负载均衡,首先需要WebClient发起请求并且添加ReactorLoadBalancerExchangeFilterFunction过滤器,在使用的地方注入ReactorLoadBalancerExchangeFilterFunction即可,
另外还需要提供ServiceInstanceListSupplier,并且注入为bean,稍后再分析原理,,这里serverId为say-hello,我们将服务实例写成固定的单个节点,8081,8082,8082
现在启动say-hello服务的8081,8082,8083服务来体现负载均衡
调整user服务的日志等级为debug
logging:level:root: debug
访问user服务的/hello接口
可以明显的发现当前的负载策略是依次轮询,并且每次起始的实例并非8081
实现原理也很简单,主要在WebClient调用的时候会首先执行ExchangeFilterFunction的filter方法,ExchangeFilterFunction的andThan可以实现无限套娃,相当于过滤器的链式调用,有点绕
filterFunctions为自己传入的filter,andThan其实相当于next,我们在/hello接口中传入的filter为ReactorLoadBalancerExchangeFilterFunction,其中负载均衡主要在这里面进行开展,也可以传入自定义的过滤器
LoadBalancerLifecycle将获取当前NamedContextFactory,并且它确保负载均衡器可以在整个生命周期正常工作,也可以做一些额外的工作,
比如可以记录日志
也可以使用MicrometerStatsLoadBalancerLifecycle记录一些指标信息
接着调用LoadBalancerLifecycle的start方法
下面会调用主要逻辑choose方法
在LoadBalancerClientConfiguration,如果我们没有定义的话会默认使用RoundRobinLoadBalancer,采用依次轮询的策略,
而像其他的ReactorLoadBalancer都需要ServiceInstanceListSupplier来提供实例的一些信息,像在这个demo中我们使用固定值
根据这些扩展接口,我们很容易的来集成其他的注册发现框架,比如nacos通过DiscoveryClientServiceInstanceListSupplier
获取完ServiceInstance,我们来看RoundRobinLoadBalancer的轮询算法实现
其中position为初始化时产生的[0,999]的随机数,每次调用都会进行+1并且与Integer.MAX_VALUE进行与运算,如果是正整数,因为Integer.MAX_VALUE是2^32 -1 。0111 1111 1111 1111 1111 1111 1111 1111,当执行Integer.MAX_VALUE + 1时
会导致最高位(符号位)发生变化,从而产生溢出,加1后的结果为 1000 0000 0000 0000 0000 0000 0000 0000,表示的是最小的负数,即Integer.MIN_VALUE,次数再和Integer.MAX_VALUE进行与运算,所以pos的取值范围为[0, Integer.MAX_VALUE]
当连续的整数对相同的数取余时,余数也是连续的,所以就实现了依次轮询的策略
SpringCloud LoadBalancer里面内置了RandomLoadBalancer随机算法,实现应该是最简单的,直接使用随机数ThreadLocalRandom.current()
当然,我们也可以自己实现负载均衡的策略,比如下面是按照权重来实现,
修改DemoServiceInstanceListSuppler中的DefaultServiceInstance,在metadata添加额外参数weight,instance1权重为1,instance2权重为2,instance3权重为3
在当前NamedContext添加ReactorLoadBalancer bean
@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(ServiceInstanceListSupplier serviceInstanceListSupplier) {return new ReactorServiceInstanceLoadBalancer() {@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return serviceInstanceListSupplier.get(request).next().map(instances -> {return getInstanceResponse(instances);});}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.size() == 1) {return new DefaultResponse(instances.get(0));}int totalWeight = instances.stream().mapToInt(this::getWeight).sum();Random random = new Random();int r = random.nextInt(totalWeight);for (ServiceInstance instance : instances) {int weight = getWeight(instance);if (r < weight) {return new DefaultResponse(instance);}r -= weight;}return new EmptyResponse();};private int getWeight(ServiceInstance instance) {// 从元数据中获取权重,默认权重为 1String weightStr = instance.getMetadata().getOrDefault("weight", "1");try {return Integer.parseInt(weightStr);} catch (NumberFormatException e) {return 1; // 如果解析失败,默认权重为 1}}};}
重启项目,可以看到节点大概是按照我们预设的权重来的,instance1 8081端口的几率还是很小的
除了上面手动往WebClient里面添加Filter的方法之外,我们更多的是使用自动配置的方式,以减少程序的复杂性,
新添加bean,并且添加注解@LoadBalancerClient,name为say-hello,
新建接口/hi
其中@LoadBalancerClient会通过LoadBalancerClientConfigurationRegistrar注入为NamedContextFactory.Specification,如果添加了@LoadBalanced注解将在BeanPostProcessor的时候将会添加ExchangeFilterFunction
参考文档
https://spring.io/guides/gs/spring-cloud-loadbalancer