Análise da sincronização de dados do registro SOFA

Este artigo analisa principalmente o módulo de sincronização de dados do SOFARegistry e não elabora muito sobre o conceito de registro e a infraestrutura do SOFARegistry. Para introdução relacionada, consulte o artigo " Registry Center under Massive Data - Introduction to SOFARegistry Architecture ".

As principais ideias de redação deste artigo estão divididas basicamente nas duas partes a seguir: a primeira parte usa a classificação de funções no SOFARegistry para explicar quais funções realizarão a sincronização de dados e a segunda parte analisa a implementação específica da sincronização de dados.

Classificação da função SOFARegistry

imagem.png
Conforme mostrado acima, SOFARegistry contém 4 funções:

Função ilustrar
Cliente Fornece o recurso de API básico para aplicativos acessarem o registro de serviço.O sistema de aplicativo chama os recursos de assinatura de serviço e publicação de serviço do registro de serviço de forma programática, contando com o pacote JAR do cliente.
SessionServer O servidor de sessão é responsável por aceitar as solicitações de publicação e assinatura de serviço do cliente e, como camada intermediária, encaminha as operações de gravação para a camada DataServer. A camada SessionServer pode ser expandida com o crescimento do número de máquinas de negócios.
Servidor de dados O servidor de dados é responsável por armazenar dados de serviço específicos. Os dados são armazenados em fragmentos de hash consistentes de acordo com dataInfoId e suporta backup de várias cópias para garantir alta disponibilidade de dados. Essa camada pode ser expandida à medida que a escala do volume de dados do serviço cresce.
MetaServer O servidor de metadados é responsável por manter uma lista consistente de SessionServer e DataServer no cluster.Como um serviço de descoberta de endereços dentro do cluster SOFARegistry, ele pode notificar todo o cluster quando o nó SessionServer ou DataServer for alterado.

Entre essas quatro funções, o MetaServer, como o próprio servidor de metadados, não trata dados reais de negócios, mas é responsável apenas por manter uma lista consistente de SessionServer e DataServer no cluster e não envolve sincronização de dados; as ações principais entre Cliente e SessionServer são assinatura e publicação.Em sentido amplo, a sincronização de dados entre o cliente do lado do usuário e o cluster SOFARegistry pode ser encontrada em: github.com/sofastack/s… , portanto, não está dentro do escopo deste artigo.

Como um serviço de sessão, o SessionServer resolve principalmente o problema de conexões massivas de clientes, seguido pelo cache de todos os dados pub publicados pelos clientes; a própria sessão não persiste os dados do serviço, mas transfere os dados para o DataServer. Os dados do serviço de armazenamento DataServer são armazenados em fragmentos de hash consistentes de acordo com dataInfoId, suporta backups de várias cópias e garante alta disponibilidade de dados.

Da análise funcional do SessionServer e DataServer, pode-se concluir que:

  • Os dados de serviço armazenados em cache pelo SessionServer precisam ser consistentes com os dados de serviço armazenados pelo DataServer

imagem.png

  • O DataServer suporta várias cópias para garantir alta disponibilidade, portanto, o servidor de dados precisa manter os dados de serviço consistentes entre várias cópias do DataServer.

imagem.png

As duas garantias de consistência de dados acima no SOFARegistry são obtidas por meio do mecanismo de sincronização de dados.

A realização concreta da sincronização de dados

O seguinte apresenta principalmente os detalhes de implementação da sincronização de dados, incluindo principalmente a sincronização de dados entre SessionServer e DataServer e sincronização de dados entre várias cópias do DataServer.

Sincronização de dados entre SessionServer e DataServer

A sincronização de dados entre SessionServer e DataServer é baseada no mecanismo de combinação push-pull

  • Push: Quando os dados forem alterados, o DataServer notificará ativamente o SessionServer, e o SessionServer verifica e confirma que precisa ser atualizado (comparado com a versão) e, em seguida, obtém ativamente os dados do DataServer.
  • 拉:除了上述的 DataServer 主动推以外,SessionServer 每隔一定的时间间隔,会主动向 DataServer 查询所有 dataInfoId 的 version 信息,然后再与 SessionServer 内存的 version 作比较,若发现 version 有变化,则主动向 DataServer 获取数据。这个“拉”的逻辑,主要是对“推”的一个补充,若在“推”的过程有错漏的情况可以在这个时候及时弥补。

关于推和拉两种模式检查的 version 有一些差异,可以详见下面 推模式下的数据同步 和 **拉模式下的数据同步 **中的具体介绍

推模式下的数据同步流程

推模式是通过 SyncingWatchDog 这个守护线程不断 loop 执行来实现数据变更检查和通知发起的。

// 这里遍历所有的 slot
for (SlotState slotState : slotTableStates.slotStates.values()) {
    try {
        sync(slotState, syncSessionIntervalMs, syncLeaderIntervalMs, slotTableEpoch);
    } catch (Throwable e) {
        SYNC_ERROR_LOGGER.error(
                "[syncCommit]failed to do sync slot {}, migrated={}",
                slotState.slot,
                slotState.migrated,
                e);
    }
}

按 slot 分组汇总数据版本。data 与每个 session 的连接都对应一个 SyncSessionTask,SyncSessionTask 负责执行同步数据的任务,核心同步逻辑在 com.alipay.sofa.registry.server.data.slot.SlotDiffSyncer#sync方法中完成,大致流程如下面时序图所示:
imagem.png
这上图圈红部分的逻辑第四步,根据 dataInfoId diff 更新 data 内存数据,这里仅处理了被移除的 dataInfoId,对于新增和更新的没有做任务处理,而是通过后面的第 5 -7 步来完成;这么做的主要原因在于避免产生空推送导致一些危险情况发生。

第 5 步中,比较的是所有变更 dataInfoId 的 pub version,具体比较逻辑参考后面 diffPublisher 小节中的介绍。

数据变更的事件通知处理

数据变更事件会被收集在 DataChangeEventCenter 的 dataCenter2Changes 缓存中,然后由一个守护线程 ChangeMerger 负责从 dataCenter2Changes 缓存中不断的读取,这些被捞到的事件源会被组装成 ChangeNotifier 任务,提交给一个单独的线程池(notifyExecutor)处理,整个过程全部是异步的。

拉模式下的数据同步流程

拉模式下,由 SessionServer 负责发起,com.alipay.sofa.registry.server.session.registry.SessionRegistry.VersionWatchDog默认情况下每 5 秒扫描一次版本数据,如果版本有发生变更,则主动进行拉取一次,流程大致如下:
imagem.png

需要注意的是,拉模式对推送流程的补充,这里的 version 是每个 sub 的 lastPushedVersion, 而 推模式的version 是 pub 的数据的 version。关于 lastPushedVersion 的获取可以参考 com.alipay.sofa.registry.server.session.store.SessionInterests#selectSubscribers

store.forEach((String dataInfoId, Map<String, Subscriber> subs) -> {
   // ...
  long maxVersion = 0;
  for (Subscriber sub : subs.values()) {
    // ...
    // 获取当前 sub 的 pushVersion
    final long pushVersion = sub.getPushedVersion(dataCenter);
    // 如果 pushVersion 比最大(最新)版本大,则将当前  pushVersion 作为最新版本推送版本
    if (maxVersion < pushVersion) {
      maxVersion = pushVersion;
    }
  }
  versions.put(dataInfoId, new DatumVersion(maxVersion));
});

DataServer 多副本之间的数据同步

主要是 slot对应的 data 的 follower 定期和 leader 进行数据同步,其同步逻辑与 SessionServer 和 DataServer 之间的数据同步逻辑差异不大;发起方式也是一样的;data 判断如果当前节点不是 leader,就会进行与 leader 之间的数据同步。

if (localIsLeader(slot)) {
   // 如果当前是 leader,则执行 session 同步或者 migrating
} else {
    // 如果当前不是 leader,则和 leader 同步数据
    syncLeader(slotState, syncLeaderIntervalMs, slotTableEpoch);
}

篇幅原因,这部分不展开讨论。

增量同步 diff 计算逻辑分析

不管是 SessionServer 和 DataServer 之间的同步,还是 DataServer 多副本之间的同步,都是基于增量 diff 同步的,不会一次性同步全量数据。本节对增量同步 diff 计算逻辑进行简单分析,核心代码在 com.alipay.sofa.registry.common.model.slot.DataSlotDiffUtils(建议阅读这部分代码时直接结合代码中的测试用例来看);主要包括计算 digest 和 publishers 两个。

diffDigest

DataSlotDiffUtils#diffDigest 方法接收两个参数

  • targetDigestMap 可以理解为目标数据
  • sourceDigestMap 可以理解为基线数据

核心计算逻辑如下代码分析

// 遍历 sourceDigestMap 元素
for (Map.Entry<String, DatumDigest> e : sourceDigestMap.entrySet()) {
  // dataInfoId
  final String dataInfoId = e.getKey();
  // 从 目标数据 集中根据 dataInfoId 获取数据摘要
  DatumDigest targetDigest = targetDigestMap.get(dataInfoId);
  // 如果目标数据集中没有当前 dataInfoId 对应的数据摘要,
  // 则将当前 dataInfoId 作为新增项
  if (targetDigest == null) {
    adds.add(dataInfoId);
    continue;
  }
  // 如果目标数据集中有当前 dataInfoId 对应的数据摘要,
  // 但是数据摘要不同,则将当前 dataInfoId 作为待更新项
  if (!targetDigest.equals(e.getValue())) {
    updates.add(dataInfoId);
  }
}


// 如果目标数据集中的 dataInfoId 不再基线数据集中时,
// 则将当前 dataInfoId 作为待移除项。
List<String> removes = new ArrayList<>();
for (String dataInfoId : targetDigestMap.keySet()) {
  if (!sourceDigestMap.containsKey(dataInfoId)) {
    removes.add(dataInfoId);
  }
}

那么根据上述 diff 计算逻辑,这里有如下几种场景(假设基线数据集数据中 dataInfoId 为 a 和 b)

  • 目标数据集为空:返回 dataInfoId 为 a 和 b 两项作为新增项
  • 目标数据集与基线数据集相等,新增项、待更新项与待移除项均为空
  • 目标数据集中包括 a,b,c 三个 dataInfoId,则返回 c 作为待移除项
  • 目标数据集中包括 a 和 c 两个 dataInfoId,则返回 c 作为待移除项,b 作为新增项

diffPublisher

diffPublisher 与 �diffDigest 计算稍有不同,diffPublisher 接收三个参数,除了目标数据集和基线数据集之外,还有一个 publisherMaxNum(默认 400),用于限制每次处理的数据个数;这里同样给出核心代码解释:

// 遍历所有目标数据集
for (DatumSummary summary : targetDatumSummaries) {
      // 拿到 dataInfoId
      final String dataInfoId = summary.getDataInfoId();
      // 看基线数据集中是否包括当前 dataInfoId 对应的 Publisher 数据
      Map<String, Publisher> publisherMap = sourcePublishers.get(dataInfoId);
      // 这里表示 dataInfoId 移除被移除了,不需要做任何处理
      if (publisherMap == null) { continue; }
      
      Set<String> registerIds = summary.getPublisherVersions().keySet();
      // 遍历 registerIds
      for (String registerId : registerIds) {
        // 如果基线数据集中不包括此 registerId,则将当前 registerId 加入待移除列表中
        if (!publisherMap.containsKey(registerId)) {
          List<String> list = removedPublishers.computeIfAbsent(dataInfoId, k -> new ArrayList<>());
          list.add(registerId);
        }
      }
      List<Publisher> publishers = new ArrayList<>();
      Map<String, RegisterVersion> versions = summary.getPublisherVersions();
      // 遍历版本
      for (Map.Entry<String, Publisher> p : publisherMap.entrySet()) {
        final String registerId = p.getKey();
        // 如果目标数据集当前 dataInfoId 的 registerIds 集中不包括基线的
        // 则作为更新项
        if (!versions.containsKey(registerId)) {
          publishers.add(p.getValue());
          continue;
        }
        // 如果当前 registerId 版本相同,则不做处理
        if (p.getValue().registerVersion().equals(versions.get(registerId))) {
          // the same
          continue;
        }
        // 不相等,则作为更新项
        publishers.add(p.getValue());
      }
    }

这里同样分析几种场景(下面只的是更新 dataInfoId 对应的 publisher,registerId 与 publisher是 一一对应):

  • 目标数据集与基线数据集相同,且数据没有超过 publisherMaxNum,返回的待更新和待移除均为空,且没有剩余未处理数据
  • 需要移除的情况:基线中不包括目标数据集 dataInfoId 的 registerId (移除的是 registerId,不是 dataInfoId)
  • 需要更新的情况:
    • 目标数据集中存在基线数据集不存在的 registerId
    • 目标数据集和基线数据集存在的 registerId 的版本不同

总结

Este artigo apresenta principalmente o módulo de sincronização de dados no SOFARegistry; em primeiro lugar, o problema de sincronização de dados entre diferentes funções é explicado a partir da classificação de funções SOFARegistry, e são analisadas a sincronização de dados entre SessionServer e DataServer e sincronização de dados entre várias cópias do DataServer. análise de sincronização, o processo geral de sincronização de dados nos dois cenários de push e pull é analisado.Finalmente, é introduzida a lógica de cálculo de diferença de adição de dados no SOFARegistry e os cenários específicos são descritos em combinação com os códigos centrais relevantes.

No geral, vale a pena aprender alguns pontos sobre o processamento da sincronização de dados SOFARegistry:

  • SOFARegistry é baseado em AP, que satisfaz consistência eventual em consistência; no processamento da lógica de sincronização real, combinado com o mecanismo de eventos, é basicamente concluído de forma assíncrona, enfraquecendo assim o impacto da sincronização de dados no processo principal.
  • Nas duas partes do modo pull e notificação de alteração de dados, um modelo similar de produção-consumo é usado internamente. Por um lado, a dissociação da lógica de produção e consumo é mais independente do código; por outro lado, a velocidade de produção e o consumo é eliminado por meio de cache ou fila, problemas diferentes e de bloqueio mútuo.
  • O modo pull complementa o modo push; sabemos que o modo push é servidor -> cliente. Quando os dados mudam, se alguma anormalidade ocorrer, fazendo com que um link servidor -> cliente falhe ao enviar, isso levará a dados mantidos por diferentes clientes • Situação inconsistente, a adição do modo pull permite que o cliente complete ativamente a verificação de consistência dos dados.

Por fim, obrigado pela leitura. Se houver algum erro no texto, por favor, aponte-o; você também está convidado a prestar atenção à comunidade sofastack. Link original: mp.weixin.qq.com/s/UqsFzSuxu…

Acho que você gosta

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