负载均衡 -LoadBalance

目录

问题分析

负载均衡

服务端负载均衡

客户端负载均衡

Spring Cloud LoadBalancer

自定义负载均衡策略

实现原理


问题分析

在 服务注册与发现——Eureka-ZEEKLOG博客 中,我们根据应用名称获取了服务实例列表,并从列表中选择了一个服务实例:

若一个服务对应多个实例,是否能够将流量合理的分配到多个实例呢?

我们启动多个 product-service 实例

修改端口号:

再添加一个实例,并启动:

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

此时,我们多次访问 127.0.0.1:8080/order/1

可以看到,多次访问的都是同一台机器,我们启动多个实例,就是希望能够减轻单机压力,也就是每个实例处理部分请求,而不是让同一台机器处理所有请求

那么,如何实现多个机器分摊负荷呢?

我们可以依次将请求分发给服务器列表中的每一台机器,因此,我们对 OrderService 中代码进行修改:

@Slf4j @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; // 当前选择实例 private static AtomicInteger atomicInteger = new AtomicInteger(1); // 实例列表 private static List<ServiceInstance> instances; @PostConstruct public void init() { // 获取服务列表 instances = discoveryClient.getInstances("product-service"); } public OrderInfo findOrderInfoById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); // 计算当前访问实例 int index = atomicInteger.getAndIncrement() % instances.size(); EurekaServiceInstance instance = (EurekaServiceInstance)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; } }

 多次访问 127.0.0.1:8080/order/1

此时请求被均衡地分配到了不同实例上,上述这种方式,就是负载均衡

负载均衡

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目的是将网络流量或计算任务智能地分配到多个服务器(或资源)从而 提高系统性能(响应更快)、增强可用性与可靠性(一台服务器宕机,其他还能继续服务)、提升可扩展性(通过增加服务器来应对更多请求)以及 避免单点过载(防止某台服务器被压垮)

我们通过一个生活中的示例来理解:

想象你有一家热门奶茶店,若只有一个收银员时,此时队伍会排得很长
而如果开了 3 个收银台,并有一个引导员把顾客平均分配到各窗口,整体效率就大幅提升 —— 这个“引导员”就是负载均衡器

负载均衡的核心作用

1. 分发请求:把用户请求(如 HTTP 请求)分给后端多个服务器。

2. 健康检查:自动检测服务器是否宕机,剔除故障节点。

3. 缓存与压缩:部分负载均衡器还能缓存静态内容,加速响应

常见负载均衡策略

策略

说明

轮询(Round Robin)

依次轮流分配请求(最简单常用)

加权轮询

性能强的服务器分配更多请求(如 A 权重 3,B 权重 1)

最少连接

把请求发给当前连接数最少的服务器

IP Hash

根据用户 IP 固定分配到某台服务器(实现会话保持)

响应时间优先

选择响应最快、负载最低的服务器

负载均衡的实现可分为 服务端负载均衡客户端负载均衡,这两种不同的流量分发策略核心区别在于:"谁来决定请求发给哪台后端服务器?"

服务端负载均衡

独立的负载均衡器(如 Nginx、云 LB) 位于客户端和后端服务之间,统一接收所有请求并分发到后端服务器

客户端负载均衡

客户端自己决定将请求发给哪一台后端服务器。客户端需事先获取服务实例列表(通常通过服务注册中心),并在本地执行负载均衡算法

接下来,我们来学习 Spring Cloud LoadBalance

Spring Cloud LoadBalancer

Spring Cloud LoadBalancer Spring Cloud 提供的一个客户端负载均衡器,用于在微服务架构中,让服务消费者(客户端)能够从多个服务实例中智能选择一个可用实例进行调用

在 Spring Cloud Netflix Ribbon 被弃用后,Spring Cloud LoadBalancer 成为了官方推荐的替代方案

要使用 Spring Cloud LoadBalancer 实现负载均衡策略非常简单,只需要给 RestTemplate 添加 @LoadBalanced 注解即可

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

IP 和端口号修改为服务名称

@Slf4j @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; public OrderInfo findOrderInfoById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); // 访问 URL String url = "http://product-service/product/" + orderInfo.getProductId(); ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class); orderInfo.setProductInfo(productInfo); return orderInfo; } } 

多次发起请求   127.0.0.1:8080/order/1,观察 product-service 的日志,可以看到请求被分配到这三个实例上:

Spring Cloud LoadBalancer 仅支持两种负载均衡策略:轮询策略随机策略,默认的负载均衡策略是 轮询策略RoundRobinLoadBalancer

若需要采用 随机策略,也非常简单

自定义负载均衡策略

定义随机策略对象,并通过 @Bean 将其加载到 Spring 容器中:

public class CustomLoadBalancerConfiguration { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory .getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }

使用 @LoadBalancerClient 注解配置上述随机策略:

@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class) @Configuration public class BeanConfig { @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
name:配置的负载均衡策略对哪个服务生效

configuration:使用的负载均衡策略

多次发起请求   127.0.0.1:8080/order/1,观察 product-service 的日志:

此时请求分布接近均匀

那么 Spring Cloud LoadBalancer 具体是如何实现负载均衡的呢?接下来我们就来看 Spring Cloud LoadBalancer 的具体实现

实现原理

Spring Cloud LoadBalancer 主要是通过 LoadBalancerInterceptor 来实现的,LoadBalancerInterceptor 会对 RestTemplate 的 请求进行拦截,然后从 Eureka 根据服务 id 获取服务列表,最后根据负载均衡算法得到真实的服务地址信息,并替换服务 id

我们来看 LoadBalancerInterceptor 的具体实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { // 从请求中获取 url, 如 http://product-service/product/1001 final URI originalUri = request.getURI(); // 获取路径主机名, 也就是服务id, 即 product-service String serviceName = originalUri.getHost(); // 判断 serviceName 是否为空 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); // 根据服务 id 进行负载均衡,并处理请求 return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } } 

继续看 execute 实现:

public class BlockingLoadBalancerClient implements LoadBalancerClient { private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory; @Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { String hint = getHint(serviceId); LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request, buildRequestContext(request, hint)); Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId); supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); // 根据 serviceId 和 lbRequest(负载均衡策略)选择服务 ServiceInstance serviceInstance = choose(serviceId, lbRequest); if (serviceInstance == null) { supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete( new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse()))); throw new IllegalStateException("No instances available for " + serviceId); } return execute(serviceId, serviceInstance, lbRequest); } @Override public <T> ServiceInstance choose(String serviceId, Request<T> request) { // 获取负载均衡器 ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId); if (loadBalancer == null) { return null; } // 根据负载均衡算法,从列表中选择一个服务实例 Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block(); if (loadBalancerResponse == null) { return null; } return loadBalancerResponse.getServer(); } } 

我们继续看不同负载均衡策略选择实现,先来看轮询策略RoundRobinLoadBalancer)实现:

public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer { @Override public Mono<Response<ServiceInstance>> choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); // 通过 processInstanceResponse 进行处理 return supplier.get(request).next() .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { // 调用 getInstanceResponse 获取选择的实例 Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } if (instances.size() == 1) { return new DefaultResponse(instances.get(0)); } // 原子性地增加并返回新值 同时 任何数字 & MAX_VALUE = 保留低31位,清除符号位 int pos = this.position.incrementAndGet() & Integer.MAX_VALUE; // 将无限增长的 pos 映射到有限的实例范围内 ServiceInstance instance = instances.get(pos % instances.size()); return new DefaultResponse(instance); } }

再来看随机策略RandomLoadBalancer)实现:

public class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer { @Override public Mono<Response<ServiceInstance>> choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); // 通过 processInstanceResponse 进行处理 return supplier.get(request).next() .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { // 调用 getInstanceResponse 获取选择的实例 Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } // ThreadLocalRandom.current(): 获取当前线程的随机数生成器 // instances.size(): 获取可用服务实例的数量 // nextInt(bound): 生成 [0, bound) 范围内的随机整数 // 生成一个介于0(包含)和instances.size()(不包含)之间的随机整数 int index = ThreadLocalRandom.current().nextInt(instances.size()); ServiceInstance instance = instances.get(index); return new DefaultResponse(instance); } }

Read more

Linux 之从硬件硬盘到文件系统的全面过渡

Linux 之从硬件硬盘到文件系统的全面过渡

前提引入 文件=内容+属性,这是从单个文件的角度。但是有很多文件,我们可以在宏观上把文件分为被打开的文件(在前面基础IO讲过)和没有被打开的文件;而被打开的文件在内存中方便管理,没有被打开的文件在磁盘里。 没有打来的文件肯定是很多的,那这么多的文件在磁盘中怎么被我们找到呢?从现阶段的认知,文件是一种目录结构,目录结构是树状的,需要路径(绝对、相对),文件存到磁盘上,最基本的诉求就是:就是被找到。而研究上面这些需求,要完成以特定的结构组织管理文件和帮我们找到文件就是文件系统做的事情!!! 1. 理解硬件 磁盘-服务器-机柜-机房 机械磁盘是计算机中唯一的一个机械设备磁盘--- 外设慢容量大,价格便宜 磁盘物理结构 磁盘存储结构 磁道是同心圆 扇区:是磁盘存储数据的基本单位,512字节,块设备 三片六面,六个磁头,磁头在传动臂的带动下,共进退!!! 磁盘写入的时候,是向柱面进行批量写入的!!! 如何定位一个扇区呢?可以先定位磁头(header)确定磁头要访问哪一个柱面(磁道)(cylinder)定位一个扇区(

By Ne0inhk
Flutter 组件 ignorium 的适配 鸿蒙Harmony 深度进阶 - 驾驭 CI 质量门禁、实现鸿蒙端构建产物安全性审计与动态忽略词库方案

Flutter 组件 ignorium 的适配 鸿蒙Harmony 深度进阶 - 驾驭 CI 质量门禁、实现鸿蒙端构建产物安全性审计与动态忽略词库方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ignorium 的适配 鸿蒙Harmony 深度进阶 - 驾驭 CI 质量门禁、实现鸿蒙端构建产物安全性审计与动态忽略词库方案 前言 在前文中,我们利用 ignorium 实现了基础的代码生成路径忽略。但在真正的“企业级 CI/CD 流水线”、“多仓库协同监控(Monorepo Audit)”以及“自动化交付审计”场景中。简单的静态忽略配置仅仅是起点。面对需要在 Jenkins 或是 GitHub Actions 运行时动态注入针对“特定批次(如 0307)”生成的临时忽略规则;面对需要分析为何某个核心类被错误地标记为了 ignored 导致构建失败;面对需要在发布 HAP 前强制验证没有任何“泄密文件”

By Ne0inhk
Linux 进程间通信之管道基础解析 —— 匿名管道的原理与实现

Linux 进程间通信之管道基础解析 —— 匿名管道的原理与实现

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 进程间通信基础认知 * 1.1 进程间通信的核心目的 * 1.2 进程间通信的发展与分类 * 二. 管道的基础概念 * 2.1 管道的定义 * 2.2 管道的核心特性(最后总结部分的图片里更全点,可以着重看那个) * 三. 匿名管道的创建与 API * 3.1 匿名管道的创建函数 * 3.2 匿名管道的简单使用示例 * 四. 基于 fork 的匿名管道跨进程通信 * 4.1 fork 共享管道的核心原理 * 4.2

By Ne0inhk
Flutter 三方库 async_result 的鸿蒙化适配指南 - 实现具备函数式错误处理与异步执行流封装的逻辑增强、支持端侧复杂请求的极致稳健建模实战

Flutter 三方库 async_result 的鸿蒙化适配指南 - 实现具备函数式错误处理与异步执行流封装的逻辑增强、支持端侧复杂请求的极致稳健建模实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 async_result 的鸿蒙化适配指南 - 实现具备函数式错误处理与异步执行流封装的逻辑增强、支持端侧复杂请求的极致稳健建模实战 前言 在进行 Flutter for OpenHarmony 开发时,当我们的异步操作(如网络请求、数据库写入)涉及到复杂的失败逻辑(如网络超时、权限不足、数据空、业务逻辑错误)时,直接使用 Future<T> 会让调用方陷入无穷无尽的 try-catch 地狱。async_result 是一款结合了 Result 模型与异步语义的高级库。本文将探讨如何在鸿蒙端构建极致、专业的异步错误处理底座。 一、原直观解析 / 概念介绍 1.1 基础原理 该库建立在“

By Ne0inhk