文章目录
- [Spring Cloud LoadBanlancer](#Spring Cloud LoadBanlancer)
Spring Cloud LoadBanlancer
快速上手
Spring Cloud 从 2020.0.1 版本开始,移除了 Ribbon 组件,使用 Spring Cloud LoadBalancer 组件来代替 Ribbon 实现客户端负载均衡
实现负载均衡---@LoadBalanced
- 给
RestTemplate这个Bean添加@LoadBalanced注解即可
java
package org.example.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 修改
IP端口号为服务名称
order-service/service/OrderService
java
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
String url = "http://product-service/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
启动多个 product-service 实例
按照上一篇文章的方法,启动多个实例
测试负载均衡
连续多次发起请求: http://127.0.0.1:8080/order/1
观察 product-service 日志,会发现请求被分配到这三个实例上了 
负载均衡策略
负载均衡策略是一种思想,无论是哪种负载均衡器,它们的负载均衡策略都是相似的,Spring Cloud LoadBalancer 仅支持两种负载均衡策略:
- 轮询 (
Round Robin):轮询策略是指服务器轮流处理用户的请求。这是一种实现最简单,也最常用的策略。生活中也有类似的场景,比如学校轮流值日,或者轮流打扫卫生 - 随机选择 (
Random):随机选择策略是指随机选择一个后端服务器来处理新的请求
自定义负载均衡策略
Spring Cloud LoadBalancer 默认如在均衡策略是轮询策略 ,实现是 RoundRobinLoadBalancer,如果服务的消费者如果想采用随机的负载均衡策略,也非常简单
定义随机算法对象
- 定义随机算法对象,通过
@Bean将其加载到Spring容器中
此处使用 Spring Cloud LoadBalancer 提供的 RandomLoadBalancer
java
package org.example.order.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
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.core.env.Environment;
public class LoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
- 注意:该类需要满足:
- 不用
@Configuration注解 - 在组建扫描范围内
- 不用
- 使用
@LoadBanlancerClient或者@LoadBalancerClients注解
在 RestTemplate 配置类上方,使用 @LoadBalancerClient 或 LoadBalancerClients 注解,可以对不同的服务提供方配置不同的客户端负载均衡算法策略
由于我们项目中只有一个服务提供者,所以使用 @LoadBalancerClient
java
package org.example.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@LoadBalancerClient注解说明name:该负载均衡策略对哪个服务生效(服务提供方)configuration:该负载均衡策略,用哪个负载均衡策略实现
LoadBalancer 原理
LoadBalancer 的实现,主要是 LoadBalancerInterceptor,这个类会对 RestTemplate 的请求进行拦截,然后从 Eureka 根据服务 ID 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务 ID
我们来看看源码实现:
java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//...
public ClientHttpResponse intercept(final HttpRequest request, final
byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a
valid hostname: " + originalUri);
return (ClientHttpResponse) this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
可以看到这里的 intercept 方法,拦截了用户的 HttpRequest 请求,然后做了几件事:
request.getURI():从请求中获取uri,也就是http://product-service/product/1001originalUri.getHost():从uri中获取路径的主机名,也就是服务id,product-serviceloadBalancer.execute:根据服务id,进行负载均衡,并处理请求
点进去继续跟踪:
java
public class BlockingLoadBalancerClient implements LoadBalancerClient {
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
String hint = this.getHint(serviceId);
LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new
LoadBalancerRequestAdapter<>(request, this.buildRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors =
this.getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach((lifecycle) -> {
lifecycle.onStart(lbRequest);
});
// 根据serviceId, 和负载均衡策略,选择处理的服务
ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach((lifecycle) -> {
lifecycle.onComplete(new CompletionContext(Status.DISCARD,
lbRequest, new EmptyResponse()));
});
throw new IllegalStateException("No instances available for " + serviceId);
} else {
return this.execute(serviceId, serviceInstance, lbRequest);
}
}
/**
* 根据serviceId, 和负载均衡策略,选择处理的服务
*/
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
// 获取负载均衡器
ReactiveLoadBalancer<ServiceInstance> loadBalancer =
this.loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
} else {
// 根据负载均衡算法,在列表中选择一个服务实例
Response<ServiceInstance> loadBalancerResponse =
(Response)Mono.from(loadBalancer.choose(request)).block();
return loadBalancerResponse == null ? null :
(ServiceInstance)loadBalancerResponse.getServer();
}
}
}