Диаграмма + исходный код для объяснения механизма синхронизации реестра кластера Eureka Server

Привыкайте писать вместе! Это 9-й день моего участия в «Новом ежедневном плане Nuggets · Апрельское задание по обновлению», нажмите, чтобы просмотреть подробности мероприятия.

Логика отбраковки службы Eureka Server

Настойчивость в достижении важнее упорства в неудачах - Ларошвко

Связанные статьи
Диаграмма + объяснение исходного кода Диаграмма анализа процесса запуска Eureka Server
+ объяснение исходного кода Диаграмма анализа процесса запуска Eureka Client
+ объяснение исходного кода Логическая схема кэша реестра Eureka Server
+ объяснение исходного кода Диаграмма процесса извлечения реестра Eureka Client
+ объяснение исходного кода Служба Eureka Client Процесс регистрации
Диаграмма + исходный код для пояснения механизма пульсации Eureka Client
Диаграмма + исходный код для пояснения анализа автономного процесса Eureka Client
Диаграмма + исходный код для пояснения логики устранения сервиса Eureka Server

Основная схема

    Взаимодействие между сервисными узлами сервера eureka, так что независимо от того, какой сервисный узел не работает, доступ к другим сервисным узлам также может получить доступ к узлу, к которому нужно получить доступ.Например, на рисунке клиент регистрируется в сервисе A. Когда регистрация прошла успешно, текущий узел будет синхронизирован с другими сервисными узлами, что является механизмом синхронизации кластера
изображение.png

С чего начать анализ

    Поскольку регистрация используется в качестве примера, давайте посмотрим, как процесс работы после регистрации синхронизирует информацию о кластере.Сначала рассмотрим код регистрации.

public void register(final InstanceInfo info, final boolean isReplication) {
    // 90
    int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    /**
     * 真正的注册是在父类的方法中
     */
    super.register(info, leaseDuration, isReplication);
    /**
     * 同步当前实例信息到其他的eureka-server节点中
     */
    replicateToPeers(Action.Register, info.getAppName(), 
                     info.getId(), info, null, isReplication);
}
复制代码

    Метод регистрации был проанализирован в статье о регистрации клиента, в основном мы рассматриваем, как синхронизировать последующую информацию об узле.

replicateToPeers(Action.Register, info.getAppName(), info.getId(), 
                 info, null, isReplication);
复制代码

основной процесс

Блок-схема ядра кода

изображение.png
    Для синхронизации регистрация нашего экземпляра, автономный режим, сердцебиение, обновление статуса и т. д. будут синхронизированы с другими сервисными узлами.

public enum Action {
    // 心跳、注册、下线、状态更新
    Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride;
}
复制代码

    Просто значения, передаваемые при вызове метода replicateToPeers, отличаются.Давайте сначала посмотрим на процесс регистрации.

Вы сами инициировали синхронизацию регистрации или ее инициировал кто-то другой?

    如果是别人发起的同步那么就放入到自己的注册表中就好了,如果不是的话那么就是自己发起的同步,这样一来就是将自己过滤除去同步给其他人,isReplication 这个值就是控制它的

private void replicateToPeers(Action action, String appName, String id,
                      InstanceInfo info /* optional */,
                      InstanceStatus newStatus /* optional */,
                      boolean isReplication) {
    /**
     * isReplication 如果当前实例有eureka-server已经同步过的话那么
     * 就不用其他eureka-server在进行同步了
     * 或者服务节点为null的话也不需要同步了
     */
    if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
        return;
    }

for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
    /**
     * 同步的时候排除自己,只需要同步给其他eureka-server即可
     */
    if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
        continue;
    }
    /**
     * 如果是某台eureka client来找eureka server进行注册,此时会给其他所有的你配置的
     * eureka server都同步这个注册请求,
     * 此时一定会基于jersey,调用其他所有的eureka server的restful接口,
     * 去执行这个服务实例的注册的请求
     */
    replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
    }
}
复制代码

遍历所有节点去进行注册当前实例

    其实就是在遍历peerEurekaNodes.getPeerEurekaNodes() 里面的值,这里面的值是在服务端初始化的时候获取的其他服务中心节点的值,所以就知道里面有多少服务中心了,将要注册的客户端注册给其他服务中心

replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node)
    
private void replicateInstanceActionsToPeers(Action action, 
       String appName, String id, InstanceInfo info, 
              InstanceStatus newStatus, PeerEurekaNode node) {
    InstanceInfo infoFromRegistry;
    CurrentRequestVersion.set(Version.V2);
    switch (action) {
        case Cancel:// 实例下线
            node.cancel(appName, id);
            break;
        case Heartbeat: // 发送心跳
            InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
            infoFromRegistry = getInstanceByAppAndId(appName, id, false);
            node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
            break;
        case Register:// 注册
            node.register(info);
            break;
        case StatusUpdate: // 状态更新
            infoFromRegistry = getInstanceByAppAndId(appName, id, false);
            node.statusUpdate(appName, id, newStatus, infoFromRegistry);
            break;
        case DeleteStatusOverride:
            infoFromRegistry = getInstanceByAppAndId(appName, id, false);
            node.deleteStatusOverride(appName, id, infoFromRegistry);
            break;
    }
}
复制代码

    上面的switch 操作分别对应了下线、发送心跳、注册、状态更新等操作,我们这里是注册所以看一下node.register(info) 方法

节点注册

    客户端注册之前我们都讲过了,就是将当前的服务节点通过jersey框架进行访问之后在走一遍注册逻辑,我们看看同步的时候是怎么操作的是不是我们想的样子

public void register(final InstanceInfo info) throws Exception {
    // 到期时间 默认是当前的系统时间 + 90s
    long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
    batchingDispatcher.process(
            // 创建了一个任务ID
            taskId("register", info),
            // 创建了一个实例复制任务
            new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
                public EurekaHttpResponse<Void> execute() {
                    return replicationClient.register(info);
                }
            },
        // 到期时间
            expiryTime
    );
}
复制代码

    我们看到这里用了一个 batchingDispatcher 批量调度器,这个调度器是在创建节点的时候尽心初始化的调度器

this.batchingDispatcher = TaskDispatchers.createBatchingTaskDispatcher(
            // 调度器名字
            batcherName,
            // 默认是 10000 个
            config.getMaxElementsInPeerReplicationPool(),
            // 批量的大小
            batchSize,
            // 最大实例同步个数默认20个
            config.getMaxThreadsForPeerReplication(),
            // 时间
            maxBatchingDelayMs,
            serverUnavailableSleepTimeMs,
            retrySleepTimeMs,
            // 任务执行器
            taskProcessor
);
复制代码

    这样一来就是通过这个批量调度器在一定的时间内也就是请求到来的时间+90s内进行同步请求注册就可以了,通过这个 replicationClient 进行注册请求,后续的逻辑就是走的上面的注册逻辑,唯一的区别就是同步的时候是放到了调度其中去执行的,不是直接去请求访问注册的

批量调度器的创建以及运行流程

// 任务调度器
public static <ID, T> TaskDispatcher<ID, T> createBatchingTaskDispatcher(String id,
                                                 int maxBufferSize,
                                                 int workloadSize,
                                                 int workerCount,
                                                 long maxBatchingDelay,
                                                 long congestionRetryDelayMs,
                                                 long networkFailureRetryMs,
                                                 TaskProcessor<T> taskProcessor) {
    // 任务接收执行器
    final AcceptorExecutor<ID, T> acceptorExecutor = new AcceptorExecutor<>(
            id, maxBufferSize, workloadSize, maxBatchingDelay, 
        congestionRetryDelayMs, networkFailureRetryMs
    );
    // 任务执行器
    final TaskExecutors<ID, T> taskExecutor = 
        TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor);
    return new TaskDispatcher<ID, T>() {
        @Override
        public void process(ID id, T task, long expiryTime) {
            acceptorExecutor.process(id, task, expiryTime);
        }

        @Override
        public void shutdown() {
            acceptorExecutor.shutdown();
            taskExecutor.shutdown();
        }
    };
}
复制代码

任务接收器创建

AcceptorExecutor(String id,
                 int maxBufferSize,
                 int maxBatchingSize,
                 long maxBatchingDelay,
                 long congestionRetryDelayMs,
                 long networkFailureRetryMs) {
    this.id = id;
    this.maxBufferSize = maxBufferSize;
    this.maxBatchingSize = maxBatchingSize;
    this.maxBatchingDelay = maxBatchingDelay;
    ThreadGroup threadGroup = new ThreadGroup("eurekaTaskExecutors");
    this.acceptorThread = new Thread(threadGroup, new AcceptorRunner(),
                                     "TaskAcceptor-" + id);
    this.acceptorThread.setDaemon(true);
    this.acceptorThread.start();
}
复制代码

    核心代码是 new AcceptorRunner() 这个代码,接收运行任务代码,进行任务队列处理,就是接收队列进入处理队列的逻辑,之后通过定义的任务执行器进行执行

任务执行器创建

    上面将要执行的请求任务进行了处理操作得到了一些需要处理的批量任务

static class BatchWorkerRunnable<ID, T> extends WorkerRunnable<ID, T> {
// 线程运行的主要逻辑代码是这块了
@Override
public void run() {
    while (!isShutdown.get()) {
        List<TaskHolder<ID, T>> holders = getWork();
        metrics.registerExpiryTimes(holders);

        List<T> tasks = getTasksOf(holders);
        // 任务执行器真正处理代码的地方
        ProcessingResult result = processor.process(tasks);
    }
}
复制代码

任务执行器执行任务

    Метод processing.process(tasks) вызывает метод процесса в ReplicationTaskProcessor для обработки запроса, отправляет задачи операции пакетного обновления через replicationClient, а также запрашивает метод submitBatchUpdates через инфраструктуру трикотажа, чтобы найти запрос одноранговой репликации/пакета/пути.

public ProcessingResult process(List<ReplicationTask> tasks) {
  // 进行任务的细节处理
    ReplicationList list = createReplicationListOf(tasks);
    EurekaHttpResponse<ReplicationListResponse> response = 
        replicationClient.submitBatchUpdates(list);
}
复制代码

Класс запроса обработки ядра пакетной задачи

    В проекте eureka-core есть PeerReplicationResource, путь запроса в этом классе имеет peerreplication, и в пути есть метод с идентификатором батча, поэтому он здесь.

@Path("/{version}/peerreplication")
@Produces({"application/xml", "application/json"})
public class PeerReplicationResource {
@Path("batch")
@POST
public Response batchReplication(ReplicationList replicationList) {
    ReplicationListResponse batchResponse = new ReplicationListResponse();
    for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {
            batchResponse.addResponse(dispatch(instanceInfo));
    }
    return Response.ok(batchResponse).build();
}
// 真正的处理方法
private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {
    ApplicationResource applicationResource = createApplicationResource(instanceInfo);
    InstanceResource resource = createInstanceResource(instanceInfo, 
                                                       applicationResource);
    String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());
    String overriddenStatus = toString(instanceInfo.getOverriddenStatus());
    String instanceStatus = toString(instanceInfo.getStatus());
    
    Builder singleResponseBuilder = new Builder();
    // 根据动作信息进行处理不同的动作
    switch (instanceInfo.getAction()) {
        case Register: // 注册
            singleResponseBuilder = handleRegister(instanceInfo, applicationResource);
            break;
        case Heartbeat:// 心跳
            singleResponseBuilder = handleHeartbeat(serverConfig, resource,
                      lastDirtyTimestamp, overriddenStatus, instanceStatus);
            break;
        case Cancel: // 下线
            singleResponseBuilder = handleCancel(resource);
            break;
        case StatusUpdate: // 状态更新
            singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);
            break;
        case DeleteStatusOverride:
            singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);
            break;
    }
    return singleResponseBuilder.build();
}
复制代码

Обработка запросов сердцебиения

    handleRegister (instanceInfo, applicationResource), этот метод предназначен для обработки запросов на регистрацию.

private static Builder handleRegister(ReplicationInstance instanceInfo,
                                      ApplicationResource applicationResource) {
    applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);
    return new Builder().setStatusCode(Status.OK.getStatusCode());
}
复制代码

    Ниже показан код запроса на регистрацию экземпляра, который мы видели ранее, но в настоящее время значение REPLICATION равно true, поэтому текущий сервисный узел — это просто запрос на регистрацию, и он не будет зарегистрирован на других узлах.

public Response addInstance(InstanceInfo info, 
           @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
    /**
     *  "true".equals(isReplication) 注意这个参数
     */
    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();  // 204 to be backwards compatible
}
复制代码

резюме

  1. Клиент инициирует регистрацию на сервере
  2. Следует ли синхронизировать в зависимости от того, является ли регистрация инициирована самой собой
  3. Создайте планировщик задач для пакетного приема и планирования задач
  4. Пакетная обработка запросов ядра задачи
  5. Обработка запросов сердцебиения

Основные моменты

    Трехуровневая очередь выполняет операции обработки задач, очереди приема, очереди обработки, очереди задач пакетного выполнения и выполнение синхронных операций.

рекомендация

отjuejin.im/post/7084364130508865572
рекомендация