添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

moss 将一个实例称之为一个 collection ,每个 collection 的存储分为4个区域( top/mid/base/clean ):

+---------------------+
| collection          |
|                     |
| * top               |
| * mid               |
| * base              |
| * clean             |
|                     |
+---------------------+
  
  • top 每当有新数据写入时,会写入top区域。
  • midtop写入很多新数据,触发merge时,merge后的数据会移动到mid区域。
  • base 当配置持久化选项时,持久化后的数据会被移动到base区域。
  • cleancollection配置了CachePersisted选项后,当进行持久化后的segment会被移动到clean区。
  • 排序线段栈

    moss使用一个栈(stack)结构来存储每次写入的新数据,栈中的每一个元素为一个线段(segment)。每次新写入的 数据在栈顶(top)。

    moss的全部写操作都要运行在一个batch中,因此每次要有写操作时,需要先调用NewBatch获取一个batchbatchmoss的操作提供原子性、独立性保证。 每个batch中包含一个segment和任意个子batch(当前子collectionbatch)。batch继承自segment

    segment为拥有一个byte数组和一个uint64数组的数据结构,每个操作都会成为一个记录,写入segment的byte数 组中,然后在uint64数组中写入偏移量和长度进行标记定位(操作类型、key长度和value长度聚合在一个uint64中)。 由于segment的数据排序算法的局限性限制,在每个batch/segment中,每个key只能记录一个操作

    在操作完相关操作后,需要调用mossExecuteBatch方法来应用batch中的相关操作。在调用ExecuteBatch 后会对segment中记录的相关操作记录进行排序,成为不可变的数据段。然后调用buildStackDirtyTop, 构建/压入segment栈(segmentStack) 放入top区域,新写入的segment将放入栈顶:

        +---------------------+
        | * top               |
        |     segment-0       |
        | * mid               |
        | * base              |
        | * clean             |
        +---------------------+
    

    当经过很多batch操作后,会变为:

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        |     segment-4       |
        |     segment-3       |
        |     segment-2       |
        |     segment-1       |
        |     segment-0       |
        | * mid               |
        | * base              |
        | * clean             |
        |                     |
        +---------------------+
    

    collection启动(Start) 时,会启动一个merger协程,它会被用户主动触发被新的写入触发 或者idle超时触发。 进行merge时,首先把topmid区域的segment stack合并成一个segment stack,放入mid区域:

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        | * mid               |
        |     segment-4       |
        |     segment-3       |
        |     segment-2       |
        |     segment-1       |
        |     segment-0       |
        | * base              |
        | * clean             |
        |                     |
        +---------------------+
    

    然后再调用mergerMain, 将midbase区域合并起来。然后生成一个堆合并迭代器iterator, 然后将多个segment stack合并成一个新segment stack

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        | * mid               |
        |     segment-0...4   |
        | * base              |
        | * clean             |
        |                     |
        +---------------------+
    

    collection启动(Start) 时,还会启动一个Persister 进行持久化的逻辑。当用户配置了持久化回调 时,持久化的逻辑会被触发。当进行完上面描述的merge操作后,会调用mergerNotifyPersistermid区域的segment stack移动到base区域:

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        |     segment-5       |
        | * mid               |
        | * base              |
        |     segment-0...4   |
        | * clean             |
        |                     |
        +---------------------+
    

    然后,Persister会调用LowerLevelUpdatebase区域的segment stack传递给用户/默认的持久化回调进行持久化 合并操作,返回一个合并后的snapshot,然后将该snapshot存储在lowerLevelSnapshot

    当用户配置了CachePersisted 时,在持久化完成时,会将base区域的segment stack移动到clean区域:

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        | * mid               |
        | * base              |
        | * clean             |
        |     segment-0...4   |
        +---------------------+
        | lower-level-storage |
        |                     |
        | mutations from...   |
        |     segment-0...4   |
        +---------------------+
    

    否则会被清除:

        +---------------------+
        | collection          |
        |                     |
        | * top               |
        | * mid               |
        | * base              |
        | * clean             |
        |                     |
        +---------------------+
        | lower-level-storage |
        |                     |
        | mutations from...   |
        |     segment-0...4   |
        +---------------------+
    

    默认持久化

    如果使用默认的持久方式时,使用的默认持久化回调为:

        func(higher Snapshot) (Snapshot, error) {
            var ss Snapshot
            ss, err = s.Persist(higher, persistOptions)
            if err != nil {
                return nil, err
            if storeSnapshotInit != nil {
                storeSnapshotInit.Close()
                storeSnapshotInit = nil
            return ss, err
    

    其核心函数为persist 它主要做几件事:

  • base区域的数据与之前的snapshot进行合并、压缩;
  • 以特定格式将snapshot写入新的持久化文件。
  • 默认持久化采用的写文件的函数为persistBasicSegment

    持久化文件格式

    moss持久化文件的命名格式为data-xxxxxx-.moss,中间部分为文件序号,序号最大的文件为最新一份持久化文件。

    文件开头4K字节为Header 部分,存储一些元数据。

    文件结尾为4K字节(可能超过4K字节,但是4K字节对其存储)的Footer 部分,存储每个SegmentLoc在文件中的偏移量。SegmentLoc 记录了每个segment持久化后的信息。

    moss也支持自定义持久化的方式,只要将实现CollectionOptionsLowerLevelInit/LowerLevelUpdate这些对象/回调 即可。其原生持久化也是依赖这些接口来实现的。自定义实现持久化时,就不需要使用OpenStoreCollection来创建collection了, 直接实现对应接口,使用NewCollection来创建collection即可。

    moss作为一个嵌入式cache引擎,嵌入到用户软件中,作为内存型的K-V存储,支持自定义持久化回调,可以方便的将数据 持久化到默认文件或者自定义的database中。但是其多层的线段栈结构也有很大的局限性:

  • 每次操作的batch都会形成一个segment,因此,每次batch的操作必须操作很多数据才能将segment合并的代价摊薄, 同时每个batch操作时,每个Key都只能操作一次,因此还可能需要使用map结构来去重 因此,适用的使用场景还是相当的有限。
  • 持久化的方式使用异步的方式,就丧失了crash consistency。而且其实现,使得用户难捕捉到持久化失败时详细的错 误,很难精确处理每条数据持久化的错误,因此其就丧失了称为一款存储引擎的资格,充其量只能称为一个不太注重持久化 的cache,如memcahced
  • moss design
  •