Analyse der Synchronisierung der SOFA-Registrierungsdaten

Dieser Artikel analysiert hauptsächlich das Datensynchronisationsmodul von SOFARegistry und geht nicht zu sehr auf das Konzept der Registrierung und die Infrastruktur von SOFARegistry ein.Für eine entsprechende Einführung lesen Sie bitte den Artikel „ Registrierungszentrum unter Massive Data – Einführung in die Architektur von SOFARegistry “.

Die wichtigsten Schreibideen dieses Dokuments sind grob in die folgenden zwei Teile unterteilt: Der erste Teil verwendet die Rollenklassifizierung in SOFARegistry, um zu erklären, welche Rollen die Datensynchronisierung durchführen, und der zweite Teil analysiert die spezifische Implementierung der Datensynchronisierung.

SOFARegistry-Rollenklassifizierung

Bild.png
Wie oben gezeigt, enthält SOFARegistry 4 Rollen:

Rolle veranschaulichen
Klient Stellt die grundlegende API-Fähigkeit für Anwendungen bereit, um auf die Dienstregistrierung zuzugreifen.Das Anwendungssystem ruft die Dienstabonnement- und Dienstveröffentlichungsfunktionender Dienstregistrierung programmgesteuert auf, indem es sich auf das Client-JAR-Paketverlässt.
Sitzungsserver Der Sitzungsserver ist dafür verantwortlich, die Dienstveröffentlichungs- und Dienstabonnementanforderungen des Clients anzunehmen, und als Zwischenschicht leitet er Schreiboperationen an die DataServer-Schicht weiter. Die SessionServer-Schicht kann mit dem Wachstum der Anzahl von Geschäftsmaschinen erweitert werden.
Datenserver Der Datenserver ist für die Speicherung bestimmter Dienstdaten verantwortlich. Die Daten werden gemäß dataInfoId in konsistenten Hash-Shards gespeichert und unterstützen die Sicherung mehrerer Kopien, um eine hohe Datenverfügbarkeit zu gewährleisten. Diese Schicht kann erweitert werden, wenn die Größe des Servicedatenvolumens wächst.
MetaServer Der Metadatenserver ist für die Pflege einer konsistenten Liste von SessionServer und DataServer im Cluster verantwortlich.Als Adressermittlungsdienst innerhalb des SOFARegistry-Clusters kann er den gesamten Cluster benachrichtigen, wenn sich der SessionServer- oder DataServer-Knoten ändert.

Unter diesen vier Rollen verarbeitet MetaServer als Metadatenserver selbst keine eigentlichen Geschäftsdaten, sondern ist nur für die Pflege einer konsistenten Liste von SessionServer und DataServer im Cluster verantwortlich und beinhaltet keine Datensynchronisierung; die Kernaktionen zwischen Client und SessionServer stellen Abonnement und Veröffentlichung dar. Im weiteren Sinne ist die Datensynchronisierung zwischen dem benutzerseitigen Client und dem SOFARegistry-Cluster unter: github.com/sofastack/s… zu finden , daher ist dies nicht Gegenstand dieses Artikels.

Als Sitzungsdienst löst SessionServer hauptsächlich das Problem massiver Client-Verbindungen, gefolgt vom Zwischenspeichern aller Pub-Daten, die von Clients veröffentlicht werden; die Sitzung selbst speichert keine Dienstdaten, sondern überträgt Daten an DataServer. Die Daten des DataServer-Speicherdienstes werden gemäß dataInfoId in konsistenten Hash-Shards gespeichert, unterstützen Backups mit mehreren Kopien und gewährleisten eine hohe Datenverfügbarkeit.

Aus der Funktionsanalyse von SessionServer und DataServer lässt sich Folgendes schließen:

  • Die vom SessionServer zwischengespeicherten Dienstdaten müssen mit den vom DataServer gespeicherten Dienstdaten übereinstimmen

Bild.png

  • DataServer unterstützt mehrere Kopien, um eine hohe Verfügbarkeit sicherzustellen, sodass der Datenserver die Dienstdaten zwischen mehreren Kopien von DataServer konsistent halten muss.

Bild.png

Die beiden oben genannten Datenkonsistenzgarantien in SOFARegistry werden durch den Datensynchronisierungsmechanismus erreicht.

Die konkrete Umsetzung der Datensynchronisation

Im Folgenden werden hauptsächlich die Implementierungsdetails der Datensynchronisierung vorgestellt, die hauptsächlich die Datensynchronisierung zwischen SessionServer und DataServer und die Datensynchronisierung zwischen mehreren Kopien von DataServer umfassen.

Datensynchronisation zwischen SessionServer und DataServer

Die Datensynchronisation zwischen SessionServer und DataServer basiert auf dem Push-Pull-Kombinationsmechanismus

  • Push: Wenn sich die Daten ändern, benachrichtigt der DataServer aktiv den SessionServer, und der SessionServer prüft und bestätigt, dass er aktualisiert werden muss (im Vergleich zur Version) und bezieht dann aktiv die Daten vom 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方法中完成,大致流程如下面时序图所示:
Bild.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 秒扫描一次版本数据,如果版本有发生变更,则主动进行拉取一次,流程大致如下:
Bild.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 的版本不同

总结

Dieses Papier stellt hauptsächlich das Datensynchronisierungsmodul in SOFARegistry vor; zuerst wird das Datensynchronisierungsproblem zwischen verschiedenen Rollen anhand der SOFARegistry-Rollenklassifizierung erläutert, und die Datensynchronisierung zwischen SessionServer und DataServer und die Datensynchronisierung zwischen mehreren Kopien von DataServer werden analysiert; in den Daten Synchronisationsanalyse wird der Gesamtprozess der Datensynchronisation in den beiden Szenarien Push und Pull analysiert, schließlich wird die Diff-Berechnungslogik der Datenaddition in SOFARegistry vorgestellt und die spezifischen Szenarien in Kombination mit den relevanten Kerncodes beschrieben.

Insgesamt gibt es einige wissenswerte Punkte zur Verarbeitung der SOFARegistry-Datensynchronisierung:

  • SOFARegistry basiert auf AP, das Eventual Consistency in Consistency erfüllt; in der eigentlichen Synchronisierungslogik-Verarbeitung kombiniert mit dem Event-Mechanismus wird es grundsätzlich asynchron durchgeführt, wodurch die Auswirkungen der Datensynchronisierung auf den Kernprozess abgeschwächt werden.
  • In den beiden Teilen Pull-Modus und Data Change Notification wird intern ein ähnliches Produktions-Verbrauchs-Modell verwendet: Einerseits ist die Entkopplung von Produktions- und Verbrauchslogik unabhängiger vom Code, andererseits die Geschwindigkeit der Produktion und Verbrauch wird durch Cache oder Warteschlange eliminiert.. verschiedene und sich gegenseitig blockierende Probleme.
  • Der Pull-Modus ergänzt den Push-Modus; wir wissen, dass der Push-Modus Server -> Client ist. Wenn sich Daten ändern und eine Anomalie auftritt, die dazu führt, dass eine Server -> Client-Verbindung nicht gepusht werden kann, führt dies zu Daten, die von verschiedenen Clients gespeichert werden Inkonsistente Situation: Das Hinzufügen des Pull-Modus ermöglicht es dem Client, die Datenkonsistenzprüfung aktiv abzuschließen.

Abschließend vielen Dank fürs Lesen. Falls Fehler im Text sind, weisen Sie bitte darauf hin; Sie können sich auch gerne an die sofastack-Community wenden. Ursprünglicher Link: mp.weixin.qq.com/s/UqsFzSuxu…

Ich denke du magst

Origin juejin.im/post/7116820785217404941
Empfohlen
Rangfolge