当前位置: 首页 > 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.lryc.cn/news/598562.html

相关文章:

  • 机器学习基础-k 近邻算法(从辨别水果开始)
  • TCP重传率优化在云服务器网络协议栈的调优实践
  • Java面试宝典:Spring专题二
  • openbmc 日志系统继续分析
  • 科大讯飞运维 OceanBase 的实践
  • Android tcp socket sample示例
  • 亚纳米级检测!潜望式棱镜的“检测密码”,决定手机远景清晰度
  • Text2SQL智能问答系统开发(一)
  • 激光雷达的单播和广播模式介绍
  • Java技术栈/面试题合集(17)-Git篇
  • C++符合快速入门(有java和js基础的)
  • 7.24路由协议总结
  • 如何将拥有的域名自定义链接到我的世界服务器(Minecraft服务器)
  • C++ 基础入门
  • 【shell脚本编程】day1 备份指定文件类型
  • 深入理解大语言模型生成参数:temperature、top\_k、top\_p 等全解析
  • 社区资源媒体管理系统设计与实现
  • 复盘—MySQL触发器实现监听数据表值的变化,对其他数据表做更新
  • Kubernetes Kubelet 资源配置优化指南:从命令行参数到配置文件的最佳实践
  • Hadoop磁盘I/O瓶颈的监控与优化:从iostat指标到JBOD vs RAID的深度解析
  • 40、鸿蒙Harmony Next开发:UI场景化-组件截图(ComponentSnapshot)
  • 跨境支付入门~国际支付结算(结算篇)
  • 龙虎榜——20250724
  • Vue工程化 ElementPlus
  • 数据结构实验-查找与排序算法
  • NPM/Yarn完全指南:前端开发的“基石“与“加速器“
  • 基于单片机智能交通灯设计
  • 人工智能与云计算双轮驱动:元宇宙如何重构全球产业生态
  • Python之底层级的网络接口——Socket(套接字)协议族及函数介绍
  • 【王树森推荐系统】推荐系统涨指标的方法05:特殊用户人群