[SpringCloud][8]Spring Cloud LoadBanlancer快速上手以及LoadBalancer原理

文章目录

Spring Cloud LoadBanlancer

快速上手

Spring Cloud2020.0.1 版本开始,移除了 Ribbon 组件,使用 Spring Cloud LoadBalancer 组件来代替 Ribbon 实现客户端负载均衡

实现负载均衡---@LoadBalanced

  1. RestTemplate 这个 Bean 添加 @LoadBalanced 注解即可
java 复制代码
package org.example.order.config;  
  
import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  
  
@Configuration  
public class BeanConfig {  
    @Bean  
    @LoadBalanced    
    public RestTemplate restTemplate() {  
        return new RestTemplate();  
    }  
}
  1. 修改 IP 端口号为服务名称
    order-service/service/OrderService
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;  
}

启动多个 product-service 实例

按照上一篇文章的方法,启动多个实例

测试负载均衡

连续多次发起请求: http://127.0.0.1:8080/order/1

观察 product-service 日志,会发现请求被分配到这三个实例上了

负载均衡策略

负载均衡策略是一种思想,无论是哪种负载均衡器,它们的负载均衡策略都是相似的,Spring Cloud LoadBalancer 仅支持两种负载均衡策略:

  1. 轮询Round Robin):轮询策略是指服务器轮流处理用户的请求。这是一种实现最简单,也最常用的策略。生活中也有类似的场景,比如学校轮流值日,或者轮流打扫卫生
  2. 随机选择Random):随机选择策略是指随机选择一个后端服务器来处理新的请求

自定义负载均衡策略

Spring Cloud LoadBalancer 默认如在均衡策略是轮询策略 ,实现是 RoundRobinLoadBalancer,如果服务的消费者如果想采用随机的负载均衡策略,也非常简单

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

此处使用 Spring Cloud LoadBalancer 提供的 RandomLoadBalancer

java 复制代码
package org.example.order.config;  
  
  
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 LoadBalancerConfig {  
    @Bean  
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,  
        LoadBalancerClientFactory loadBalancerClientFactory) {  
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);  
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,  
                ServiceInstanceListSupplier.class), name);  
    }  
}
  • 注意:该类需要满足:
    1. 不用 @Configuration 注解
    2. 在组建扫描范围内
  1. 使用 @LoadBanlancerClient 或者 @LoadBalancerClients 注解

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

由于我们项目中只有一个服务提供者,所以使用 @LoadBalancerClient

java 复制代码
package org.example.order.config;  
  
import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  
  
@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)  
@Configuration  
public class BeanConfig {  
    @Bean  
    @LoadBalanced    public RestTemplate restTemplate() {  
        return new RestTemplate();  
    }  
}
  • @LoadBalancerClient 注解说明
    1. name:该负载均衡策略对哪个服务生效(服务提供方)
    2. configuration:该负载均衡策略,用哪个负载均衡策略实现

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,也就是 http://product-service/product/1001
  2. originalUri.getHost():从 uri 中获取路径的主机名,也就是服务 idproduct-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();
        }
    }
}
相关推荐
Genlt19 小时前
Docker 镜像与 Dockerfile 基础指南:从编写到管理
后端
用户329104422504119 小时前
基于 Claude Code 实现 CI/CD 完整流程
前端·后端
星栈19 小时前
别让 API 跳去登录页:我在 Axum 里做了认证失败双通道
前端·后端·开源
Cache技术分享19 小时前
416. 现代 Java I/O 最佳实践 - 高效、简洁、安全地处理文本与数据
前端·后端
倚栏听风雨19 小时前
EdgeValue 详细分析
后端
用户7138742290019 小时前
OAuth 2.0 中的state参数:从规范到实践的深度解析
后端
倚栏听风雨19 小时前
StateGraph 详细分析
后端
用户7138742290019 小时前
Cookie 深度技术指南:从原理到安全实践
后端
倚栏听风雨19 小时前
AsyncCommandAction 详细分析
后端