Spring Cloud LoadBalancer 教程

LoadBalancerRequest 接口

在 Spring Cloud LoadBalancer 中,LoadBalancerRequest 作为一个函数式接口,主要作用是封装需要在负载均衡选中的服务实例上执行的请求逻辑。它充当了负载均衡选择(由 LoadBalancerClient 负责)与实际服务调用(例如 HTTP 请求)之间的桥梁,不仅承载核心调用逻辑,还支持在请求执行前后嵌入自定义处理(如日志记录、metrics 指标统计、失败重试等增强功能)。

当 LoadBalancerClient 在选中实例后,将触发执行 LoadBalancerRequest 。LoadBalancerRequest 将作为“请求执行的载体”,封装了针对服务实例的具体调用逻辑(如发送 HTTP 请求、处理响应)。

LoadBalancerRequest 实现了“负载均衡选择”与“执行请求” 逻辑解耦,这使负载均衡客户端(LoadBalancerClient)无需关心请求的具体内容,只需专注于实例选择和调用触发。

📢 注意:LoadBalancerRequest 需要配合 LoadBalancerClient.execute(...) 方法使用,适用于所有需要通过负载均衡调用服务的场景(如 RestTemplate、自定义服务调用等)。

接口定义

LoadBalancerRequest 接口仅定义一个核心方法 apply(),用于在指定实例上执行 HTTP 请求。代码如下:

package org.springframework.cloud.client.loadbalancer;

import org.springframework.cloud.client.ServiceInstance;

public interface LoadBalancerRequest<T> {

	/**
	 * 对选中的服务实例执行实际的HTTP请求操作,并返回结果。
	 *
	 * 该方法由 LoadBalancerClient 在完成服务实例选择后调用,参数为负载均衡器选中的服务实例(ServiceInstance),
	 * 开发者可在此方法中实现具体的服务调用逻辑(如 HTTP 请求、RPC 调用等),同时可嵌入前置/后置处理逻辑。
	 *
	 * @param instance 负载均衡器选中的服务实例,包含服务的主机、端口等连接信息
	 * @return 服务调用的响应结果,类型为泛型 T
	 * @throws Exception 执行请求过程中可能抛出的异常(如连接超时、服务不可用等)
	 */
	T apply(ServiceInstance instance) throws Exception;

}

注意,LoadBalancerRequest 通常与 LoadBalancerClient 配合使用,完整流程如下:

(1)开发者通过 lambda 或匿名类定义 LoadBalancerRequest,封装具体的请求逻辑(如使用 RestTemplate 发送 HTTP 请求)。

(2)调用 LoadBalancerClient.execute(serviceId, request) 方法,触发负载均衡流程:

    • LoadBalancerClient 先通过负载均衡策略选择一个服务实例(调用 choose(serviceId))。

    • 接着调用 request.apply(instance),在选中的实例上执行请求逻辑。

(3)获取 apply 方法的返回值,即请求结果。


示例:使用 LoadBalancerRequest 调用服务

注意:这里不需要使用 @LoadBalanced 去修饰 RestTemplate,直接创建一个 RestTemplate 即可。

下面代码展示了如何通过 LoadBalancerRequest 配合 LoadBalancerClient 实现负载均衡调用。

代码如下:

package com.hxstrive.springcloud.loadbalancer_demo.demo3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.web.client.RestTemplate;
import java.net.URI;

@SpringBootApplication
@EnableDiscoveryClient
public class LoadbalancerDemoApplication implements CommandLineRunner {

    // 注入 LoadBalancerClient 实例
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    public static void main(String[] args) {
        SpringApplication.run(LoadbalancerDemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        // org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient@5c40498b
        System.out.println(loadBalancerClient);

        // 服务名称
        final String serviceId = "USER-SERVICE";
        // 目标接口地址,主机部分为服务名,而非实际IP地址
        final URI originalUri = URI.create("http://" + serviceId + "/api/users");
        // 通过 LoadBalancerClient 调用服务
        String result = loadBalancerClient.execute(serviceId, new LoadBalancerRequest<String>() {
            // 发起请求
            // instance 为 LoadBalancerClient 通过负载均衡算法获取到的服务实例
            @Override
            public String apply(ServiceInstance instance) throws Exception {
                // LoadBalancerUriTools.reconstructURI() 工具方法用于将服务地址中的服务名替换为实际的IP地址
                String url = LoadBalancerUriTools.reconstructURI(instance, originalUri).toString();
                return new RestTemplate().getForObject(url, String.class);
            }
        });
        System.out.println(result);
    }

}

注意:上述代码实现了 CommandLineRunner 接口,应用启动成功后自动执行 run(String... args) 方法,避免为了测试去创建一个控制器,通过浏览器访问,这样可以快速验证一些问题。

代码通过创建一个匿名 LoadBalancerRequest 内部类封装“根据实例地址发送 HTTP 请求” 的逻辑,loadBalancerClient.execute() 负责选择实例并触发执行。这样就不需要手动指定实例地址,由负载均衡器自动选择,实现了服务调用的负载均衡透明化。

  

与其他组件的集成

RestTemplate 集成

通过@LoadBalanced 注解修饰的 RestTemplate,其内部也是通过 LoadBalancerRequest 封装 HTTP 请求,由 LoadBalancerClient 完成实例选择和具体调用。

这样就避免我们自己去实现 LoadBalancerRequest,封装 HTTP 实际请求逻辑,开发就非常便捷。例如:

package com.hxstrive.springcloud.loadbalancer_demo.demo2.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 AppConfig {

    // 注入 RestTemplate,并添加 @LoadBalanced 注解启用负载均衡
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

LoadBalancerClient 集成

LoadBalancerClient 的 execute() 方法的核心逻辑就是调用 LoadBalancerRequest.apply(instance),是连接实例选择与请求执行的关键。

如下图,LoadBalancerClient 通过调用 apply() 发起实际 HTTP 调用:

LoadBalancerRequest 接口

更多细节可以分析源码。

拦截器与增强

除了与 RestTemplate 和 LoadBalancerClient 集成,还可通过 LoadBalancerRequestTransformer 对 LoadBalancerRequest 进行增强(如添加请求头、日志记录),实现请求的预处理。

LoadBalancerRequestTransformer 是一个用于对负载均衡均衡请求进行预处理转换的接口,主要作用是在请求发送到目标服务实例之前,对请求的元数据(如请求头、请求参数等)进行自定义修改或增强,以满足特定业务场景需求。

LoadBalancerRequestTransformer 接口定义如下:

package org.springframework.cloud.client.loadbalancer;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpRequest;

/**
 * 负载均衡请求转换器,用于在请求发送到目标服务实例前对 HTTP 请求进行自定义转换。
 * 支持基于目标服务实例的信息(如元数据、地址等)动态修改请求内容,如添加请求头、调整路径等。
 * 
 * @Order(0) 注解指定了默认的执行顺序为0,多个转换器可通过调整Order值控制执行先后(值越小越先执行)
 */
@Order(0)
public interface LoadBalancerRequestTransformer {

    /**
     * 转换器的默认执行顺序,值为0
     */
    int DEFAULT_ORDER = 0;

    /**
     * 对HTTP请求进行转换,可基于目标服务实例的信息定制请求内容。
     * 
     * @param request  原始的HTTP请求对象(包含请求头、方法、URI等信息)
     * @param instance 经过负载均衡选择的目标服务实例(可从中获取服务ID、主机、端口、元数据等)
     * @return 转换后的HTTP请求对象,将被用于实际发送到目标服务实例
     */
    HttpRequest transformRequest(HttpRequest request, ServiceInstance instance);
}

使用场景示例

(1)统一添加请求头:为所有经过负载均衡的请求添加 X-LoadBalancer-Service 头,标识目标服务名。

package com.hxstrive.springcloud.loadbalancer_demo.demo4.transformer;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.stereotype.Component;

@Component
public class ServiceHeaderTransformer implements LoadBalancerRequestTransformer {

    @Override
    public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
        // 基于目标服务实例的信息构建自定义请求头
        final HttpHeaders headers = new HttpHeaders();
        headers.putAll(request.getHeaders());
        headers.add("X-LoadBalancer-Service", instance.getServiceId()); // 添加服务名头
        
        // 返回包装了新请求头的 HttpRequest
        return new HttpRequestWrapper(request) {
            @Override
            public HttpHeaders getHeaders() {
                System.out.println("->> headers=" + headers);
                return headers;
            }
        };
    }

}

(2)根据实例元数据调整请求:若服务实例的元数据中包含 version 信息,可动态修改请求路径适配版本。

package com.hxstrive.springcloud.loadbalancer_demo.demo4.transformer;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;

@Component
public class VersionPathTransformer implements LoadBalancerRequestTransformer {

    @Override
    public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
        // 从服务实例元数据中获取版本信息
        String version = instance.getMetadata().getOrDefault("version", "v1");
        // 原始请求路径(如 /api/user)
        String originalPath = request.getURI().getPath();
        // 拼接版本路径(如 /v2/api/user)
        String newPath = "/" + version + originalPath;
        
        // 构建新的 URI 并包装请求
        final URI newUri = UriComponentsBuilder.fromUri(request.getURI())
                .path(newPath)
                .build()
                .toUri();
        
        return new HttpRequestWrapper(request) {
            @Override
            public URI getURI() {
                System.out.println("->> newUri=" + newUri);
                return request.getURI(); // 为了能顺利调用,不返回 newUri
            }
        };
    }

}

💥注意,LoadBalancerRequestTransformer 通常通过以下方式生效:

(1)将自定义 LoadBalancerRequestTransformer 注册为 Spring 容器中的 Bean。

(2)负载均衡框架会自动发现这些转换器,并在请求执行前(LoadBalancerRequest.apply 阶段)按顺序调用它们的 transformRequest 方法。

(3)转换后的请求会被发送到目标服务实例。

编写完自己的 LoadBalancerRequestTransformer 后,可以在启动项目的 main() 方法打印出来,看看是否注册,如下:

package com.hxstrive.springcloud.loadbalancer_demo.demo4;

//...

@RestController
@SpringBootApplication
@EnableDiscoveryClient
public class LoadbalancerDemoApplication {

    @Autowired
    private RestTemplate restTemplate;

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(LoadbalancerDemoApplication.class, args);
        // 打印所有已加载的转换器
        Map<String, LoadBalancerRequestTransformer> transformers = context.getBeansOfType(LoadBalancerRequestTransformer.class);
        System.out.println("Loaded transformers: " + transformers.keySet());
    }

    @GetMapping("/getData")
    public String getData() throws Exception {
        // 关键:用服务名代替具体 IP:端口,LoadBalancer 会自动处理
        String url = "http://USER-SERVICE/api/users";  // 服务名与提供者的 spring.application.name 一致
        return restTemplate.getForObject(url, String.class);
    }

}

启动应用程序将输出如下信息:

Loaded transformers: [serviceHeaderTransformer, versionPathTransformer, loadBalancerServiceInstanceCookieTransformer, xForwarderHeadersTransformer]

继续访问 http://localhost:8080/getData 地址,输出信息如下:

->> ServiceHeaderTransformer#transformRequest()
->> VersionPathTransformer#transformRequest()
->> newUri=http://localhost:7003/api/users/v1/api/users
->> headers=[Accept:"text/plain, application/json, application/*+json, */*", Content-Length:"0", X-LoadBalancer-Service:"USER-SERVICE"]

根据输出得知,请求确实被修改了。

总结

LoadBalancerRequest 是 Spring Cloud LoadBalancer 中封装请求执行逻辑的核心接口,通过函数式设计简化了“选中实例后执行请求”的流程。它与 LoadBalancerClient 协同工作,使开发者无需关心实例选择细节,只需专注于具体的服务调用逻辑,同时支持通过转换器(LoadBalancerRequestTransformer)进行请求增强,灵活满足日志、追踪、认证等附加需求。

注意:在实际开发中,LoadBalancerRequest 通常由框架自动创建(如 @LoadBalanced RestTemplate),但了解其原理有助于理解负载均衡的执行链路和自定义扩展。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号