feign负载均衡
在没有网关和注册中心的情况下,如果你手动写死两个副本(服务实例)的地址,Feign 默认是不能自动完成负载均衡的,除非你 配合 Ribbon 或其他负载均衡组件 来实现。
✅ 实现方式(可行方案):
你可以通过以下方式让 Feign 实现负载均衡:
方式一:使用 Ribbon(Spring Cloud Netflix Ribbon)
虽然 Spring Cloud 已逐步废弃 Ribbon,但在一些项目中仍可用。
步骤:
- 定义服务名并配置多个地址:
my-service:ribbon:listOfServers: http://192.168.1.10:8080,http://192.168.1.11:8080
- Feign 接口中使用服务名:
@FeignClient(name = "my-service")
public interface MyServiceClient {@GetMapping("/api/test")String test();
}
这样,Feign + Ribbon 就可以在这两个地址之间自动负载均衡。
方式二:自定义 Feign 的 Targeter
或 Client
实现自己的负载均衡逻辑(高级用法)
你可以不用 Ribbon,而是实现自己的 FeignClientFactoryBean
或注入一个自定义 Client
实例来控制请求分发逻辑,这样可以自己轮询或者按权重访问写死的服务实例。
❌ 不可行情况(默认行为):
如果你直接写死 Feign 的 URL:
@FeignClient(name = "my-service", url = "http://192.168.1.10:8080")
那么 Feign 就只能请求这个 URL,不会做负载均衡。
✅ 推荐实践(若不使用注册中心):
-
如果你不想接入注册中心(如 Eureka/Nacos):
- 用 Ribbon(或 Spring Cloud LoadBalancer)+ Feign 是最简单的方式。
-
如果使用的是 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+,引入以下依赖:
<!-- 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
中配置:
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 接口
@FeignClient(name = "my-service") // name 对应配置的 clients.my-service
public interface MyServiceClient {@GetMapping("/api/test")String test();
}
4. 启用 Feign 和 LoadBalancer
确保主类上有:
@EnableFeignClients
@SpringBootApplication
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}
✅ 三、负载均衡策略(可选)
你可以自定义负载均衡策略:
@Configuration
public class LoadBalancerConfig {@Bean@LoadBalancedReactorLoadBalancer<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. 添加依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
2. 修改你的 Feign 接口方法,结合熔断:
@FeignClient(name = "my-service")
public interface MyServiceClient {@GetMapping("/api/test")String test();
}
然后在调用方使用如下方式包装熔断:
@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. 定义实体类(用于从数据库中取服务地址)
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
@Component
public class DbServiceInstanceListSupplier implements ServiceInstanceListSupplier {private final String serviceId = "my-service";private final MyInstanceRepository repository; // 你自己的DAO类public DbServiceInstanceListSupplier(MyInstanceRepository repository) {this.repository = repository;}@Overridepublic String getServiceId() {return serviceId;}@Overridepublic 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 调用
@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. 定义一个轮询负载均衡器
@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 实现类
@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;}@Overridepublic Response execute(Request request, Request.Options options) throws IOException {String baseUrl = loadBalancer.chooseServer();// 重写原始请求的 URLString originalUrl = request.url(); // 例如 http://dummy/api/xxxURI 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 配置中
@Configuration
public class FeignCustomConfig {@Beanpublic Client feignClient(MySimpleLoadBalancer loadBalancer) {return new MyFeignClient(loadBalancer);}
}
4. 定义 Feign 接口(URL 随便写,反正会被你自定义 Client 改写)
@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 的调用大致流程是:
- 创建 Feign 接口代理;
- 封装请求为
Request
; - 调用
Client.execute(Request, Options)
; - 执行真正的 HTTP 请求。
✅ Ribbon 的实现原理(旧版)
在使用 Ribbon 时,Spring Cloud 会注入一个 LoadBalancerFeignClient
:
public class LoadBalancerFeignClient implements Client {private final Client delegate;private final SpringClientFactory clientFactory;@Overridepublic 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
,只是实现不同:
public class LoadBalancerFeignClient implements Client {private final Client delegate;private final LoadBalancerClientFactory clientFactory;@Overridepublic 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 ,插入自己的负载均衡策略 |