系列文章目录
文章目录
- 系列文章目录
- 一、负载均衡介绍
- [二、Spring Cloud LoadBalancer](#二、Spring Cloud LoadBalancer)
-
- 2.1、快速上手
-
- [2.1.1、使用Spring Cloud LoadBalancer实现负载均衡](#2.1.1、使用Spring Cloud LoadBalancer实现负载均衡)
- 2.1.2、启动多个product-service实例
- 2.1.3、测试负载均衡
- 2.2、负载均衡策略
- [2.3、LoadBalancer 原理](#2.3、LoadBalancer 原理)
- 三、总结

一、负载均衡介绍
1.1、问题描述
上篇文章中远程调用的代码如下:
java
List<ServiceInstance> instances =discoveryClient.getInstances("productservice");
//服务可能有多个, 获取第⼀个
EurekaServiceInstance instance = (EurekaServiceInstance)instances.get(0);
- 根据应用名称获取了服务实例列表
- 从列表中选择了一个服务实例
**思考:**如果一个服务对应多个实例呢? 流量是否可以合理的分配到多个实例呢?
现象观察:
我们再启动2个product-service实例
选中要启动的服务, 右键选择 Copy Configuration

在弹出的框中, 选择 Modify options -> Add VM options

添加 VM options : -Dserver.port=9091
9091 为服务启动的端口号, 根据自己的情况进行修改

现在IDEA的Service窗口就会多出来⼀个启动配置, 右键启动服务就可以

同样的操作, 再启动1个实例, 共启动3个服务

观察Eureka, 可以看到product-service下有三个实例:

访问日志:
访问: http://127.0.0.1:8080/order/1
通过观察日志我们发现:请求多次访问, 都是同⼀台机器。这肯定不是我们想要的结果, 我们启动多个实例, 是希望可以分担其他机器的负荷, 那么如何实现呢?
解决方案:
我们可以对上述代码进行简单修改:
java
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static List<ServiceInstance> instances;
@PostConstruct
public void init(){
//根据应⽤名称获取服务列表
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
//服务可能有多个, 轮询获取实例
int index = atomicInteger.getAndIncrement() % instances.size();
ServiceInstance instance =instances.get(index);
log.info(instance.getInstanceId());
//拼接url
String url = instance.getUri()+"/product/"+ orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url,
ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
修改代码后,我们再次启动发现请求被均衡的分配在了不同的实例上, 这就是负载均衡。
1.2、什么是负载均衡
负载均衡(Load Balance,简称 LB) , 是高并发, 高可用系统必不可少的关键组件.
当服务流量增大时, 通常会采用增加机器的方式进行扩容, 负载均衡就是用来在多个机器或者其他资源中, 按照⼀定的规则合理分配负载.
⼀个团队最开始只有⼀个⼈, 后来随着工作量的增加, 公司又招聘了几个⼈. 负载均衡就是: 如何把工作量均衡的分配到这几个人⾝上, 以提高整个团队的效率
1.3、负载均衡的一些实现
上面的例子中, 我们只是简单的对实例进行了轮询, 但真实的业务场景会更加复杂. 比如根据机器的配置进行负载分配, 配置高的分配的流量高, 配置低的分配流量低等.
类似企业员工: 能力强的员工可以多承担一些工作.服务多机部署时, 开发人员都需要考虑负载均衡的实现, 所以也出现了⼀些负载均衡器, 来帮助我们实现负载均衡.
负载均衡分为服务端负载均衡和客户端负载均衡
服务端负载均衡
在服务端进行负载均衡的算法分配.
比较有名的服务端负载均衡器是Nginx. 请求先到达Nginx负载均衡器, 然后通过负载均衡算法, 在多个服务器之间选择一个进行访问.

客户端负载均衡
在客户端进行负载均衡的算法分配.
把负载均衡的功能以库的方式集成到客户端, 而不再是由一台指定的负载均衡设备集中提供.
比如Spring Cloud的Ribbon, 请求发送到客户端, 客户端从注册中心(比如Eureka)获取服务列表, 在发送请求前通过负载均衡算法选择⼀个服务器,然后进行访问.
Ribbon是Spring Cloud早期的默认实现, 由于不维护了, 所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方维护)

客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置
二、Spring Cloud LoadBalancer
2.1、快速上手
2.1.1、使用Spring Cloud LoadBalancer实现负载均衡
1 . 给 RestTemplate 这个Bean添加 @LoadBalanced 注解就可以
java
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.修改IP端口号为服务名称
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;
}
2.1.2、启动多个product-service实例
按照上面的方式,启动多个实例。
2.1.3、测试负载均衡
启动后,我们观察日志,发现请求被分配到三个实例上了

2.2、负载均衡策略
负载均衡策略是⼀种思想, ⽆论是哪种负载均衡器, 它们的负载均衡策略都是相似的. Spring CloudLoadBalancer 仅⽀持两种负载均衡策略: 轮询策略 和 随机策略
- 轮询(Round Robin): 轮询策略是指服务器轮流处理用户的请求. 这是⼀种实现最简单, 也最常用的策略. 生活中也有类似的场景, 比如学校轮流值日, 或者轮流打扫卫⽣.
- 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处理新的请求.
自定义负载均衡策略:
Spring Cloud LoadBalancer 默认负载均衡策略是轮询策略, 实现是 RoundRobinLoadBalancer, 如果服务的消费者如果想采用随机的负载均衡策略, 也非常简单.
- 定义随机算法对象, 通过 @Bean 将其加载到 Spring 容器中
此处使用Spring Cloud LoadBalancer提供的 RandomLoadBalancer
java
public class LoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment
environment,LoadBalancerClientFactory loadBalancerClientFactory) {
String name =
environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
System.out.println("=============="+name);
return new
RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
注意: 该类需要满足:
1 . 不用 @Configuration 注释
2 . 在组件扫描范围内
- 使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解在 RestTemplate 配置类上方, 使用@LoadBalancerClient 或 @LoadBalancerClients 注解, 可以对不同的服务提供方配置不同的客户端负载均衡算法策略.由于项目中只有⼀个服务提供者, 所以使用@LoadBalancerClient
java
@LoadBalancerClient(name = "product-service", configuration =LoadBalancerConfig.class)
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@LoadBalancerClient 注解说明
- name: 该负载均衡策略对哪个服务生效(服务提供方)
- configuration : 该负载均衡策略 用哪个负载均衡策略实现.
2.3、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请求,然后做了几件事:
1 . request.getURI() 从请求中获取uri
2 . service/product/1001 originalUri.getHost() 从uri中获取路径的主机名, 也就是服务id, product-service
3 . loadBalancer.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();
}
}
}
三、总结
以上就是本文全部内容,本文主要为大家介绍了负载均衡-LoadBalance以及实际应用。感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持