Анализ исходного кода Spring Cloud Ribbon

Java

Каталог столбцов

  1. Анализ исходного кода Spring Cloud OpenFeign
  2. Анализ исходного кода Spring Cloud Ribbon
  3. Анализ исходного кода Spring Cloud Alibaba Sentinel
  4. Анализ исходного кода Spring Cloud Gateway
  5. Анализ исходного кода Spring Cloud Alibaba Nacos

подготовка кода

зависимости

+------------+              +------------+
|            |              |            |
|            |              |            |
|            |              |            |
|            |              |            |
|  consumer  +------------> |   provider |
|            | RestTemplate |            |
|            |              |            |
|            |              |            |
|            |              |            |
+------------+              +------------+

пом-зависимости

Просто добавьте обнаружение службы nacos, на нее ссылаются внутриspring-cloud-ribbonсвязанные зависимости

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

позвонить клиенту

Здесь мы используем самый простойRestTemplateпозвоните, чтобы начатьRibbon

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
	return new RestTemplate();
}

// Controller 使用restTemplate 调用服务提供方接口
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);

Анализ исходного кода

Создать перехватчик звонков

1. Получить все@LoadBalancedотмеченRestTemplate

public class LoadBalancerAutoConfiguration {
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
}

2. УвеличениеLoadBalancerInterceptorлогика обработки

  • не представленspring-retryиспользует
@Bean
public LoadBalancerInterceptor ribbonInterceptor() {
	return new LoadBalancerInterceptor();
}
  • вводитьspring-retryиспользует
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor() {
	return new RetryLoadBalancerInterceptor();
}
  • Бизнес-логика LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept() {
        final URI originalUri = request.getURI();
        // http://demo-provider/req 截取 demo-provider 服务名称
        String serviceName = originalUri.getHost();

        // 默认注入的 RibbonAutoConfiguration.RibbonLoadBalancerClient
        return this.loadBalancer.execute(serviceName,
                // 创建请求对象
                this.requestFactory.createRequest(request, body, execution));
    }
}

выполнить перехватчик

3. Выполнение RibbonLoadBalancerClient

//RibbonAutoConfiguration默认注入的RibbonLoadBalancerClient
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
  return new RibbonLoadBalancerClient(springClientFactory());
}

4. выполнить выполнение

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    public <T> T execute(){
        //获取具体的ILoadBalancer实现
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

        // 调用ILoadBalancer 实现获取Server
        Server server = getServer(loadBalancer, hint);
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        //获取状态记录器,保存此次选取的server
        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
}

Получить ILoadBalancer

5 SpringClientFactory

// bean 工厂生成LoadBalancer 的实现
protected ILoadBalancer getLoadBalancer(String serviceId) {
	return this.springClientFactory.getLoadBalancer(serviceId);
}

// 具体生成逻辑看 RibbonClientConfiguration,这个Bean 只有工厂调用的时候才会创建
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
	return new ZoneAwareLoadBalancer<>();
}

6. Создайте зависимости LoadBalancer

название Реализация по умолчанию эффект
IClientConfig DefaultClientConfigImpl параметры конфигурации ленточного клиента, такие как: настройки времени ожидания, настройки сжатия и т. д.
ServerList NacosServerList Экземпляр целевой службы, конкретная реализация клиента обнаружения службы
ServerListFilter ZonePreferenceServerListFilter Логическая обработка фильтра для списка экземпляров ServerList
IRule ZoneAvoidanceRule Правила выбора Серверов для балансировки нагрузки
IPing DummyPing Реализация метода для проверки доступности службы
ServerListUpdater PollingServerListUpdater Реализация операции для обновления списка серверов

Приведенная выше ссылка на реализацию по умолчаниюRibbonClientConfiguration. ZoneAwareLoadBalancer

Получить экземпляр службы

//Server server = getServer(loadBalancer, hint);  4. excute 方法
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
	return loadBalancer.chooseServer(hint != null ? hint : "default");
}

7. ZoneAwareLoadBalancer

public class ZoneAwareLoadBalancer{
    public ZoneAwareLoadBalancer() {
        // 调用父类初始化方法。 这里会开启实例维护的定时任务等 (具体解析参考 扩展部分)
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }
    @Override
    public Server chooseServer(Object key) {
        // 若是使用的 Nacos 服务发现,则没有 Zone 的概念,直接调用父类的实现
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            return super.chooseServer(key);
        }
        // 以下为有 Zone 的概念 例如 Eureka  (具体)
        ...
        return server;
    }
}
  • вызов родительского классаIRuleРеализовать выбор сервера
public Server chooseServer(Object key) {
	return rule.choose(key);
}

8. Правило выбора PredicateBasedRule

public abstract class PredicateBasedRule {
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        // 获取断言配置
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }
}

9. Утверждение списка услуг ZoneAvoidancePredicate

public class ZoneAvoidancePredicate {
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if (!ENABLED.get()) {
            return true;
        }
        // 还是获取区域配置,如是使用的 Nacos 直接返回true
        String serverZone = input.getServer().getZone();
        if (serverZone == null) {
            // there is no zone information from the server, we do not want to filter
            // out this server
            return true;
        }
        // 区域高可用判断
        ...
    }
}

Расширение: Обслуживание списка серверов

Инициализировать список серверов

выше6. Создайте зависимости LoadBalancer,серединаServerListТаблица экземпляров целевой службы, конкретная реализация клиента обнаружения службы. Давайте посмотрим на реализацию Nacos

public class NacosServerList extends AbstractServerList<NacosServer> {
    @Override
    public List<NacosServer> getInitialListOfServers() {
        return getServers();
    }

    @Override
    public List<NacosServer> getUpdatedListOfServers() {
        return getServers();
    }

    private List<NacosServer> getServers() {
        String group = discoveryProperties.getGroup();
        //调用nacos-sdk 查询实例列表
        List<Instance> instances = discoveryProperties.namingServiceInstance()
                .selectInstances(serviceId, group, true);
        // 类型转换
        return instancesToServerList(instances);

    }
}

Обновить ServerListUpdater

  • После инициализации ServerList операция обновления проходитPollingServerListUpdater
public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        // 更新任务 交给updateAction 具体实现
        final Runnable wrapperRunnable = () -> {
            updateAction.doUpdate();
            lastUpdated = System.currentTimeMillis();
        };

        // 开启后台线程定时执行  updateAction
        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                wrapperRunnable,
                initialDelayMs,
                refreshIntervalMs,
                TimeUnit.MILLISECONDS
        );
    }
}
  • реализация updateAction
public void doUpdate() {
	DynamicServerListLoadBalancer.this.updateListOfServers();
}
public class PollingServerListUpdater implements ServerListUpdater {
    public void updateListOfServers() {
        List<T> servers = new ArrayList();
        
        // 调用NacosServiceList 获取全部服务列表
        servers = this.serverListImpl.getUpdatedListOfServers();
        
        // 如果配置实例过滤器在执行过滤
        if (this.filter != null) {
            servers = this.filter.getFilteredListOfServers((List)servers);
        }
        
        // 更新LoadBalancer 服务列表
        this.updateAllServerList((List)servers);
    }
}

Расширение: обслуживание состояния сервера

  • Запускается при первоначальном создании LoadBalancersetupPingTask()
public BaseLoadBalancer() {
  this.name = DEFAULT_NAME;
  this.ping = null;
  setRule(DEFAULT_RULE);
  // 开启ping 检查任务
  setupPingTask();
  lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
  • setupPingTask
void setupPingTask() {
  // 是否可以ping, 默认的DummyPing 直接 跳过不执行
  if (canSkipPing()) {
    return;
  }
  // 执行PingTask
  lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);
  // 开启任务
  new BaseLoadBalancer.Pinger(pingStrategy).runPinger();
}
  • SerialPingStrategy Логика последовательного выполнения
// 串行调度执行 Iping 逻辑
private static class SerialPingStrategy implements IPingStrategy {
  @Override
  public boolean[] pingServers(IPing ping, Server[] servers) {
    int numCandidates = servers.length;
    boolean[] results = new boolean[numCandidates];

    for (int i = 0; i < numCandidates; i++) {
      results[i] = false; /* Default answer is DEAD. */
      if (ping != null) {
        results[i] = ping.isAlive(servers[i]);
      }

    }
    return results;
  }
}
  • URL-адрес для вызова, чтобы судить о доступности
public class PingUrl implements IPing {
    public boolean isAlive(Server server) {
        urlStr = urlStr + server.getId();
        urlStr = urlStr + this.getPingAppendString();
        boolean isAlive = false;
        HttpClient httpClient = new DefaultHttpClient();
        HttpUriRequest getRequest = new HttpGet(urlStr);
        String content = null;

        HttpResponse response = httpClient.execute(getRequest);
        content = EntityUtils.toString(response.getEntity());
        isAlive = response.getStatusLine().getStatusCode() == 200;
        return isAlive;
    }
}

Расширение: отложенная загрузка RibbonClient

Как видно из вышесказанного, по умолчанию Лента будет создаваться по первому запросу.LoadBalancer, этот механизм ленивой загрузки вызовет первый вызов после запуска службызадержка обслуживанияПроблемы, даже таймаут предохранителей в автоматических выключателях (hystrix) и т.д.

Чтобы решить эту проблему, мы настроим голодающую загрузку ленты.

ribbon:
  eager-load:
    clients:
      - provider
  • RibbonApplicationContextInitializerФабрика автоматически вызывается после запуска службы для предварительного создания необходимых ленточных клиентов.
public class RibbonApplicationContextInitializer
        implements ApplicationListener<ApplicationReadyEvent> {
    private final List<String> clientNames;

    protected void initialize() {
        if (clientNames != null) {
            for (String clientName : clientNames) {
                this.springClientFactory.getContext(clientName);
            }
        }
    }
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        initialize();
    }

}

план дальнейших действий

Добро пожаловать, чтобы следовать за мной, обновить позжеRibbon,Hystrix,Sentinel,Nacosи другие компоненты графического анализа исходного кода.

Еще одно примечание: Вышеуказанные фотоматериалы (омниграфле и миллиард картинок) можно найти в публичном аккаунте.JAVA架构日记Получать

"★★★★★" Система управления разрешениями RBAC на основе Spring Boot 2.2, Spring Cloud Hoxton & Alibaba, OAuth2