リボンのソースコード解析
自動組立
頼る
<!--添加ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
spring-cloud-starter-netflix-ribbon
スターター コンポーネントを参照してください。依存パッケージの spring.factories ファイルに移動して、
RibbonAutoConfigurationを確認できます。
@AutoConfigurationBefore は、
特定の構成クラスがロードされる前に現在の構成クラスがロードされることを示します。
LoadBalancerAutoConfiguration の前に、RibbonAutoConfiguration をロードする必要があることを説明します。
挿入されたクラスを見てみましょう
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
new SpringClientFactory()
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
親クラスを見てみましょう: NamedContextFactory
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
ibbonClientConfiguration をパラメータとして親クラスに渡します。
ApplicationListener を実装するために、RibbonClientConfiguration がどのように Bean 定義の
RibbonApplicationContextInitializer に解析されるかを見てみましょう。これにより、
Springboot は開始時に onApplicationEvent メソッドを呼び出します。
protected void initialize() {
if (clientNames != null) {
for (String clientName : clientNames) {
this.springClientFactory.getContext(clientName);
}
}
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
initialize();
}
getContext メソッド
org.springframework.cloud.context.named.NamedContextFactory#getContextを見てみましょう。
getContext メソッドは createContext メソッド
org.springframework.cloud.context.named.NamedContextFactory#createContextを呼び出します
。 このdefaultConfigTypeは、渡したRibbonClientConfigurationです。
登録方法を見てみましょう
public void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
this.reader.register(annotatedClasses);
}
org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerメソッド
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}
これは、スプリングが構成クラスを解析する方法ではなく、構成クラスを Bean 定義に解析することであることがわかります。
なぜ、RibbonClientConfigurationクラスについてこれほど多くを説明しなければならないのかというと、このクラスは多くのコア クラスを挿入するためです。
ロードバランサー自動構成
LoadBalancerAutoConfiguration が有効になる条件は、コンテナー内に LoadBalancerClient クラスが存在する必要があることがわかります。
このクラスは、RibbonAutoConfiguration に挿入されます。
@LoadBalanced で表される RestTemplate を収集するときは、 Spring が自動的に収集
します。
@Target({
ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
@LoadBalance は複合アノテーションであり、最下層は引き続き @Qualifier アノテーションを使用していることがわかります。
そのため、RestTemplate コレクションを収集できます。
RestTemplate
RestTemplate は InterceptingHttpAccessor を継承するため、インターセプターを取得および設定する機能があります。
loadBalancedRestTemplateInitializer
loadBalancedRestTemplateInitializer() はいくつかのことを初期化し、その中に内部クラスがあり、トラバーサル用に現在の RestTemplate を取得し、トラバーサル カスタマイザは RestTemplate コンポーネントをカスタマイズするために特別に使用され、各カスタマイザを使用して各 RestTemplate をカスタマイズします。このメソッドでは、すべて @LoadBalanced によって注釈が付けられ
ます識別された RestTemplate は、リボンのカスタム インターセプター LoadBalancerInterceptor を追加します。
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
restTemplateCustomizer メソッドでは、ClientHttpRequestInterceptor インターセプターが、入力された RestTemplate ごとにカスタマイズされ、インターセプターはそのサブクラス LoadBalancerInterceptor によって実装されます。
リボン カスタム インターセプターの作成
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
インターセプタ固有のメソッドを追加します。
まず現在のインターセプタ コレクション (List) を取得し、次に、loadBalancerInterceptor を現在のコレクションに追加し、最後に新しいコレクションをrestTemplate に戻します。
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
LoadBalancerInterceptor は、 resttemplate を使用して API を呼び出すときに、
インターセプト メソッド
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#interceptに入ります。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
//当前loadBalancer为RibbonLoadBalancerClient
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
要求が開始されると、リボンは LoadBalancerInterceptor インターセプターを使用して要求をインターセプトします。このインターセプタでは、LoadBalancerClient.execute() メソッドが呼び出されます。Execute
() はサブクラスのリボンLoadBlancerClient によって実行されます。
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(j
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
ILoadBalancer は、 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);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
具体的にはZoneAwareLoadBalancerを返します
ZoneAwareLoadBalancer
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
親クラスDynamicServerListLoadBalancerを見てください。
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
DynamicServerListLoadBalancer の親クラスBaseLoadBalancer
public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
initWithConfig メソッドを見てください。
com.netflix.loadbalancer.BaseLoadBalancer#setPingInterval
public void setPingInterval(int pingIntervalSeconds) {
if (pingIntervalSeconds < 1) {
return;
}
this.pingIntervalSeconds = pingIntervalSeconds;
if (logger.isDebugEnabled()) {
logger.debug("LoadBalancer [{}]: pingIntervalSeconds set to {}",
name, this.pingIntervalSeconds);
}
setupPingTask(); // since ping data changed
}
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
//启动一个任务调度,每隔10秒 ping一次服务实例,检查状态,
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
この手順は主に、タスク スケジューラを起動し、10 秒ごとにサービス インスタンスに ping を実行し、ステータスを確認することです。
残りの初期化
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
EnableAndInitLearnNewServersFeature メソッド
EnableAndInitLearnNewServersFeature(): 定期的な更新
PollingServerListUpdater では、UpdateAction を実行する動作である Runnable スレッドが作成され、一定時間の遅延の後、定期的に Runnable スレッドが実行され、UpdateAction 内の操作が実行されます レジストリを更新し
、デフォルトでは、1 秒後に Runnable スレッドが初めて実行されます。
//その後、Runnable スレッドは 30 秒ごとに実行されます。] 独自のリボンの LoadBalancer にレジストリを更新します。
doupdate メソッドと updateListOfServers メソッドを見てください。
レジストリ センターでは、例として nacos を取り上げています
。com.alibaba.cloud.nacos.ribbon.NacosServerList#getUpdatedListOfServers
public List<NacosServer> getUpdatedListOfServers() {
return this.getServers();
}
private List<NacosServer> getServers() {
try {
String group = this.discoveryProperties.getGroup();
List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, group, true);
return this.instancesToServerList(instances);
} catch (Exception var3) {
throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + this.serviceId, var3);
}
}
以下のselectInstancesメソッド
com.alibaba.nacos.client.naming.NacosNamingService#selectInstances(java.lang.String, java.lang.String, boolean)
public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
return this.selectInstances(serviceName, groupName, healthy, true);
}
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
if (subscribe) {
serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
} else {
serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
}
return this.selectInstances(serviceInfo, healthy);
}
焦点は getServiceInfo メソッドにあります
public ServiceInfo getServiceInfo(String serviceName, String clusters) {
LogUtils.NAMING_LOGGER.debug("failover-mode: " + this.failoverReactor.isFailoverSwitch());
String key = ServiceInfo.getKey(serviceName, clusters);
if (this.failoverReactor.isFailoverSwitch()) {
return this.failoverReactor.getService(key);
} else {
//从缓存中获取实例信息
ServiceInfo serviceObj = this.getServiceInfo0(serviceName, clusters);
//如果不存在则去nacos服务端调用
if (null == serviceObj) {
serviceObj = new ServiceInfo(serviceName, clusters);
this.serviceInfoMap.put(serviceObj.getKey(), serviceObj);
this.updatingMap.put(serviceName, new Object());
//nacos服务端调用
this.updateServiceNow(serviceName, clusters);
this.updatingMap.remove(serviceName);
} else if (this.updatingMap.containsKey(serviceName)) {
synchronized(serviceObj) {
try {
serviceObj.wait(5000L);
} catch (InterruptedException var8) {
LogUtils.NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, var8);
}
}
}
//异步更新内存注册列表
this.scheduleUpdateIfAbsent(serviceName, clusters);
return (ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey());
}
}
ここでの時間制限付きタスクは基本的に終了します
次に、次のプロセスに進み、最初にインターセプトされた場所に戻ります
// 後で呼び出すために負荷分散アルゴリズムを通じてインスタンスを選択します
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
//rule就是Irule
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
Irueによるサービス強度の選択
Irule実装クラスNacosRuleを見てみましょう
com.alibaba.cloud.nacos.ribbon.NacosRule#choose
public Server choose(Object key) {
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
//这个和定时任务调用的一样
List<Instance> instances = namingService.selectInstances(name, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
} else {
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(clusterName)) {
List<Instance> sameClusterInstances = (List)instances.stream().filter((instancex) -> {
return Objects.equals(clusterName, instancex.getClusterName());
}).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
} else {
LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{
name, clusterName, instances});
}
}
//根据负载均衡策略选取一个
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
} catch (Exception var9) {
LOGGER.warn("NacosRule error", var9);
return null;
}
}
このメソッドは、実際にはローカル リングからサービス インスタンスを取得します。取得できた場合は返され、取得できなかった場合は、
nacos サーバーへのリモート呼び出しを開始してサービス インスタンス セットを取得し、
最後に選択します。後続の呼び出しの負荷分散アルゴリズムによるインスタンス ノード
最後に、インターセプター メソッドの IP とポートを置き換えて、リモート http 呼び出しを開始します。