文章目录
- 多机部署,负载均衡-LoadBalance
-
- [1. 开启多个服务](#1. 开启多个服务)
- [2. 什么是负载均衡](#2. 什么是负载均衡)
- [3. Spring Cloud LoadBalance](#3. Spring Cloud LoadBalance)
-
- 快速上手
-
- [使用Spring Cloud LoadBalance实现负载均衡](#使用Spring Cloud LoadBalance实现负载均衡)
- 修改IP,端口号为服务名称
- 启动多个服务
- 负载均衡策略
- LoadBalance原理
多机部署,负载均衡-LoadBalance
1. 开启多个服务
多复制两个服务,将服务全部启动起来
此时多次访问原来的接口,发现
调用的还是同一个
我们对代码进行修改:
java
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderMapper orderMapper;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static List<ServiceInstance> instances;
@PostConstruct
public void init() {
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo getOrderList(int orderId){
OrderInfo orderInfo = orderMapper.getOrderList(orderId);
int index = atomicInteger.getAndIncrement() % instances.size();
EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(index);
String url = instance.getUri() + "/product/getProduct?productId=" + orderInfo.getProductId();
log.info(url);
ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
通过轮询的方式去将请求均衡的分配到不同的实例上,这就是负载均衡
2. 什么是负载均衡
负载均衡是高并发,高可用系统必不可少的关键组件
当服务流量增大的时候,通常会采用增加机器的方式进行扩容,负载均衡就是用来在多个机器或者其他资源中,按照一定的规则合理分配负载,也就是负载均衡策略
负载均衡的实现
负载均衡分为服务端负载均衡和客户端负载均衡
服务端负载均衡
指的是在服务端进行负载均衡的算法分配
例如Nginx就是比较出名的服务端负载均衡器,请求会先到达Nginx负载均衡器,然后通过负载均衡算法,在多个服务器之间选择一个进行访问
客户端负载均衡
在客户端进行负载均衡的算法分配
就是将负载均衡的功能以库的方式集成到客户端,而不是由一台指定的负载均衡设备集中提供
比如Spring Cloud的Ribbon,请求发送到客户端,客户端从注册中心(比如Eureka)获取服务列表,在发送请求的时候通过负载均衡算法一个服务器,然后进行访问
但是Ribbon是Spring Cloud早期的默认实现,由于不维护了,现在最新版本的Spring Cloud 负载均衡集成的是Spring Cloud LoadBalance(Spring Cloud官方维护)
客户端负载均衡器和服务端负载均衡器的最大区别在于服务清单存储的位置
3. Spring Cloud LoadBalance
快速上手
使用Spring Cloud LoadBalance实现负载均衡
给RestTemplate这个Bean 添加 @LoadBalance
注解
java
@Configuration
public class BeanConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改IP,端口号为服务名称
java
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public OrderInfo getOrderList(int orderId){
OrderInfo orderInfo = orderMapper.getOrderList(orderId);
String url = "http://product-service/product/getProduct?productId=" + orderInfo.getProductId();
log.info(url);
ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
启动多个服务
进行6次访问
为了方便观察,我们在product-service里面加上日志
此时进行访问
可以看到请求被均匀的分布在3个实例上
负载均衡策略
负载均衡策略是一种思想,无论是哪种负载均衡,他们的负载均衡策略都是相似的.Spring Cloud LoadBalance仅支持两种负载均衡策略:轮询策略以及随机策略
- 轮询:轮询策略指的是服务器轮流处理用户的请求.这是一种实现最简单,也最常用的策略
- 随机选择:随机选择策略是指随机选择一个后端服务器来处理新的请求
自定义负载均衡策略
Spring Cloud LoadBalance默认的负载均衡策略是轮询策略.实现是RoundRobinLoadBalance
如果想使用随机的负载均衡策略:
- 定义随机算法对象,通过@Bean将其加载到Spring容器里面
这里我们使用Spring Cloud LoadBalancer提供的RandomLoadBalance
java
public class LoadBalanceConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalance(Environment env, LoadBalancerClientFactory clientFactory) {
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(clientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);
}
}
注意:不用@Configuration
注释,要求这个类在组件扫描范围内
- 使用
@LoadBalancerClieent
或者@LoadBalancerClients
注解在RestTemplate配置类上方,就可以实现对不同的服务提供方配置不同的客户端负载均衡算法策略
java
@LoadBalancerClient(name = "product-service",configuration = LoadBalanceConfig.class)
@Configuration
public class BeanConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
其中,name表示该负载均衡策略对哪个服务生效(服务提供方),Configuration表示负载均衡策略
LoadBalance原理
LoadBalance的实现,主要是LoadBalanceInterceptor
,这个类会对RestTemplate
的请求进行拦截,然后从Eureka根据服务id获取服务列表,然后利用负载均衡算法得到真实的服务地址信息,替换服务Id
java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
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方法,拦截了用户的请求后,做了以下几件事:
request.getURI()
从请求中获取urioriginalUri.getHost()
从uri中获取路径的主机名,也就是服务IdloadBalancer.execute
根据服务Id,进行负载均衡,并处理请求
我们继续看execute
方法
java
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);
}
}
java
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();
}
}