SOFARegistry 源码|数据分片之核心-路由表 SlotTable 剖析

SOFARegistry注册中心的搭建(蚂蚁金服) 之前的文章中,详细的介绍了如何去搭建一个基于SOFABoot框架的项目,我们都知道,在用SpringBoot框架去开发一款分布式项目时,Spring Cloud为我们提供了一整套的针对多个服务之间的相互调用的问题,包括Eureka、Hystrix、Feign、Config等。在之前的文章中也介绍了,SOFABoot的主要功能中,除了介绍过的健康检查能力,类隔离和日志隔离之外,还有一个重要的,就是SOFAStack中一些SOFA中间件的集成管理。其实,SOFAStack同Spring Cloud一样,就... 阅读详情

be3abb2a2475ebe6bfa8ed561a2bbec3.gif

文|程征征(花名:泽睿 )

高德软件开发工程师

负责高德新场景业务探索开发与维护

对领域驱动、网络通讯、数据一致性有一定的研究与实践

本文 23009 字 阅读 25 分钟

“

第一次关注 SOFA 社区是在开发一个故障剔除组件时,发现 SOFARPC 中也有类似的组件。在 SOFARPC 的设计中,入口采用了一种无缝插入的设计方式,使得在不破坏开放封闭原则前提下,引入单机故障剔除能力。并且是基于内核设计和总线设计,做到可插拔、零侵入,整个故障剔除模块是通过 SPI 动态加载的。统计信息的收集也是通过事件驱动的方式,在 RPC 同步或异步调用完成后,会向事件总线 EventBus 发送对应事件。事件总线接收到对应的事件,以执行后续的故障剔除逻辑。

基于以上优秀的设计,我也将其纳为己用,也因此开启了在 SOFA 社区的开源探索之路。陆续研究了 SOFABoot、SOFARPC 以及 MOSN 等,自我感觉每一个项目的代码水平都很高,对我自己的代码提升有很大的帮助。

SOFARegistry 是一个开源的注册中心提供了服务的发布注册订阅等功能,支持海量的服务注册订阅请求。作为一个名源码爱好者,虽然看过 SOFA 的架构文章大致了解其中的设计哲学,但是因为没有从代码中了解过细节,实际上也是一知半解。恰好借助 SOFARegistry 开辟的源码分析活动,基于自己的兴趣选择了 SlotTable 这个任务。

PART. 0

贡献者前言

SOFARegistry 对于服务数据是分片进行存储的,因此每一个 data server 只会承担一部分的服务数据,具体哪份数据存储在哪个 data server 是有一个称为 SlotTable 的路由表提供的,session 可以通过 SlotTable 对对应的 data derver 进行读写服务数据, slot 对应的 data follower 可以通过 SlotTable 寻址 leader 进行数据同步。

维护 SlotTable 是由 Meta 的 leader 负责的,Meta 会维护 data 的列表,会利用这份列表以及 data 上报的监控数据创建 SlotTable,后续 data 的上下线会触发 Meta 修改 SlotTable, SlotTable 会通过心跳分发给集群中各个节点。

SlotTable 在 SOFARegistry 是非常核心的概念,简称路由表。简单来说 SOFARegistry 是需要将发布订阅数据存储在不同的机器节点上,才能保证数据存储的横向扩展。那么不同机器上到底存储哪些数据,就是 SlotTable 来保存的。

SlotTable 保存了 Slot 和机器节点之间的映射关系,数据通过 Hash 定位到某一个 Slot 上,通过 Slot 找到对应的机器 node 节点,将数据存储到对应的机器上。在这过程中有很多细节需要我们了解。比如说每一个 Slot 对应 leader 和 follow 节点是如何分配的。如果机器负载不平衡该如何平衡,SlotTable 的更新是如何进行的呢? 这些都是很有趣的细节实现。

PART. 1

DataServer 更新

SlotTable 路由表过程

ceffc3d88ac8cb1d91580a854b8424af.png

如上图所示 session 和 data 节点定时会向 Meta 节点上报心跳,Meta 节点维护了 data 以及 session 节点列表信息,并且在心跳请求中将返回 SlotTable 路由表信息,data 节点将路由表 SlotTable 保存在本地中。

PART. 2

SlotTable 更新平衡算法

由前文可知,SOFARegistry 采用了数据分片存储在 DataServer 节点之上,那么随之而来的问题就是:

数据如何分片呢 ?

SOFARegistry 采用预分配的方式

传统的一致性 Hash 算法有数据分布范围不固定的特性,该特性使得服务注册数据在服务器节点宕机、下线、扩容之后,需要重新存储排布,这为数据的同步带来了困难。

- 大多数的数据同步操作是利用操作日志记录的内容来进行的;

- 传统的一致性 Hash 算法中,数据的操作日志是以节点分片来划分的,节点变化导致数据分布范围的变化。

在计算机领域,大多数难题都可以通过增加一个中间层来解决,那么对于数据分布范围不固定所导致的数据同步难题,也可以通过同样的思路来解决。

这里的问题在于——当节点下线后,若再以当前存活节点 ID 一致性 Hash 值去同步数据,就会导致已失效节点的数据操作日志无法获取。

既然数据存储在会变化的地方无法进行数据同步,那么如果把数据存储在不会变化的地方,是否保证数据同步的可行性呢?

答案是肯定的,这个中间层就是预分片层,通过把数据与预分片这个不会变化的层相互对应,就能解决这个数据同步的难题。

目前业界主要代表项目如 Dynamo、Casandra、Tair、Codis、Redis cluster 等,都采用了预分片机制来实现这个不会变化的层。

事先将数据存储范围等分为 N 个 Slot 槽位,数据直接与 Slot 相对应,数据的操作日志与相应的 Solt 对应,Slot 的数目不会因为节点的上下线而产生变化,由此保证了数据同步的可行性。

除此之外,还需要引进“路由表”的概念 。

534e72a11f97d5c18444b1f8a0750593.png

“路由表”负责存放每个节点和 N 个 Slot 的映射关系,并保证尽量把所有 Slot 均匀地分配给每个节点。这样,当节点上下线时,只需要修改路由表内容即可。保持 Slot 不变,即保证了弹性扩缩容,也大大降低了数据同步的难度。

实际上,上述 Slot 和节点的映射关系在源码中以 SlotTable 和 Slot 的方式进行表达。

源码如下代码块所示:

public final class SlotTable implements Serializable {
  public static final SlotTable INIT = new SlotTable(-1, Collections.emptyList());
  // 最后一次更新的时间 epoch
  private final long epoch;
  //保存了 所有的 slot 信息;slotId ---> slot 对象的映射
  private final Map<Integer, Slot> slots;
}
public final class Slot implements Serializable, Cloneable {
  public enum Role {
    Leader,
    Follower,
  }
  private final int id;
  //当前slot的leader节点
  private final String leader;
  //最近更新时间
  private final long leaderEpoch;
  //当前slot的follow节点
  private final Set<String> followers;
}

由于节点在动态变化中,所以 Slot 和节点的映射也在时刻变化中,那么我们接下来的重点就是 SlotTable 的变更过程。

SlotTable 的变更是在 Meta 节点中触发、当有服务上下线的时候会触发 SlotTable 的变更、除此之外也会定期执执行 SlotTable 的变更。

SlotTable 的整个同步更新步骤,如图所示:

com.alipay.sofa.registry.server.Meta.slot.arrange.ScheduledSlotArranger#arrangeSync.

SlotTable 的定期变更是通过在初始化 ScheduledSlotArranger 时候,实例化守护线程不断的定期执行内部任务 Arranger 的 arrangeSync 方法来实现 SlotTable 变更的。

大致流程如下所示:

f2606bdb74ed0cbc93208d2428bd23a5.png

因为负责 SlotTable 的更新是在 MetaServer 中的主节点更新的。

更新 SlotTable 步骤如下:

 第一步|判断是否是主节点 

主节点才负责真正的 SlotTable 变更步骤。

 第二步|获取最新的 DataServer 节点 

重新分配 SlotTable 本质是对 DataServer 节点和 Slot 槽位之间的映射关系进行重新分配。所以肯定需要获取到当前正在存活的 DataServer 节点信息,从而方便的对之进行 Slot 分配。

f20049520858bcf0f3d3eef151d6473b.png

(这里获取正在存活的 DataServer 也就是有和 MetaServer 维持心跳的 DataServer,底层是从 com.alipay.sofa.registry.server.Meta.lease.impl.SimpleLeaseManager 中获取,感兴趣可以查看相关源码。) 

 第三步|分配前置校验 

实际上一些边界条件的判断,例如 DataServer 是否为空的,DataServer 的大小是否大于配置的 minDataNodeNum,只有满足这些条件才进行变更。

 第四步 |执行 trayArrageSlot 方法 

首先获取进程内部锁,实际上是一个 ReentrantLock,这里主要是为了避免定时任务多次同时执行 SlotTable 的分配工作。

private final Lock lock = new ReentrantLock();

随后便是根据当前的 Data 节点信息创建 SlotTableBuilder,这里的 SlotTableBuilder 又是何方神圣呢?

回到 SlotTable 更新的方式,一般是创建一个新的 SlotTable 对象,然后去代替老的 SlotTable 对象,从而完成变更 SlotTable 操作。一般不会直接对老的 SlotTable 直接进行增删该操作,这样并发导致的一致性问题很难控制。

所以基于 SlotTableBuilder 的名称就可以看出,它是 SlotTable 的创建者,内部聚合了 SlotBuilder 对象。其实和 SlotTable 类似的 SlotTable 内部聚合了 Slot 信息。

在查看 SlotTable 变更算法之前,我们先了解一下 SlotTableBuilder 的创建过程。

SlotBuilder 的结构如下所示:

public class SlotTableBuilder {
  //当前正在创建的 Slot 信息
  private final Map<Integer, SlotBuilder> buildingSlots = Maps.newHashMapWithExpectedSize(256);
  // 反向查询索引数据、通过 节点查询该节点目前负责哪些 slot 的数据的管理。
  private final Map<String, DataNodeSlot> reverseMap = Maps.newHashMap();
  //slot 槽的个数
  private final int slotNums;
  //follow 节点的数量
  private final int followerNums;
  //最近一次更新的时间
  private long epoch;
}

从 SlotTableBuilder 可以看出内部聚合了一个 buildingSlots,标识正在创建的 Slot。因为 SlotTable 是由 Slot 构成的,这点也很容易理解。除此之外 SlotTableBuilder 内部也聚合了一个 reverseMap,代表反向查询索引,这个映射的 key 是 dataServer,value是 DataNodeSlot 对象。

DataNodeSlot 源码如下:

/**
    通过 Slot 找 leader和follows.
    本质上是通过节点找 Slot,当前节点作为 leaders 的 Slot、和以当前节点作为 follower 的节点.
    也就是说当前我这个节点,我在那些 Slot 中作为 leader, 对应的是 Set<Integer> leaders.
    以及我当前这个节点在哪些 slot 中作为 follow,对应存储在 Set<Integer> follows.
**/
public final class DataNodeSlot  {
  private final String dataNode;
  private final Set<Integer> leaders = Sets.newTreeSet();
  private final Set<Integer> followers = Sets.newTreeSet();
}

54c5a737d09691c41f3b53877ddccfd0.png

用一张图来表达 DataNodeSlot 

它和图 1 是刚好相反的映射。通过节点查找与该节点有关联的 Slot 信息,因为后面要经常用到这一层查询,所以直接将这种关系保存下来。为了后面陈述方便,这里统计几种陈述方式。

- 节点被作为 leader 的 Slot 集合我们称为 : 节点 leader 的 Slot 集合

- 节点被作为 follow 的 Slot 集合我们称为 : 节点 follow 的 Slot 集合

- SlotTable 关联的所有节点统称为:SlotTable 的节点列表

再回到 SlotTableBuilder 创建:

private SlotTableBuilder createSlotTableBuilder(SlotTable slotTable, 
                                                List<String> currentDataNodeIps,
                                                int slotNum,int replicas) {
    //通过 NodeComparator 包装当前新增的、删除的的节点.
    NodeComparator comparator = new NodeComparator(slotTable.getDataServers(), currentDataNodeIps);
    SlotTableBuilder slotTableBuilder = new SlotTableBuilder(slotTable, slotNum, replicas);
    //执行 slotTableBuilder 的初始化
    slotTableBuilder.init(currentDataNodeIps);


    //在这里将已经下线的 data 节点删除掉、
    //其中已经删除的 是通过 NodeComparator 内部的getDiff方法 实现的。
    comparator.getRemoved().forEach(slotTableBuilder::removeDataServerSlots);
    return slotTableBuilder;
}

方法参数 SlotTable 是通过 SlotManager 对象获取到旧的 SlotTable 对象。currentDataNodeIps 代表当前存活的 dataServer (通过心跳维持和 MetaServer 的连接) 然后传入 createSlotTableBuilder 方法内部。createSlotTableBuilder 方法内部通过 NodeComparator 对象计算并且包装了旧的 “SlotTable 的节点列表” 与 传入的 currentDataNodeIps 之前的差异值。包括当前 CurrentDataNodeIps 中新增和删除的 DataServer。

随之调用 SlotTableBuilder 的 init 方法 ,执行 SlotTableBuilder 的初始化。

SlotTableBuilder 的 init 源码如下:

public void init(List<String> dataServers) {
    for (int slotId = 0; slotId < slotNums; slotId++) {
      Slot slot = initSlotTable == null ? null : initSlotTable.getSlot(slotId);
      if (slot == null) {
        getOrCreate(slotId);
        continue;
      }
     //1. 重新新建一个 SlotBuilder 将原来的 Slot 里面的数据拷贝过来
     //2. 拷贝 leader节点
      SlotBuilder slotBuilder =
          new SlotBuilder(slotId, followerNums, slot.getLeader(), slot.getLeaderEpoch());
     //3. 拷贝 follow 节点。
      slotBuilder.addFollower(initSlotTable.getSlot(slotId).getFollowers())
      buildingSlots.put(slotId, slotBuilder);
    }
     //4. 初始化反向查询索引数据、通过 节点查询该节点目前管理哪些 slot
    initReverseMap(dataServers);
  }

由上面的代码可以看出实际上 init 做了这么一件事情: 初始化 SlotBuilder 内部的 SlotBuilder 对象,并且将原来旧的 SlotTable 的 leader 和 follow 节点全部拷贝过去了。注意在实例化 SlotTableBuilder 的时候传入了旧的 SlotTable 也就是这里的 initSlotTable 对象。

init 方法最后一步的 initReverseMap 从名称可以看出构建了一个实例化反向路由表,反向查找表。从 Node 节点到 Slot 的查找功能,因为在之后的处理当中经常会用到某一个 data 节点负责了那些 Slot 的 leader 角色,以及哪些 Slot 的 follow 角色,所以这里做了一层索引处理。

再回到 ScheduledSlotArranger 类中 createSlotTableBuilder 方法最后一步,此时 SlotTableBulder 内部已经完成了旧的 SlotTable 的数据拷贝。

comparator.getRemoved().forEach(slotTableBuilder::removeDataServerSlots);

上文我们说过 comparator 对象内部保存了新的 dataServer 和旧的 “SlotTable 的节点列表” 比较信息。

所以在新的 dataServer 中已经删除的节点,我们需要从 SlotTableBuilder 中删除。内部的删除逻辑也是迭代所有的 SlotBuilder 比较 leader 和当前节点是否相同,相同则删除,follow 同理。

public void removeDataServerSlots(String dataServer) {
    for (SlotBuilder slotBuilder : buildingSlots.values()) {
      //删除该 SlotBuilder follow 节点中的 dataServer
      slotBuilder.removeFollower(dataServer)
      //如果该 SlotBuilder 的 leader 节点是 dataServer ,
      //那么设置该 SlotBuilder 的 leader 节点为空、需要重新进行分配
      if (dataServer.equals(slotBuilder.getLeader())) {
        slotBuilder.setLeader(null);
      }
    }
    reverseMap.remove(dataServer);
}

总结来说创建 SlotTableBuilder 的过程就是根据旧的 SlotTable 实例化 SlotTableBuilder (内部的 SlotBuilder),计算旧的 “SlotTable 的节点列表”和当前最新的 dataServer的差异值,更新 SlotTableBuilder 内部的 SlotBuilder 相关的 leader 和 follow 值。

5cc59368d0bc9f5b50a9b22baab0a852.png

到这一步实际上已经做完了 SlotTableBuilder 的构建过程。到这里想想接下来该做什么呢? 

可以想想,如果我们触发 SlotTable 重新分配的是某一个 dataA 节点下线了,那么在 SlotTableBuilder::removeDataServerSlots 这一步会将我们正在创建的 SlotTableBuilder 中的 dataA 所管理的 Slot 的 leader 或者 follow 删除掉,那么该 Slot 的 leader 或者 follow 很可能就会变成空。

也就是说该 Slot 没有 data 节点处理请求。于是我们根据当前 SlotBuilder 中是否有为完成分配的 Slot 来决定是否进行重新分配操作,是否有未完成分配的 Slot 代码块如下。

public boolean hasNoAssignedSlots() {
    for (SlotBuilder slotBuilder : buildingSlots.values()) {
      if (StringUtils.isEmpty(slotBuilder.getLeader())) {
        //当前 Slot的leader节点为空
        return true;
      }
      if (slotBuilder.getFollowerSize() < followerNums) {
        //当前 Slot的follow节点的个数小于配置的 followerNums
        return true;
      }
    }
    return false;
  }

创建完成 SlotTableBuilder 并且有没有完成分配的 Slot,执行真正的分配过程,如下图所示:

334943f96260c6fb4bb62c25ed953fae.png

由图可知分配过程最后委托给 DefaultSlotAssigner,DefaultSlotAssigner 在构造方法中实例化了当前正在创建的 SlotTableBuilder /currentDataServers 的视图 /MigrateSlotGroup,其中 MigrateSlotGroup 内部保存的是那些缺少 leader 以及 follow 的 Slot。

public class MigrateSlotGroup {
   //哪些 Slot 缺少 leader
  private final Set<Integer> leaders = Sets.newHashSet();
  //哪些 Slot 缺少 follow 以及缺少的个数
  private final Map<Integer, Integer> lackFollowers = Maps.newHashMap();
}

assign 代码如下,代码中先分配缺少 leader 的 Slot,随后分配缺少 follow 的 Slot。

public SlotTable assign() {
    BalancePolicy balancePolicy = new NaiveBalancePolicy();
    final int ceilAvg =
        MathUtils.divideCeil(slotTableBuilder.getSlotNums(), currentDataServers.size());
    final int high = balancePolicy.getHighWaterMarkSlotLeaderNums(ceilAvg);
    
    //分配缺少 leader 的 Slot
    if (tryAssignLeaderSlots(high)) {
      slotTableBuilder.incrEpoch();
    } 
    //分配缺少 follow 的 Slot
    if (assignFollowerSlots()) {
      slotTableBuilder.incrEpoch();
    } 
    return slotTableBuilder.build();
}

leader 节点分配

进入 tryAssignLeaderSlots 方法内部查看具体分配算法细节,通过代码注释的方式来解释具体实现。

private boolean tryAssignLeaderSlots(int highWatermark) {
    //按照 follows 节点的数量 从大到小排序 0 比较特殊排在最后面,0 为什么比较特殊呢、因为无论怎么分配、
    //最终选择出来的 leader一定不是该 Slot 的 follow、因为该 Slot 的follow为空
    //优先安排 follow 节点比较少的 Slot
    //其实这点也可以想明白的。这些没有 leader 的 Slot 分配顺序肯定是要根据 follow节点越少的优先分配最好
    //以防止这个 follow 也挂了,那么数据就有可能会丢失了。
    List<Integer> leaders =
        migrateSlotGroup.getLeadersByScore(new FewerFollowerFirstStrategy(slotTableBuilder));
    for (int slotId : leaders) {
      List<String> currentDataNodes = Lists.newArrayList(currentDataServers);
       //选择 nextLeader 节点算法?
      String nextLeader =
          Selectors.slotLeaderSelector(highWatermark, slotTableBuilder, slotId)
              .select(currentDataNodes);
      //判断 nextLeader 是否是当前 Slot的 follow 节点 将 follow节点提升为主节点的。
      boolean nextLeaderWasFollower = isNextLeaderFollowerOfSlot(slotId, nextLeader);
      // 将当前 Slot 的 leader 节点用选择出来的 nextLeader 替换
      slotTableBuilder.replaceLeader(slotId, nextLeader);
      if (nextLeaderWasFollower) {
        //因为当前 Slot 将 follow 节点提升为 leader 节点了、那么该 Slot 肯定 follows 个数又不够了、需要再次分配 follow 节点
        migrateSlotGroup.addFollower(slotId);
      }
    }
    return true;
  }

上面分配 leader 代码中核心选择 nextLeader 方法:

String nextLeader =
          Selectors.slotLeaderSelector(highWatermark, slotTableBuilder, slotId)
              .select(currentDataNodes);

通过 Selectors 选择一个合适的 leader 节点。

继续追踪 DefaultSlotLeaderSelector.select 方法内部,同理我们采用代码注释的方式来解释具体实现。

public String select(Collection<String> candidates) {
  //candidates: 当前所有的候选节点,也是 tryAssignLeaderSlots 方法传入的 currentDataServers
  Set<String> currentFollowers = slotTableBuilder.getOrCreate(slotId).getFollowers();
  Collection<String> followerCandidates = Lists.newArrayList(candidates);
  followerCandidates.retainAll(currentFollowers);
  //经过 followerCandidates.retainAll(currentFollowers)) 之后 followerCandidates 
  //仅仅保留当前 Slot 的 follow 节点
  //并且采取了一个策略是 当前 follow 节点作为其他 Slot 的 leader 最少的优先、
  //用直白的话来说。
  //当前 follower 越是没有被当做其他 Slot 的 leader 节点、那么
  //证明他就是越 '闲' 的。必然优先考虑选择它作为leader 节点。
  String leader = new LeastLeaderFirstSelector(slotTableBuilder).select(followerCandidates);
  if (leader != null) {
    DataNodeSlot dataNodeSlot = slotTableBuilder.getDataNodeSlot(leader);
    if (dataNodeSlot.getLeaders().size() < highWaterMark) {
      return leader;
    }
  }
  //从其他的机器中选择一个,优先选择充当 leader 的 Slot 个数最少的那一个 DataServer
  return new LeastLeaderFirstSelector(slotTableBuilder).select(candidates);
}

通过上面 select 方法源码注释相信可以很容易理解 SOFARegistry 的做法。总结来说,就是首先从当前 Slot 的 follow 节点中找出 leader,因为在此情况下不需要做数据迁移,相当于主节点挂了,提升备份节点为主节点实现高可用。

但是具体选择哪一个,SOFARegistry 采取的策略是在所有的 follow 节点中找出最 “闲”的那一个,但是如果它所有的 follow 节点作为 leader 节点管理的 Slot 个数大于 highWaterMark,那么证明该 Slot 的所有 follow 节点都太”忙”了,那么就会从全部存活的机器中选择一个 “当作为 leader 节点管理的 Slot 个数”最少的那一个,但是这种情况其实有数据同步开销的。

follow 节点分配

同理通过源码注解方式来详述:

private boolean assignFollowerSlots() {
    //使用 FollowerEmergentScoreJury 排序得分策略表明
    // 某一个 Slot 缺少越多 follow,排序越靠前。
    List<MigrateSlotGroup.FollowerToAssign> followerToAssigns =
        migrateSlotGroup.getFollowersByScore(new FollowerEmergentScoreJury());
    int assignCount = 0;
    for (MigrateSlotGroup.FollowerToAssign followerToAssign : followerToAssigns) {
      // 当前待分配的 slotId
      final int slotId = followerToAssign.getSlotId();
      // 当前 slotId 槽中还有多少待分配的 follow 从节点。依次迭代分配。
      for (int i = 0; i < followerToAssign.getAssigneeNums(); i++) {
        final List<String> candidates = Lists.newArrayList(currentDataServers);
        // 根据上文中的 DataNodeSlot 结构、依据节点被作为follow 的slot的个数从小到大排序。
        // follows 个数一样、按照最少作为 leader 节点进行排序。
        // 其实最终目的就是找到最 "闲" 的那一台机器。
        candidates.sort(Comparators.leastFollowersFirst(slotTableBuilder));
        boolean assigned = false;
        for (String candidate : candidates) {
          DataNodeSlot dataNodeSlot = slotTableBuilder.getDataNodeSlot(candidate);
          //跳过已经是它的 follow 或者 leader 节点的Node节点
          if (dataNodeSlot.containsFollower(slotId) || dataNodeSlot.containsLeader(slotId)) {
            continue;
          }
          //给当前 slotId 添加候选 follow 节点。
          slotTableBuilder.addFollower(slotId, candidate);
          assigned = true;
          assignCount++;
          break;
        }
      }
    }
    return assignCount != 0;
  }

如之前所述,MigrateSlotGroup 保存了需要进行重新分配 leader 以及 follow 的 Slot 信息。

算法的主要步骤如下:

1. 找到所有没有足够 follow 的 Slot 信息

2. 根据缺少 follow 个数越多越优先原则排序

3. 迭代所有缺少 follow 的 Slot 信息,这里是被 MigrateSlotGroup.FollowerToAssign 包装

4. 内部循环迭代缺少 follow 大小,添加给该 Slot 所需的 follow

5. 对候选 dataServer 进行排序,按照 “闲、忙“成都进行排序

6. 执行添加 follow 节点

到此,已经给缺少 leader 或者 follow 的 Slot 完成了节点分配。

SlotTable 平衡算法

了解完 SlotTable 的变更过程以及算法之后,相信大家对此有了自己的理解。那么 SlotTable 的平衡过程其实也是类似的。

详情可以参考源码:

com.alipay.sofa.registry.server.Meta.slot.balance.DefaultSlotBalancer。

因为在节点的频繁上下线过程中,势必会导致某一些节点的负载(负责的 Slot 管理数量)过高,某些节点的负载又很低,这样需要一种动态平衡机制来保证节点的相对负载均衡。

入口在 DefaultSlotBalancer.balance 方法内部:

public SlotTable balance() {
    //平衡 leader 节点
    if (balanceLeaderSlots()) {
      LOGGER.info("[balanceLeaderSlots] end");
      slotTableBuilder.incrEpoch();
      return slotTableBuilder.build();
    }
    if (balanceHighFollowerSlots()) {
      LOGGER.info("[balanceHighFollowerSlots] end");
      slotTableBuilder.incrEpoch();
      return slotTableBuilder.build();
    }
    if (balanceLowFollowerSlots()) {
      LOGGER.info("[balanceLowFollowerSlots] end");
      slotTableBuilder.incrEpoch();
      return slotTableBuilder.build();
    }
    // check the low watermark leader, the follower has balanced
    // just upgrade the followers in low data server
    if (balanceLowLeaders()) {
      LOGGER.info("[balanceLowLeaders] end");
      slotTableBuilder.incrEpoch();
      return slotTableBuilder.build();
    }
    return null;
}

由于篇幅限制,这里只分析 leader 节点平衡过程。如上源码中的 balanceLeaderSlots() 其余过程和它类似,感兴趣的读者也可以自己查找源码分析。

进入 balanceLeaderSlots 方法内部:

private boolean balanceLeaderSlots() {
    //这里就是找到每一个节点 dataServer 作为leader的 slot 个数的最大天花板值----> 
    //容易想到的方案肯定是平均方式、一共有 slotNum 个slot、
    //将这些slot的leader归属平均分配给 currentDataServer
    final int leaderCeilAvg = MathUtils.divideCeil(slotNum, currentDataServers.size());
    if (upgradeHighLeaders(leaderCeilAvg)) {
      //如果有替换过 leader、那么就直接返回、不用进行 migrateHighLeaders 操作
      return true;
    }
    if (migrateHighLeaders(leaderCeilAvg)) {
      //经过上面的 upgradeHighLeaders 操作
      //不能找到 follow 进行迁移、因为所有的follow也都很忙、在 exclude 当中、
      //所以没法找到一个follow进行迁移。那么我们尝试迁移 follow。
      //因为 highLeader 的所有 follower 都是比较忙、所以需要将这些忙的节点进行迁移、期待给这些 highLeader 所负责的 slot 替换一些比较清闲的 follow
      return true;
    }
    return false;
  }

我们重点关注 upgradeHighLeaders 方法,同理采用源码注解的方式。

private boolean upgradeHighLeaders(int ceilAvg) {
    //"如果一个节点的leader的slot个数大于阈值、那么就会用目标slot的follow节点来替换当前leader"  最多移动 maxMove次数
    final int maxMove = balancePolicy.getMaxMoveLeaderSlots();
    //理解来说这块可以直接将节点的 leader 个数大于 ceilAvg 的 节点用其他节点替换就可以了、为什么还要再次向上取整呢?
    //主要是防止slotTable出现抖动,所以设定了触发变更的上下阈值 这里向上取整、是作为一个不平衡阈值来使用、
    // 就是只针对于不平衡多少(这个多少可以控制)的进行再平衡处理
    final int threshold = balancePolicy.getHighWaterMarkSlotLeaderNums(ceilAvg);
    int balanced = 0;
    Set<String> notSatisfies = Sets.newHashSet();
    //循环执行替换操作、默认执行 maxMove 次
    while (balanced < maxMove) {
      int last = balanced;
      //1. 找到 哪些节点的 leader 个数 超过 threshold 、并对这些节点按照leader 的个数的从大到小排列。
      final List<String> highDataServers = findDataServersLeaderHighWaterMark(threshold);
      if (highDataServers.isEmpty()) {
        break;
      }
      // 没有任何 follow 节点能用来晋升到 leader 节点
      if (notSatisfies.containsAll(highDataServers)) {
        break;
      }
      //2. 找到可以作为新的leader的 节点,但是不包含已经不能添加任何leader的节点、因为这些节点的leader已经超过阈值了。
      final Set<String> excludes = Sets.newHashSet(highDataServers);
      excludes.addAll(findDataServersLeaderHighWaterMark(threshold - 1));
      for (String highDataServer : highDataServers) {
        if (notSatisfies.contains(highDataServer)) {
          //如果该节点已经在不满足替换条件队列中、则不在进行查找可替换节点操作
          continue;
        }
        //找到可以作为新的leader的 节点,但是不包含已经不能添加任何leader的节点、因为这些节点的leader已经超过阈值了。
        //算法过程是: 
        //1. 从 highDataServer 所负责的所有 slot 中找到某一个 slot、这个 slot 满足一个条件就是: 该 slot 的 follow 节点中有一个最闲(也就是 节点的leader的最小)
        //2. 找到这个 slot、我们只需要替换该 slot 的leader为找到的follow
        
        //其实站在宏观的角度来说就是将 highDataServer 节点leader 的所有slot的follow节点按照闲忙程度进行排序、
        //找到那个最闲的、然后让他当leader。这样就替换了 highDataServer 当leader了
        Tuple<String, Integer> selected = selectFollower4LeaderUpgradeOut(highDataServer, excludes);
        if (selected == null) {
          //没有找到任何 follow节点用来代替 highDataServer节点、所以该节点不满足可替换条件、加入到 notSatisfies 不可替换队列中. 以便于外层循环直接过滤。
          notSatisfies.add(highDataServer);
          continue;
        }
        //找到 highDataServer 节点的某一个可替换的 slotId
        final int slotId = selected.o2;
        // 找到 slotId 替换 highDataServer 作为leader 的节点 newLeaderDataServer
        final String newLeaderDataServer = selected.o1; 
        // 用 newLeaderDataServer 替换 slotId 旧的 leader 节点。
        slotTableBuilder.replaceLeader(slotId, newLeaderDataServer); 
        balanced++;
      }
      if (last == balanced) break;
    }
    return balanced != 0;
  }

进入关键查找可替换的 slotId 和新的 leader 节点的过程,同理采用源码注解的方式。

/*
    从 leaderDataServer 所 leader 的所有 Slot中、选择一个可以替换的 slotId
    和新的 leader 来替换 leaderDataServer
   */
  private Tuple<String, Integer> selectFollower4LeaderUpgradeOut(
      String leaderDataServer, Set<String> excludes) {
    //获取当前 leaderDataServer 节点 leader 或者 follow 的 slotId 视图。DataNodeSlot 结构我们上文有说过。
    final DataNodeSlot dataNodeSlot = slotTableBuilder.getDataNodeSlot(leaderDataServer);


    Set<Integer> leaderSlots = dataNodeSlot.getLeaders();
    Map<String, List<Integer>> dataServers2Followers = Maps.newHashMap();
    //1. 从 dataNodeSlot 获取 leaderDataServer 节点leader的所有slotId: leaderSlots
    for (int slot : leaderSlots) {
      //2. 从slotTableBuilder 中找出当前 slot 的follow
      List<String> followerDataServers = slotTableBuilder.getDataServersOwnsFollower(slot);
      //3. 去掉excludes ,得到候选节点,因为 excludes 肯定不会是新的 leader 节点
      followerDataServers = getCandidateDataServers(excludes, null, followerDataServers);
      //4. 构建 候选节点到 slotId 集合的映射关系。
      for (String followerDataServer : followerDataServers) {
        List<Integer> followerSlots =
            dataServers2Followers.computeIfAbsent(followerDataServer, k -> Lists.newArrayList());
        followerSlots.add(slot);
      }
    }
    if (dataServers2Followers.isEmpty()) {
      //当 leaderDataServer 节点的follow 都是 excludes 中的成员时候、那么就有可能是空的。
      return null;
    }
    List<String> dataServers = Lists.newArrayList(dataServers2Followers.keySet());
    //按照 候选节点的 leader的 slot 个数升序排序、也就是也就是找到那个最不忙的,感兴趣可以查看 leastLeadersFirst 方法内部实现。
    dataServers.sort(Comparators.leastLeadersFirst(slotTableBuilder));
    final String selectedDataServer = dataServers.get(0);
    List<Integer> followers = dataServers2Followers.get(selectedDataServer);
    return Tuple.of(selectedDataServer, followers.get(0));
  }

至此,我们完成了高负载 leader 节点的替换,在此过程中如果有替换过,那么直接返回。如果没有替换过,我们会继续执行 DefaultSlotBalancer 中的 migrateHighLeaders 操作。

因为如果经过 DefaultSlotBalancer 中的 upgradeHighLeaders 操作之后没有进行过任何 leader 的替换,那么证明高负载的 leader 节点同样它的 follow 节点也很忙,所以需要做得就是对这些忙的 follow 节点也要进行迁移。

具体的源码注释如下:

private boolean migrateHighLeaders(int ceilAvg) {
    final int maxMove = balancePolicy.getMaxMoveFollowerSlots();
    final int threshold = balancePolicy.getHighWaterMarkSlotLeaderNums(ceilAvg);


    int balanced = 0;
    while (balanced < maxMove) {
      int last = balanced;
      // 1. find the dataNode which has leaders more than high water mark
      //    and sorted by leaders.num desc
      final List<String> highDataServers = findDataServersLeaderHighWaterMark(threshold);
      if (highDataServers.isEmpty()) {
        return false;
      }
      // 2. find the dataNode which could own a new leader
      // exclude the high
      final Set<String> excludes = Sets.newHashSet(highDataServers);
      // exclude the dataNode which could not add any leader
      excludes.addAll(findDataServersLeaderHighWaterMark(threshold - 1));
      final Set<String> newFollowerDataServers = Sets.newHashSet();
      // only balance highDataServer once at one round, avoid the follower moves multi times
      for (String highDataServer : highDataServers) {
        Triple<String, Integer, String> selected =
            selectFollower4LeaderMigrate(highDataServer, excludes, newFollowerDataServers);
        if (selected == null) {
          continue;
        }
        final String oldFollower = selected.getFirst();
        final int slotId = selected.getMiddle();
        final String newFollower = selected.getLast();
        slotTableBuilder.removeFollower(slotId, oldFollower);
        slotTableBuilder.addFollower(slotId, newFollower);
        newFollowerDataServers.add(newFollower);
        balanced++;
      }
      if (last == balanced) break;
    }
    return balanced != 0;
  }

PART. 3

session 和 data 节点

如何使用路由表

上文我们了解了 SlotTable 路由表在心跳中从 Meta 节点获取并且更新到本地中。

那么 session 和 data 节点如何使用路由表呢?

首先,我们先看看 session 节点如何使用 SlotTable 路由表。

session 节点承担着客户端的发布订阅请求,并且通过 SlotTable 路由表对 data 节点的数据进行读写。session 节点本地 SlotTable 路由表保存在 SlotTableCacheImpl 。

public final class SlotTableCacheImpl implements SlotTableCache {
  // 不同计算 Slot 位置的算法抽象
  private final SlotFunction slotFunction = SlotFunctionRegistry.getFunc();


  //本地路由表、心跳中从 Meta 节点获取到。
  private volatile SlotTable slotTable = SlotTable.INIT;
    
    
  //根据 dataInfoId 获取 slotId
  @Override
  public int slotOf(String dataInfoId) {
    return slotFunction.slotOf(dataInfoId);
  }
}

源码中的 SlotFunctionRegistry 注册了两种算法实现,分别是 crc32 和 md5,源码如下所示:

public final class SlotFunctionRegistry {
  private static final Map<String, SlotFunction> funcs = Maps.newConcurrentMap();


  static {
    register(Crc32cSlotFunction.INSTANCE);
    register(MD5SlotFunction.INSTANCE);
  }


  public static void register(SlotFunction func) {
    funcs.put(func.name(), func);
  }


  public static SlotFunction getFunc() {
    return funcs.get(SlotConfig.FUNC);
  }
}

随便选择某一个算法,例如 MD5SlotFunction,根据 dataInfoId 计算 slotId 的实现如下:

public final class MD5SlotFunction implements SlotFunction {
  public static final MD5SlotFunction INSTANCE = new MD5SlotFunction();


  private final int maxSlots;
  private final MD5HashFunction md5HashFunction = new MD5HashFunction();


  private MD5SlotFunction() {
    this.maxSlots = SlotConfig.SLOT_NUM;
  }
  //计算 slotId的最底层逻辑。可见也是通过取 hash 然后对 Slot 槽个数取余
  @Override
  public int slotOf(Object o) {
    // make sure >=0
    final int hash = Math.abs(md5HashFunction.hash(o));
    return hash % maxSlots;
  }
}

了解了具体根据 DataInfoId 来通过 SlotTable 获取具体的数据 slotId,我们来看看在 session 节点中何时触发计算 datInfoId 的 slotId。

我们可以想想,一般 session 节点使用来处理客户端的发布订阅请求,那么当有发布请求的时候,发布的数据同时也会向 data 节点写入发布的元数据,那么肯定需要知道该数据保存在哪一台机器上,此时就需要根据 dataInfoId 找到对应的 slotId,进而找到对应的 leader 节点,通过网络通讯工具将发布请求转发给该节点处理,session 数据接收发布请求处理 handler 为 PublisherHandler。

e6a96ec46b66a5181e1d0fa698e9f1c9.png

如上面时序图所示,在 DataNodeServiceImpl 最后的 commitReq 方法中会将发布请求添加到内部的 BlockingQueue 当中去,DataNodeServiceImpl 内部的 Worker 对象会消费 BlockingQueue 中内部执行真正的数据写入过程。

详细源码请参考 :

private final class Worker implements Runnable {
    final BlockingQueue<Req> queue;
    Worker(BlockingQueue<Req> queue) {
      this.queue = queue;
    }


    @Override
    public void run() {
      for (; ; ) {
          final Req firstReq = queue.poll(200, TimeUnit.MILLISECONDS);
          if (firstReq != null) {
            Map<Integer, LinkedList<Object>> reqs =
                drainReq(queue, sessionServerConfig.getDataNodeMaxBatchSize());
            //因为 Slot 的个数有可能大于 work/blockingQueue 的个数、所以
            //并不是一个 Slot 对应一个 work、那么一个 blockQueue 中可能存在发往多个slot的数据、这里
            //有可能一次发送不完这么多数据、需要分批发送、将首先进入队列的优先发送了。
            LinkedList<Object> firstBatch = reqs.remove(firstReq.slotId);
            if (firstBatch == null) {
              firstBatch = Lists.newLinkedList();
            }
            firstBatch.addFirst(firstReq.req);
            request(firstReq.slotId, firstBatch);
            for (Map.Entry<Integer, LinkedList<Object>> batch : reqs.entrySet()) {
              request(batch.getKey(), batch.getValue());
            }
          }
        }
     }
 }


private boolean request(BatchRequest batch) {
  final Slot slot = getSlot(batch.getSlotId());
  batch.setSlotTableEpoch(slotTableCache.getEpoch());
  batch.setSlotLeaderEpoch(slot.getLeaderEpoch());
  sendRequest(
      new Request() {
        @Override
        public Object getRequestBody() {
          return batch;
        }


        @Override
        public URL getRequestUrl() {
          //通过 Slot 路由表找到对应的 leader data 节点,这
          //个路由表是 心跳中从 Meta 节点获取来的。
          return getUrl(slot);
        }
      });
  return true;
}

‍

——

借此源码解析的任务让我仔细的研究了 SlotTable 数据预分片机制,真正了解了一个工业级别的数据分片是如何实现的,高可用是如何做的,以及在功能实现中的各种取舍。

因此和大家分享这篇对 SOFARegistry SlotTable 的剖析 ,欢迎大家留言指导。

也有幸参与了 SOFARegistry 的社区会议,在社区前辈们的指导下认真了解了具体细节的设计考量,一起讨论 SOFARegistry 的未来发展。

欢迎加入,参与 SOFA 社区的源码解析

目前 SOFARegistry 源码解析任务已发完, Layotto 源码解析还有 1 个任务待认领,有兴趣的朋友可以试试看。

55121ea88c0002e3ab8704711aaad9c6.png

Layotto

待认领任务

# 01 #

01

WebAssembly 相关

https://github.com/mosn/layotto/issues/427

之后也会推出其他项目的源码解析活动,敬请期待...

   本周推荐阅读  

7f80f95c75d9310095d26354f50bc86b.png

HAVE FUN | SOFARegistry、Layotto 源码解析活动最新进展

50b97ca53265bfbd10808e9a937221db.png


BabaSSL 发布 8.3.0|实现相应隐私计算的需求

MOSN 发布 v1.0 版本!

7c7b8cd45ea30ed729dad2d315fc7ffa.png

“SOFA 星球”闯关计划 ——Layotto 飞船


1b48427523744b538b251ed0ee2d48b6.png

金融级应用开发|SOFABoot 框架剖析

d91ff98e5f0062bc5eca485e73acc112.png

三个原则判定电源环路稳定性 环路稳定性测试接线图如图2所示,将信号电阻(Inserted Resistor )接入电源的环路控制电路中,注入一特定幅度和频率范围的扫频干扰信号(OUT),当干扰信号达到一定的幅度时,环路系统便对干扰信号有所响应,然后使用频率响应分析仪对电源环路输出端(CH2)和电源环路干扰信号注入端(CH1)同时进行比值跟踪测量,则可以得出频率与增益和相位的关系曲线图——波特曲线图(Bode Plot Graph),如图1所示。相位裕度,增益裕度,穿越频率,如图(波特图)所示。相位容限即相位裕度,增益容限即增益裕度。 阅读详情

相关推荐

FOFA奇技淫巧:从搜索语法到实战场景的深度挖掘

本文深入探讨了FOFA搜索语法在网络空间测绘中的高效应用,从基础语法到实战场景的深度挖掘。通过渗透测试、威胁狩猎、资产发现等实际案例,展示了如何利用FOFA精准定位目标系统、识别蜜罐、追踪威胁,并提供了合规使用指南和性能优化技巧,助力安全工作者提升效率。

weixin_32428571的博客 237

微服务之注册中心SOFARegistry

1.SOFARegistry部署链接 https://www.sofastack.tech/projects/sofa-registry/server-quick-start/ 2.

NeverSayNever 2771

PackML工业标准解析:统一机器状态管理与数据交换的实践指南

在工业自动化与智能制造领域,实现设备间的互联互通与高效协同是核心挑战。这背后依赖于一套标准化的机器行为与数据交换规范,其原理在于通过定义统一的状态模型与接口协议,消除不同厂商设备间的“语言”隔阂。这项技术的核心价值在于大幅降低系统集成复杂度、提升运维效率与生产透明度,是构建数字化工厂的基石。其典型应用场景包括包装机械、离散制造产线等需要多设备协同的领域。本文聚焦的PackML(包装机械语言)标准,正是这一理念的杰出实践,它通过一套严谨的**状态机模型**和**数据标签(PackTags)** 体系,为机器提

weixin_30070339的博客 401

sofaRegistry 学习篇

文章目录地址个人笔记上图服务发现的推、拉模型服务推送性能优化(可以应用到日常大批量任务处理优化)异常处理 地址 海量数据下的注册中心 - SOFARegistry 架构介绍 个人笔记 dubbo不支持Eureka,Eureka是基于application级别,dubbo是group,version,sofaRegistry以dataInfoId sofaRegistry跟其他注册中心一样,有自己的维护健康的机制。Eureka采用TTL(Time To Live),Zookeeper 采用心跳跟sess

大鸡腿的博客 861

别再用Excel硬算传热了!用MATLAB/Octave复现国赛数学建模A题(附完整代码与避坑指南)

本文详细介绍了如何利用MATLAB/Octave高效解决数学建模中的传热问题,替代传统的Excel计算方法。通过复现2018年国赛A题的高温作业服传热模型,从理论推导到代码实现,提供了完整的有限差分法解决方案和参数拟合技巧,帮助读者提升数学建模效率和准确性。

weixin_30512965的博客 379

建议收藏:华为海思IC设计笔试题,含解析(附下载)

接触过的部门有麒麟芯片、CPU/GPU,总体面试感觉非常好,规范且认真,第一轮手撕代码状态机+复盘机考题+瞄准几个知识点深挖,时间1hour+;一二轮面试是连着的,所以很累。但是岗位就那么多,在面试的时候,很多同学因为准备不充分,与岗位失之交臂,无缘进入该公司。时序电路有记忆功能,不会产生锁存器,锁存器一般在组合逻辑中case写不全,else写不全,或者敏感列表写不全。

nuoweishizi的博客 3288

windows下搭建本地sofa-registry

SOFARegistry 是蚂蚁金服开源的一个生产级、高时效、高可用的服务注册中心。SOFARegistry 最早源自于淘宝的 ConfigServer,十年来,随着蚂蚁金服的业务发展,注册中心架构已经演进至第五代。目前 SOFARegistry 不仅全面服务于蚂蚁金服的自有业务,还随着蚂蚁金融科技服务众多合作伙伴,同时也兼容开源生态。SOFARegistry 采用 AP 架构,支持秒级时效性推送,同时采用分层架构支持无限水平扩展。

Spirit_NKlaus的专栏 1305

VS2022命令行编译C++报错?一招解决‘fatal error C1034: iostream: 不包括路径集‘

本文针对VS2022命令行编译C++时常见的'fatal error C1034: iostream: 不包括路径集'错误,深入剖析了其根源在于环境变量缺失。文章提供了永久配置用户环境变量(PATH、INCLUDE、LIB)的一劳永逸解决方案,确保在任何终端中都能直接使用cl命令进行编译,并给出了进阶配置与常见错误排查指南。

weixin_29011667的博客 304

SOFARegistry 源码|数据同步模块解析

本文主要介绍了 SOFARegistry 中的数据同步模块。在整个过程中,我们首先从 SOFARegistry 角色分类阐述不同角色之间存在的数据同步问题,并针对其中 SessionServer 与 DataServer 之间的数据同步 和 DataServer 多副本之间的数据同步进行了展开。......

金融级分布式架构 452

YOLOv8改进,YOLOv8主干网络替换为ShuffleNetV2

ShuffleNet V2的设计原则:1.减少内存访问成本:通过优化数据传输路径,减少内存访问延迟,提高整体运算速度。2.考虑平台特性:针对不同硬件平台的特性进行优化设计,确保在目标平台上的高效运行。3.结构简洁高效:采用简洁而高效的模块设计,降低计算复杂度,同时保持或提升模型的预测准确性。下图,以四种不同计算复杂度水平下的准确性(在ImageNet验证集上的分类),速度和FLOPs的测量结果。(a, c) 为GPU结果,批量大小为8。(b, d) 为ARM结果,批量大小为1。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1565

46、大数据助力SDN优化流量管理与控制器

本文探讨了大数据在软件定义网络(SDN)中的应用,重点分析了如何利用大数据技术优化SDN控制器的可扩展性和弹性。通过介绍SDN控制器的核心功能及其面临的挑战,提出了集中式、分布式、混合式和分层式控制器放置策略,并结合大数据分析系统架构与技术,详细阐述了在控制器放置优化、流聚合和备份路径识别等场景中的实际应用。文章还给出了大数据分析在SDN控制器优化中的操作流程与优势,展望了未来结合人工智能与物联网的发展方向,强调了数据安全与算法效率的重要性。

xx56789的博客 86

一致性hash的奥妙

从上面情况来看,一致性hash算法对于新增、删除节点,只是会影响部分数据。

大鸡腿的博客 450

Go语言爬虫:chromedp 的详细使用

chromedp 是 Go 语言中无头浏览器自动化的终极利器。它能驱动真实的 Chrome 浏览器,处理 JavaScript 渲染、复杂交互和反爬虫机制

数据知道的博客 4493

SOFARegistry:高性能、可扩展的服务注册与发现组件

是蚂蚁集团开源的一款服务注册与发现框架,旨在提供高可用性、高性能和可扩展性的服务治理方案。本文将深入探讨其核心特性、技术分析及应用场景,帮助开发者更好地理解和利用这一强大工具。 ## 项目简介 SOFARegistry 是一款基于 Publish/Subscribe 模型设计的服务注册与发现系统,它支持多数据中心、多机房的跨地域通信,且具备实时数据同步能力。该项目的主要目标是解决大型分布式系统

gitblog_00011的博客 663

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

探索 SOFARegistry(一)|基础架构篇

简 介SOFARegistrySOFARegistry 是蚂蚁集团在生产大规模使用的服务注册中心,经历了多年大促的考验,支撑蚂蚁庞大的服务集群,具有分布式可水平扩容、容量大、推送延迟低...

金融级分布式架构 1291

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

[从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了坚实的铺垫。本系列我们接着分析 Data Server,顺带会涉及一些 Session Server。因为 DataServer 和 MetaServer 代码实现和架构的基本套路类似,所以我们主要关心差异点和DataServer的特点。本文会分析DataServer程序的基本架构。

罗西的思考 1003

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

SOFARegistry | 大规模集群优化实践

SOFARegistry 在蚂蚁内部迭代升级过程中,每年大促都会引来一些新的挑战,通过不断的优化这些在大规模集群遇到的性能瓶颈,我们总结出一些优化方案,来解决大规模集群遇到的性能问题。 通过阅读这篇文章,读者可以学习到一些 Java 和 Go 语言系统的优化技巧,在系统遇到瓶颈的时候,能够知道有哪些优化手段针对性的进行优化。

金融级分布式架构 623

蚂蚁金服服务注册中心 SOFARegistry 解析 | 服务发现优化之路

SOFAStack (Scalable Open Financial Architecture Stack)是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架...

金融级分布式架构 783

SOFA Registry 开源项目教程

SOFA Registry 是一个高性能、高可用、可扩展的分布式注册中心,适用于大规模分布式系统。以下是其主要目录结构及其功能介绍: ``` sofa-registry/ ├── bin/ │ ├── start.sh │ └── stop.sh ├── conf/ │ ├── registry.properties │ └── logback.xml ├── docs/ │

gitblog_00526的博客 492

Jetpack Compose 深入探索系列三:Compose runtime

slot table(插槽表)是一个优化的内存结构,runtime 使用它来存储组合的当前状态。它在初始组合时填充数据,并在每次重新组合时更新。我们可以把它看作是所有Composable函数调用的跟踪,包括它们在源码中的位置、参数、记住的值、CompositionLocals等等。它包含了在合成过程中发生的一切内容。所有这些信息将被 Composer 稍后用于生成下一个更改列表,因为对树的任何更改总是依赖于当前状态。 slot table 记录Composable的状态,而 change list 则是对

川峰的博客 1310

深入理解 Jetpack Compose 内核:SlotTable 系统

Compose 的绘制有三个阶段,组合 > 布局 > 绘制。从组合到上屏的一系列过程中主要是依靠 SlotTable 系统来完成,SlotTable 记录了 Composiiton 的状态,当 SlotTable 的状态发生变化时,会触发 LayoutNode 树的更新,实现 UI 重新渲染。......

移动端开发干货分享 3506

【亲测免费】 SOFA Registry 开源项目教程

SOFA Registry 是一个基于 Java 实现的高性能、高可用、高扩展性的分布式注册中心,由蚂蚁金服开发并开源。它支持海量数据注册和查询,适用于微服务架构中的服务注册与发现。SOFA Registry 提供了稳定的服务注册和发现机制,确保服务间的可靠通信。 ## 项目快速启动 ### 环境准备 - JDK 1.8 或更高版本 - Maven 3.2.5 或更高版本 - Git ###

gitblog_00447的博客 651

SOFARegistry完全指南:从0到1搭建高性能服务注册中心

SOFARegistry是蚂蚁金服开源的生产级、低延迟、高可用服务注册中心,采用AP架构设计,支持毫秒级实时推送和无限水平扩展。作为微服务架构的核心组件,它能帮助开发者轻松实现服务注册与发现,确保分布式系统的稳定运行。 ## 📌 核心优势:为什么选择SOFARegistry? SOFARegistry经过十年技术演进,已发展至第六代架构,具备以下关键特性: - **高可用性**:支持多副本

gitblog_00023的博客 373

docker服务找不到注册中心_服务注册中心概述之SOFARegistry

一:注册中心概念如上图,注册中心最常见的应用场景是rpc调用的服务寻址,在rpc调用过程中,存在两个角色,分别是服务发布者(Publisher)和服务订阅者(Subscriber)。Publisher需要把服务注册到注册中心,发布的内容主要是ip,端口,序列化方式;Subscriber在第一次调用时,会通过Registry找到相应的地址列表,通过不同的算法(比如随机,轮训等)获取一个服务...

weixin_39886841的博客 505

SOFA Registry的简单安装

参考资料 https://www.bookstack.cn/read/SOFARegistry/Server-QuickStart.md 安装步骤 导出源码 git clone https://gitee.com/sofastack/sofa-registry.git 编译打包 cd sofa-registry mvn clean package -DskipTests 解压到指定目录 cp server/distribution/integration/target/registry-integrat

白首码农的博客 1063

蚂蚁金服服务注册中心 MetaServer 功能介绍和实现剖析 | SOFARegistry 解析

SOFAStack(ScalableOpenFinancialArchitecture Stack) 是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。 SOFARegistry 是蚂蚁金服开源的具有承载海量服务注册和订阅能力的、高可用的服务注册中心,在支付宝/蚂蚁金服的业务发展驱动下,近十年间已经演进至第五代。 本...

大隐隐于野 648
上一篇: MOSN 发布 v1.0 版本!
下一篇: 开源许可证热门及冷门问题接地气探讨
SOFAStack
博客等级 码龄7年 747粉丝 389原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值