feign负载均衡

在没有网关和注册中心的情况下,如果你手动写死两个副本(服务实例)的地址,Feign 默认是不能自动完成负载均衡的 ,除非你 配合 Ribbon 或其他负载均衡组件 来实现。


✅ 实现方式(可行方案):

你可以通过以下方式让 Feign 实现负载均衡:

方式一:使用 Ribbon(Spring Cloud Netflix Ribbon)

虽然 Spring Cloud 已逐步废弃 Ribbon,但在一些项目中仍可用。

步骤:

  1. 定义服务名并配置多个地址:
yaml 复制代码
my-service:
  ribbon:
    listOfServers: http://192.168.1.10:8080,http://192.168.1.11:8080
  1. Feign 接口中使用服务名:
java 复制代码
@FeignClient(name = "my-service")
public interface MyServiceClient {
    @GetMapping("/api/test")
    String test();
}

这样,Feign + Ribbon 就可以在这两个地址之间自动负载均衡。


方式二:自定义 Feign 的 TargeterClient 实现自己的负载均衡逻辑(高级用法)

你可以不用 Ribbon,而是实现自己的 FeignClientFactoryBean 或注入一个自定义 Client 实例来控制请求分发逻辑,这样可以自己轮询或者按权重访问写死的服务实例。


❌ 不可行情况(默认行为):

如果你直接写死 Feign 的 URL:

java 复制代码
@FeignClient(name = "my-service", url = "http://192.168.1.10:8080")

那么 Feign 就只能请求这个 URL,不会做负载均衡。


✅ 推荐实践(若不使用注册中心):

  1. 如果你不想接入注册中心(如 Eureka/Nacos):

    • 用 Ribbon(或 Spring Cloud LoadBalancer)+ Feign 是最简单的方式。
  2. 如果使用的是 Spring Cloud 2020+,建议用 Spring Cloud LoadBalancer 替代 Ribbon。


Spring Cloud LoadBalancer

Feign 搭配 Spring Cloud LoadBalancer(SC LoadBalancer)是 Spring Cloud 2020 及以后的推荐方式 ,用于在 没有 Ribbon、没有注册中心 的情况下,仍然实现 多实例负载均衡调用


✅ 一、场景目标

你有两个服务实例,地址写死,例如:

复制代码
http://192.168.1.10:8080
http://192.168.1.11:8080

你希望 Feign 每次调用时在这两个地址之间负载均衡。


✅ 二、实现步骤

1. 添加依赖

确保使用的是 Spring Cloud 2020+,引入以下依赖:

xml 复制代码
<!-- Feign 依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- LoadBalancer 依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

2. 配置固定服务列表(写死多个地址)

application.yml 中配置:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      clients:
        my-service:
          instances:
            - uri: http://192.168.1.10:8080
            - uri: http://192.168.1.11:8080

注意:这是 Spring Cloud LoadBalancer 的静态服务配置方式,不需要注册中心。


3. 定义 FeignClient 接口

java 复制代码
@FeignClient(name = "my-service") // name 对应配置的 clients.my-service
public interface MyServiceClient {
    @GetMapping("/api/test")
    String test();
}

4. 启用 Feign 和 LoadBalancer

确保主类上有:

java 复制代码
@EnableFeignClients
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

✅ 三、负载均衡策略(可选)

你可以自定义负载均衡策略:

java 复制代码
@Configuration
public class LoadBalancerConfig {

    @Bean
    @LoadBalanced
    ReactorLoadBalancer<ServiceInstance> myServiceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory factory) {
        return new RoundRobinLoadBalancer(
            factory.getLazyProvider("my-service", ServiceInstanceListSupplier.class),
            "my-service"
        );
    }
}

默认就是 RoundRobin(轮询),也可以实现自定义策略。


✅ 四、调用示意

当你注入并调用 MyServiceClient.test() 方法时,Spring Cloud LoadBalancer 会自动从你配置的两个实例中轮询选择一个实例进行调用。


实现熔断

Spring Cloud LoadBalancer 本身不具备熔断功能 。它的职责是实现客户端负载均衡(选择可用实例),不会探测实例是否健康,也不会在实例失败时自动熔断或剔除


✅ 你关心的问题:

一个副本在更新时不可用,LoadBalancer 会不会跳过它?

  • 不会自动跳过。
  • LoadBalancer 仍然会轮询或使用指定策略挑选这个"坏"实例。
  • 如果你不加控制,它会把请求发给这个更新中的实例,导致访问失败。

✅ 如何解决这个问题?

✅ 方案一:结合 Spring Cloud Circuit Breaker(推荐)

Spring Cloud 提供 spring-cloud-starter-circuitbreaker-resilience4j(或 Sentinel/Hystrix)来实现熔断机制。

步骤如下:

1. 添加依赖:
xml 复制代码
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
2. 修改你的 Feign 接口方法,结合熔断:
java 复制代码
@FeignClient(name = "my-service")
public interface MyServiceClient {
    @GetMapping("/api/test")
    String test();
}

然后在调用方使用如下方式包装熔断:

java 复制代码
@Service
public class MyServiceWrapper {

    private final MyServiceClient client;
    private final CircuitBreakerFactory<?, ?> cbFactory;

    public MyServiceWrapper(MyServiceClient client, CircuitBreakerFactory<?, ?> cbFactory) {
        this.client = client;
        this.cbFactory = cbFactory;
    }

    public String safeCall() {
        return cbFactory.create("myServiceCircuitBreaker")
            .run(() -> client.test(), throwable -> fallback());
    }

    private String fallback() {
        return "fallback result";
    }
}

这样,如果某个实例失败,熔断器会在短期内阻止再请求那个实例。


✅ 方案二:借助健康检查 + 自定义过滤器

你也可以定期在服务端维护一个"健康实例列表",通过 Spring Cloud LoadBalancer 的 ServiceInstanceListSupplier 自定义逻辑,把不健康的副本剔除(但这需要你自己维护健康状态)。


✅ 总结

功能 是否由 LoadBalancer 提供
负载均衡 ✅ 是
熔断(失败跳过) ❌ 否(需配合 Circuit Breaker)
实例健康剔除 ❌ 否(需你手动实现)

数据库获取副本

是的,Spring Cloud LoadBalancer 支持自定义服务实例列表来源,你完全可以把副本地址从数据库中读取,并动态更新这些地址。这样当某个副本不可用时,你只需从数据库中移除它,LoadBalancer 就不会再调用它。


✅ 实现目标

  • 使用 Spring Cloud LoadBalancer
  • 实例地址不来自注册中心,也不写死在配置文件中
  • 从数据库查询服务实例列表
  • 可以通过删除数据库记录控制负载均衡行为

✅ 实现方式

你需要自定义一个 ServiceInstanceListSupplier

1. 定义实体类(用于从数据库中取服务地址)

java 复制代码
public class MyServiceInstance implements ServiceInstance {
    private final String serviceId;
    private final URI uri;

    public MyServiceInstance(String serviceId, String host, int port) {
        this.serviceId = serviceId;
        this.uri = URI.create("http://" + host + ":" + port);
    }

    @Override public String getInstanceId() { return null; }
    @Override public String getServiceId() { return serviceId; }
    @Override public String getHost() { return uri.getHost(); }
    @Override public int getPort() { return uri.getPort(); }
    @Override public boolean isSecure() { return false; }
    @Override public URI getUri() { return uri; }
    @Override public Map<String, String> getMetadata() { return Collections.emptyMap(); }
}

2. 自定义 ServiceInstanceListSupplier

java 复制代码
@Component
public class DbServiceInstanceListSupplier implements ServiceInstanceListSupplier {

    private final String serviceId = "my-service";
    private final MyInstanceRepository repository; // 你自己的DAO类

    public DbServiceInstanceListSupplier(MyInstanceRepository repository) {
        this.repository = repository;
    }

    @Override
    public String getServiceId() {
        return serviceId;
    }

    @Override
    public Flux<List<ServiceInstance>> get() {
        List<MyInstanceEntity> dbInstances = repository.findAll(); // 从数据库获取
        List<ServiceInstance> instances = dbInstances.stream()
            .map(i -> new MyServiceInstance(serviceId, i.getHost(), i.getPort()))
            .collect(Collectors.toList());
        return Flux.just(instances);
    }
}

3. 启用 Feign 调用

java 复制代码
@FeignClient(name = "my-service")
public interface MyServiceClient {
    @GetMapping("/api/test")
    String test();
}

✅ 实现效果

  • 每次 LoadBalancer 获取实例列表时,都会从数据库中查询;
  • 当你想"熔断"某个副本,只需从数据库中移除该条记录;
  • 系统会自动跳过失效副本。

⚠️ 注意

  • get() 方法可以加缓存(比如用 @Scheduled 定时刷新实例列表),避免每次都查库;
  • 确保数据库查询不会成为瓶颈。

自定义 Feign 的负载均衡逻辑

当然可以,下面我给你一个完整的示例:自定义 Feign 的 Client 实现负载均衡逻辑,不依赖 Ribbon、Spring Cloud LoadBalancer,也不需要注册中心。


✅ 场景

你手动指定两个服务实例地址:

复制代码
http://192.168.1.10:8080
http://192.168.1.11:8080

你希望 Feign 每次调用时,在它们之间轮询负载均衡。


✅ 步骤详解

1. 定义一个轮询负载均衡器

java 复制代码
@Component
public class MySimpleLoadBalancer {

    private final List<String> servers = List.of(
        "http://192.168.1.10:8080",
        "http://192.168.1.11:8080"
    );

    private final AtomicInteger index = new AtomicInteger(0);

    public String chooseServer() {
        int i = Math.abs(index.getAndIncrement() % servers.size());
        return servers.get(i);
    }
}

2. 自定义 Feign Client 实现类

java 复制代码
@Component
public class MyFeignClient implements Client {

    private final Client delegate = new Client.Default(null, null); // 使用默认 Feign 客户端
    private final MySimpleLoadBalancer loadBalancer;

    public MyFeignClient(MySimpleLoadBalancer loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        String baseUrl = loadBalancer.chooseServer();

        // 重写原始请求的 URL
        String originalUrl = request.url(); // 例如 http://dummy/api/xxx
        URI originalUri = URI.create(originalUrl);
        URI newUri = URI.create(baseUrl + originalUri.getPath() + (originalUri.getQuery() != null ? "?" + originalUri.getQuery() : ""));

        Request newRequest = Request.create(
            request.httpMethod(),
            newUri.toString(),
            request.headers(),
            request.body(),
            request.charset(),
            request.requestTemplate()
        );

        return delegate.execute(newRequest, options);
    }
}

3. 注入到 Feign 配置中

java 复制代码
@Configuration
public class FeignCustomConfig {

    @Bean
    public Client feignClient(MySimpleLoadBalancer loadBalancer) {
        return new MyFeignClient(loadBalancer);
    }
}

4. 定义 Feign 接口(URL 随便写,反正会被你自定义 Client 改写)

java 复制代码
@FeignClient(name = "dummy", url = "http://dummy", configuration = FeignCustomConfig.class)
public interface MyServiceClient {
    @GetMapping("/api/test")
    String test();
}

✅ 效果

  • 每次调用 MyServiceClient.test() 时,都会通过你自己的轮询逻辑在两个地址之间切换;
  • 你可以随时改写轮询逻辑为:加权、最少连接数、健康检查等;
  • 不依赖 Ribbon、LoadBalancer 或注册中心。

Ribbon、LoadBalancer的实现原理

非常准确的问题!

是的,从 原理上讲 ,无论是旧版的 Ribbon ,还是新版的 Spring Cloud LoadBalancer ,它们实现 Feign 的客户端负载均衡,核心就是:替换 Feign 默认的 Client 实现,注入它们自己的逻辑


✅ Feign 调用流程回顾

Feign 的调用大致流程是:

  1. 创建 Feign 接口代理;
  2. 封装请求为 Request
  3. 调用 Client.execute(Request, Options)
  4. 执行真正的 HTTP 请求。

✅ Ribbon 的实现原理(旧版)

在使用 Ribbon 时,Spring Cloud 会注入一个 LoadBalancerFeignClient

java 复制代码
public class LoadBalancerFeignClient implements Client {
    private final Client delegate;
    private final SpringClientFactory clientFactory;

    @Override
    public Response execute(Request request, Request.Options options) {
        // 从 Request 的 URL 里提取服务名
        // 用 Ribbon 的负载均衡策略选择实例
        // 替换 URL,再用 delegate(如 OkHttp)执行请求
    }
}

特点:

  • 从请求 URL 中解析服务名(如 my-service);
  • 用 Ribbon 的 ILoadBalancer 查找实例;
  • 替换 URL;
  • 交由底层 HTTP 客户端(如 OkHttpClient)执行。

✅ Spring Cloud LoadBalancer 的实现原理(新版)

Ribbon 被废弃后,Spring Cloud 使用了 LoadBalancerClientFactory 来创建实例列表,替代 Ribbon。

注入的也是一个 LoadBalancerFeignClient,只是实现不同:

java 复制代码
public class LoadBalancerFeignClient implements Client {
    private final Client delegate;
    private final LoadBalancerClientFactory clientFactory;

    @Override
    public Response execute(Request request, Request.Options options) {
        // 从 serviceId 获取 ServiceInstanceListSupplier
        // 负载均衡选择一个实例
        // 替换 request.url 为真实 IP+端口
        // 调用底层 HTTP Client 发请求
    }
}

✅ 总结

框架 作用原理
Feign 默认 直接用 URL 发请求(无负载均衡)
Feign + Ribbon 使用 Ribbon 的负载均衡器 + 重写 Client 逻辑
Feign + LoadBalancer 使用 Spring Cloud LoadBalancer + 重写 Client 逻辑
你自定义的实现 一样地,重写 Client,插入自己的负载均衡策略

相关推荐
小晶晶京京1 天前
day35-负载均衡
运维·网络·网络协议·学习·负载均衡
CodeDevMaster2 天前
Gemini Balance:轻松实现Gemini API负载均衡与无缝切换的终极指南
负载均衡·api·gemini
扶风呀5 天前
具有熔断能力和活性探测的服务负载均衡解决方案
运维·负载均衡
Hello World呀5 天前
springcloud负载均衡测试类
spring·spring cloud·负载均衡
菜菜子爱学习6 天前
Nginx学习笔记(七)——Nginx负载均衡
笔记·学习·nginx·负载均衡·运维开发
扶风呀6 天前
负载均衡详解
运维·后端·微服务·面试·负载均衡
PXM的算法星球6 天前
spring gateway配合nacos实现负载均衡
spring·gateway·负载均衡
抛物线.9 天前
Docker Compose 部署高可用 MongoDB 副本集集群(含 Keepalived + HAProxy 负载均衡)
mongodb·docker·负载均衡
ZNineSun10 天前
什么是负载均衡,有哪些常见算法?
负载均衡·osi·七层网络模型
竹竿袅袅10 天前
Nginx 反向代理与负载均衡架构
nginx·架构·负载均衡