vearch源码阅读——创建db、space以及partition的过程

182 阅读3分钟

继续带着问题读这段代码

  • 怎么决定partition挂在哪个ps上的

createDB

创建db的http请求长这样,我们从这里入手

curl -XPUT -H "content-type:application/json" -d '{
    "name": "db_name"
}
' http://master_server/db/_create

函数调用链是这样的:

func (ca *clusterAPI) createDB(c *gin.Context) ->
    func (ms *masterService) createDBService(ctx context.Context, db *entity.DB) 

来看看createDBService()函数做了啥

createDBService

func (ms *masterService) createDBService(ctx context.Context, db *entity.DB) (err error) {
    // 检查是否合法:1.dbname是否合法 2.不能有id
    db.Validate()
    // 检查ps,初次创建是没有的
    ms.validatePS(ctx, db.Ps)

    // 为DB生成一个唯一的ID
    db.Id, _ = ms.Master().NewIDGenerate(ctx, entity.DBIdSequence, 1, 5*time.Second)

    err = ms.Master().STM(context.Background(), func(stm concurrency.STM) error {
            idKey, nameKey, bodyKey := ms.Master().DBKeys(db.Id, db.Name)

            if stm.Get(nameKey) != "" {
                return fmt.Errorf("dbname %s is exists", db.Name)
            }

            if stm.Get(idKey) != "" {
                return fmt.Errorf("dbID %d is exists", db.Id)
            }
            value, _ := json.Marshal(db)

            stm.Put(nameKey, cast.ToString(db.Id))
            stm.Put(idKey, db.Name)
            stm.Put(bodyKey, string(value))
            return nil
    })
    return err
}

可以看见步骤是:检查db和ps是否合法,然后为db生成一个ID。之后,为这个db准备了3个key,并在etcd里存上相关的value

  • idKey: db/{db.Id}
  • nameKey: db/{db.Name}
  • bodyKey: db/body/{db.Id} 再以这3个key作为key,

createSpace

Space是表空间,相当于sql里的table,创建表空间的请求如下:具体参数说明文档在这里

curl -XPUT -H "content-type: application/json" -d'
{
    "name": "space1",
    "partition_num": 1,
    "replica_num": 1,
    "engine": {
        "name": "gamma",
        "index_size": 70000,
        "id_type": "String",
        "retrieval_type": "IVFPQ",
        "retrieval_param": {
            "metric_type": "InnerProduct",
            "ncentroids": 2048,
            "nsubvector": 32
        }
    },
    "properties": {
        "field1": {
            "type": "keyword"
        },
        "field2": {
            "type": "integer"
        },
        "field3": {
            "type": "float",
            "index": true
        },
        "field4": {
            "type": "string",
            "array": true,
            "index": true
        },
        "field5": {
            "type": "integer",
            "index": true
        },
        "field6": {
            "type": "vector",
            "dimension": 128
        },
        "field7": {
            "type": "vector",
            "dimension": 256,
            "format": "normalization",
            "store_type": "RocksDB",
            "store_param": {
                "cache_size": 2048,
                "compress": {"rate":16}
            }
        }
    }
}
' http://master_server/space/$db_name/_create

入口函数是func (ca *clusterAPI) createSpace(c *gin.Context)

这个函数工作就是根据请求构建了一个space对象。space.Engine记录了引擎信息,由请求传入,space.Version被初始化为1。然后调用createSpaceService函数,这个函数有点长,我们一步一步来看

第一步是解析Properties,space.Properties表示这个表的每一项,类似于sql表的每一列

_, err = mapping.SchemaMap(space.Properties) // 解析Properties

解析函数是SchemaMap, 就是确认是否合法

下一步:确认这个要创建的space是否存在,存在的话就返回

下一步:扫描所有Server,存在servers数组里,并为space生成唯一id

servers, err := ms.Master().QueryServers(ctx)
spaceID, err := ms.Master().NewIDGenerate(ctx, entity.SpaceIdSequence, 1, 5*time.Second)
space.Id = spaceID

下一步:这步很关键,可能能解决提出的问题

width := math.MaxUint32 / space.PartitionNum
for i := 0; i < space.PartitionNum; i++ {
    partitionID, err := ms.Master().NewIDGenerate(ctx, entity.PartitionIdSequence, 1, 5*time.Second) // 自增

    space.Partitions = append(space.Partitions, &entity.Partition{
            Id:      entity.PartitionID(partitionID),
            SpaceId: space.Id,
            DBId:    space.DBId,
            Slot:    entity.SlotID(i * width),
    })
}

创建space的请求中包含了partition_num,这一步根据partition_num创建新的对应数量的partition信息,挂在space下面,但是这时候还不确定对应的具体机器

下一步 关键,能回答问题就在这里

//peak servers for space
var paddrs [][]string
for i := 0; i < len(space.Partitions); i++ {
    if addrs, err := ms.generatePartitionsInfo(
        servers, 
        serverPartitions,   //计算好每个server的partition数量
        space.ReplicaNum, 
        space.Partitions[i]
    ); err != nil {
            return err
    } else {
            paddrs = append(paddrs, addrs)
    }
}

哪个partition用哪个机器的问题就在这里,看完generatePartitionsInfo函数就能回答了

根据space.Partitions也就是partition的数量,对每个partition调用一次generatePartitionsInfo函数。参数serverPartitions记录了每个server下面有多少partition

generatePartitionsInfo函数先给所有server按照partition数量升序排列,然后根据备份数量Replicas取最少的几个server,记录下他们的地址RpcAddr()ID加入数组返回

这一步,创建的每个partition用的机器地址就存在了二维数组paddrs[][]里,这就回答了上面的问题啦

下一步就是要给这些机器发送创建partition的请求:

client.CreatePartition(addr, space, partition.Id)

最后check一下所有partition是否ok

ok,问题解决

createPartition

这个要看ps里