Diagrama + código fuente para explicar el mecanismo de sincronización del registro del clúster del servidor Eureka

¡Acostúmbrate a escribir juntos! Este es el noveno día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento

Lógica de selección del servicio del servidor Eureka

La perseverancia en el logro es más importante que la tenacidad en el fracaso - Laroshvko

Artículos relacionados
Diagrama + explicación del código fuente Diagrama de análisis del proceso de inicio del servidor Eureka + explicación del código fuente Diagrama
de análisis del proceso de inicio del cliente Eureka + explicación del código fuente Diagrama
lógico de la memoria caché del registro del servidor Eureka + explicación del código fuente Diagrama
de flujo del registro de extracción del cliente Eureka
+ explicación del código fuente Servicio del cliente Eureka proceso de registro
Diagrama + código fuente para explicar el
diagrama de flujo del mecanismo de latido del cliente Eureka + código fuente para explicar el análisis del proceso fuera de línea del cliente Eureka
Diagrama + código fuente para explicar la lógica de eliminación del servicio del servidor Eureka

Esquema básico

    Intercomunicación entre los nodos de servicio del servidor eureka, de modo que no importa qué nodo de servicio esté caído, al acceder a otros nodos de servicio también se puede acceder al nodo al que se quiere acceder. Por ejemplo, en la figura, el cliente se registra en el servicio A. Cuando el registro es exitoso, el nodo actual se sincronizará con otros nodos de servicio, que es el mecanismo de sincronización del clúster
imagen.png

Dónde comenzar su análisis

    Dado que el registro se usa como ejemplo, echemos un vistazo a cómo el proceso de operación después del registro sincroniza la información del clúster.Veamos primero el código de registro.

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);
}
复制代码

    El método de registro ha sido analizado en el artículo sobre el registro de clientes, principalmente nos fijamos en cómo sincronizar la información del nodo posterior.

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

proceso central

Diagrama de flujo del núcleo del código

imagen.png
    Para la sincronización, nuestro registro de instancia, fuera de línea, latido, actualización de estado, etc. se sincronizarán con otros nodos de servicio.

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

    Es solo que los valores que se pasan cuando se llama al método replicateToPeers son diferentes. Veamos primero el proceso de registro.

¿Iniciaste tú mismo la sincronización del registro o alguien más la inició?

    如果是别人发起的同步那么就放入到自己的注册表中就好了,如果不是的话那么就是自己发起的同步,这样一来就是将自己过滤除去同步给其他人,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);
    }
}
复制代码

任务执行器执行任务

    El método processor.process(tasks) llama al método de proceso en ReplicationTaskProcessor para procesar la solicitud, envía tareas de operación de actualización por lotes a través de replicationClient y también solicita el método submitBatchUpdates a través del marco jersey para encontrar la solicitud de peerreplication/batch/path

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

Clase de solicitud de procesamiento principal de tarea por lotes

    Hay un PeerReplicationResource en el proyecto de eureka-core. La ruta de solicitud en esta clase tiene replicación entre pares, y hay un método con un identificador de lote en la ruta, así que está aquí.

@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();
}
复制代码

Manejo de solicitudes de latidos

    handleRegister (instanceInfo, applicationResource), este método es el método para manejar las solicitudes de registro

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

    Mirando hacia abajo está el código de la solicitud de registro de instancia que hemos visto antes, pero en este momento, el valor de REPLICACIÓN es verdadero, por lo que el nodo de servicio actual es solo una solicitud de registro y no se registrará con otros nodos.

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
}
复制代码

resumen

  1. El cliente inicia el registro con el servidor
  2. Si sincronizar según sea un registro iniciado por él mismo
  3. Cree un programador de tareas para la recepción por lotes y la programación de tareas
  4. Solicitudes principales de tareas de procesamiento por lotes
  5. Manejo de solicitudes de latidos

Reflejos

    La cola de tres niveles realiza operaciones de procesamiento de tareas, colas de recepción, colas de procesamiento, colas de tareas de ejecución por lotes y ejecución de operaciones sincrónicas.

Supongo que te gusta

Origin juejin.im/post/7084364130508865572
Recomendado
Clasificación