负载均衡调用过程:
我们先从启动加载配置入口开始看。DefaultFeignLoadBalancedConfiguration是默认的负载均衡配置。这里其实就是扩展点,当没有feignClient bean加载时就会默认加载该类。如果想要实现自己的只要覆盖配置该bean即可。
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
可以看下LoadBalancerFeignClient这个构造函数哪些地方在用,其实这里实现了4种,最后一个是我自己实现用来做feign调用cat埋点的。
LoadBalancerFeignClient的代码可以发现使用了装饰器模式对client赋予额外的负载均衡能力。
//实现了client接口
public class LoadBalancerFeignClient implements Client {
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
private final Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;
//实际最后的使用的client是外部传入的 Client.Default
//这里对client包装了额外的功能:负载均衡
public LoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) {
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
//底层还是调用的netflix的ribbon
//里面是核心代码
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
//..
}
}
这里代码稍显复杂,没必要贴出来。但是要知道主要干两件事:
- 使用负载均衡算法找到可以调用的Server,默认是随机。
- 然后调用外部传入client的具体方法去发送请求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
//负载均衡命令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
//submit里面会去获取最终用到的Server
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//实际上最终执行http请求的还是外部传入的client,毕竟这只是做负载的增强
//这里默认实现是FeignLoadBalancer 里面就会调用传入的client
//默认会使用java的HttpURLConnection
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
附一张执行流程图
服务列表怎么维护的?
我们先看下这个初始化配置的入口 RibbonClientConfiguration。
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
//这个类的父类就是DynamicServerListLoadBalancer
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
这里其实理清楚这几个接口的关系其实就知道他们是怎么协同运作的。ribbon五个核心接口。
- ServerList. 关联注册中心客户端取获取服务列表。比如DiscoveryEnabledNIWSServerList就是走EurekaClient取获取列表。
- ServerListFilter. 去过滤获取到的列表
- IRule. 负载规则 随机,轮训等
- IPing. 怎么去检测服务器的一个状态,包括直接不检查、判断Server状态、或者发送请求检查等。
- ServerListUpdater. 采用什么策略来更新服务器列表。PollingServerListUpdater默认30秒拉取一次列表。
附一张我画的图说明怎么运行的。allserverlist是所有列表,upserverlist是可用列表。