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,插入自己的负载均衡策略

相关推荐
YJQ99671 天前
LVS负载均衡群集解析:理解LVS-NAT的工作原理
php·负载均衡·lvs
晴天Y281 天前
基于LVS实现负载均衡,对NAT模式的介绍和使用案例
服务器·负载均衡·lvs
李匠20243 天前
C++负载均衡远程调用学习之负载均衡算法与实现
运维·c++·学习·负载均衡
李匠20244 天前
C++负载均衡远程调用学习之HOOK注册机制
java·c++·学习·负载均衡
程序猿不脱发24 天前
什么是负载均衡?NGINX是如何实现负载均衡的?
运维·nginx·负载均衡
xbd_zc5 天前
【Ansible自动化运维实战:从Playbook到负载均衡指南】
运维·自动化·ansible·负载均衡
李匠20245 天前
C++负载均衡远程调用学习之 Dns-Route关系构建
运维·c++·学习·负载均衡
請叫我菜鳥6 天前
Nginx反向代理的负载均衡配置
运维·nginx·负载均衡
gaog2zh7 天前
010302-oss_反向代理_负载均衡-web扩展2-基础入门-网络安全
网络安全·负载均衡·oss·反向代理