当前位置: 首页 > news >正文

负载均衡-LoadBalance

前提回顾:

order-service远程调用的代码是这样写的,先是根据应用名称获取了服务实例列表,接着从列表中选择了第一个服务实例。

List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
String uri = instances.get(0).getUri().toString();

也就是说,我可能是不止一个服务实例,那么问题来了,如果我一个服务对应着多个实例,那流量是否可以合理的分配到多个实例呢?

我们现在来做个简单的测试:

我们再启动2个product-service实例,先选中要启动的服务,右键选择 Copy Configuration

添加 VM options : -Dserver.port=9091

其中9091是服务启动的端口号,可以根据情况修改。

同样的操作,在创建一个,现在就一共有了三个product-service服务。

然后右键选中要启动的项目,点击 run

从eureka中我们看到product-service有三个实例

多次访问:http://127.0.0.1:8080/order/1 看看日志情况

通过日志我们发现,多次请求,访问的都是同一台机器。

这与我们的预期相差太多,我们启动多个实例,是希望可以分担负荷,那该如何实现呢?

解决办法:

修改一下order-service中service层的代码

	//计数器,使用原子类,保证线程安全。private AtomicInteger count = new AtomicInteger(1);private List<ServiceInstance> instances;//保证eureka返回来的实例是固定的。@PostConstructpublic void init(){//从Eureka中获取服务列表instances = discoveryClient.getInstances("product-service");}public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//计算轮流的实例indexint index = count.getAndIncrement() %instances.size();//获取实例String uri = instances.get(index).getUri().toString();//拼接urlString url = uri + "/product/" + orderInfo.getProductId();log.info("远程调用url:{}",url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}

思路:

通过日志可以看出来,请求被平均的分配到不同的实例上,这就是负载均衡。

负载均衡

负载均衡(Load Balance,简称 LB) , 是⾼并发, ⾼可⽤系统必不可少的关键组件。

当服务流量增⼤时, 通常会采⽤增加机器的⽅式进⾏扩容, 负载均衡就是⽤来在多个机器或其他资源中, 按照⼀定的规则(负载均衡策略)合理分配负载。

负载均衡分为 服务端负载均衡 和 客户端负载均衡。

刚才在上面例子是采用的轮询的方式来实现的负载均衡。当然还是有其他的方式来实现负载均衡,来应对不同的场景。

一些负载均衡算法:

算法工作原理适用场景
轮询 (Round Robin)依次分配请求服务器性能均等时
加权轮询按权重比例分配请求服务器配置不一时
最少连接选择当前连接数最少的服务器长连接服务(如WebSocket)
源IP哈希相同客户端IP分配到相同服务器需要会话保持的应用
响应时间加权优先选择响应最快的服务器对延迟敏感的服务

服务多机部署时, 开发⼈员都需要考虑负载均衡的实现, 所以也出现了⼀些负载均衡器, 来帮助我们实

现负载均衡。

服务端负载均衡

在服务端进⾏负载均衡的算法分配.

⽐较有名的服务端负载均衡器是Nginx. 请求先到达Nginx负载均衡器, 然后通过负载均衡算法, 在多个

服务器之间选择⼀个进⾏访问。

客户端负载均衡

在客⼾端进⾏负载均衡的算法分配。

把负载均衡的功能以库的⽅式集成到客⼾端, ⽽不再是由⼀台指定的负载均衡设备集中提供。

⽐如Spring Cloud的Ribbon, 请求发送到客⼾端, 客⼾端从注册中⼼(⽐如Eureka)获取服务列表, 在发

送请求前通过负载均衡算法选择⼀个服务器,然后进⾏访问。

Ribbon是Spring Cloud早期的默认实现, 由于不维护了, 所以最新版本的Spring Cloud负载均衡集成的

是Spring Cloud LoadBalancer(Spring Cloud官⽅维护)。

客⼾端负载均衡和服务端负载均衡最⼤的区别在于服务清单所存储的位置。

Spring Cloud LoadBalancer实现负载均衡

1.添加注解

给RestTemplate 这个 Bean 添加 @Loadbalanced 注解

@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}}

2.修改远程调用代码,把IP和端口号改成应用名

public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);String url = "<http://product-service/product/>" + orderInfo.getProductId();log.info("远程调用url:{}",url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}

3.测试用例

先启动多个product-service实例,再多次访问http://127.0.0.1:8080/order/1

观察日志,就会发现请求被平均的分配到每个实例上了。

Spring Cloud LoadBalancer负载均衡策略

负载均衡策略是⼀种思想, ⽆论是哪种负载均衡器, 它们的负载均衡策略都是相似的。 Spring Cloud

LoadBalancer 仅⽀持两种负载均衡策略: 轮询策略 和 随机策略

  1. 轮询(Round Robin): 轮询策略是指服务器轮流处理⽤⼾的请求. 这是⼀种实现最简单, 也最常⽤的

    策略. ⽣活中也有类似的场景, ⽐如学校轮流值⽇, 或者轮流打扫卫⽣。

  2. 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处理新的请求。

自定义随机负载均衡策略

Spring Cloud LoadBalancer 默认负载均衡策略是 轮询策略, 实现是 RoundRobinLoadBalancer, 如果

服务的消费者如果想采⽤随机的负载均衡策略, 也⾮常简单。

参考官⽹地址:Spring Cloud LoadBalancer :: Spring Cloud Commons

  1. 定义随机算法对象,通过 @Bean 将其加载到Spring容器中

    此处使用 SpringCloudLoadBalancer提供的RandomLoadBalancer

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 CustomLoadBalancerConfiguration {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);}
}

<aside> 💡

注意:该类需要满足的条件:

  1. 不用 @Configuration 注解

  2. 在组件扫描范围内 </aside>

  3. 使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解

在RestTemplate 配置类上方,使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解,可以对不同的服务提供方配置不同的客户端负载均衡算法策略。

@LoadBalancerClient:一个服务的提供者。

@LoadBalancerClients:多个服务的提供者。

@LoadBalancerClient(name = "product-service" , configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}}

其中:

  1. name:该负载均衡策略对哪个服务生效(服务提供方)
  2. configuration:该负载均衡策略 用哪个负载均衡策略实现。

LoadBalancer 原理(不发)

LoadBalancer 的实现, 主要是 LoadBalancerInterceptor , 这个类会对 RestTemplate 的请求进⾏拦截, 然后从Eureka根据服务id获取服务列表,随后利⽤负载均衡算法得到真实的服务地址信息,替换服务id。 我们来看看源码实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {//...public ClientHttpResponse intercept(final HttpRequest request, finalbyte[] 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, 也就是 http://product-service/product/1001
  2. originalUri.getHost() 从uri中获取路径的主机名, 也就是服务id, product-service
  3. loadBalancer.execute 根据服务id, 进⾏负载均衡, 并处理请求

点进去继续跟踪

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 (T)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();}}}

http://www.dtcms.com/a/295502.html

相关文章:

  • YOLOv4深度解析:革命性的实时目标检测技术
  • 基于Zig语言,opencv相关的c++程序静态交叉编译
  • USRP X440
  • Vulnhub Web-Machine-N7靶机攻略(附VB安装教程)
  • Docker快速安装Clickhouse
  • Vue 项目中的组件引用如何实现,依赖组件间的数据功能交互及示例演示
  • OpenLayers 综合案例-基础图层控制
  • 解密 Base64 编码:从原理到应用的全面解析
  • 前端实现 excel 数据导出,封装方法支持一次导出多个Sheet
  • Effective Python 第16条:用get处理字典缺失键,避免in与KeyError的陷阱
  • 时间日期选择器组件进行日期和时间的禁用处理逻辑
  • 让UV管理一切!!!
  • wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析
  • 再生基因总结
  • Vue工程化 ElementPlus
  • Android Camera createCaptureSession
  • 精密圆柱销类分拣系统“cad【9张】三维图+设计书明说
  • 货车手机远程启动的扩展功能有哪些
  • 二次元姓名生成器(饮料名+动漫角色名)
  • 研发过程都有哪些
  • 遨游三防平板|国产芯片鸿蒙系统单北斗三防平板,安全高效
  • 【jupyter 使用多进程方案】
  • 使用爬虫获取游戏的iframe地址
  • SSL 证书与 HTTPS 的关系:一文理清核心关联
  • 顶级水体视效一键添加~地表中的水体设置
  • OpenCV计算机视觉实战(17)——特征点检测详解
  • 基于python django的农业可视化系统,以奶牛牧场为例
  • 3D Semantic Occupancy Prediction
  • 行业热点丨SimLab解决方案如何高效应对3D IC多物理场与ECAD建模挑战?
  • Redis学习:持久化与事务(Transaction)