Loading

Nebula 数据写入流程梳理

1、总览

  • Webservice
  • Query
  • Meta
  • Storage
  • Raftpart
  • Kvstore

1.1、时序图

2、Query流程

query:parser → validator → optimizer → executor

Parser 完成对语句的词法语法解析并生成抽象语法树(AST),Validator 会将 AST 转化为执行计划,Optimizer 对执行计划进行优化,而 Executor 负责实际数据的计算。

上下文QueryContext内容:

  • query语句(ctx->setQuery(query))
  • 执行线程(ctx→setRunner(getThreadManager()))
  • 登录session(ctx→setSessionMgr(sessionManager_.get()))
  • 执行参数(ctx->setParameterMap(parameterMap))

2.1、parser

Parser 代码实现在:src/parser 目录下

包括字典文件(lexicon)和语法规则(grammar)

词法文件是 src/parser/scanner.lex,语法文件是 src/parser/parser.yy

2.2、validator

Validator 代码实现在 src/graph/validator 和 src/graph/planner 目录。

src/graph/validator 目录主要包括各种子句的 Validator 实现,比如 OrderByValidator、LimitValidator、GoValidator 等

src/graph/planner/plan 目录定义了所有 PlanNode 的数据结构,用于生成最终的执行计划

src/graph/planner 目录还定义了 nGQL 和 MATCH 语句的 planner 实现,用于生成 nGQL 和 MATCH 语句执行计划

入口函数:

// Entry of validating sentence.
// Check session, switch space of validator context, create validators and validate.
// static
Status Validator::validate(Sentence* sentence, QueryContext* qctx) {
  DCHECK(sentence != nullptr);
  DCHECK(qctx != nullptr);
 
  // Check if space chosen from session. if chosen, add it to context.
  auto session = qctx->rctx()->session();
  if (session->space().id > kInvalidSpaceID) {
    auto spaceInfo = session->space();
    qctx->vctx()->switchToSpace(std::move(spaceInfo));
  }
 
  auto validator = makeValidator(sentence, qctx);
  NG_RETURN_IF_ERROR(validator->validate());
 
  auto root = validator->root();
  if (!root) {
    return Status::SemanticError("Get null plan from sequential validator");
  }
  qctx->plan()->setRoot(root);
  return Status::OK();
}

2.3、optimizer

优化器的源码实现都在 src/graph/optimizer 目录下

优化器
数据库的优化器通常分为两类,一类是基于规则的优化器 RBO(Rule-basd optimizer),一类是基于代价的优化 CBO(Cost-based optimizer),
前者完全基于预设的优化规则进行优化,匹配的条件和优化的结果都比较固定;
后者则会根据收集的数据统计信息计算不同执行计划的执行代价,尽量选择代价最小的执行计划。

目前 Nebula Graph 主要实现的是 RBO

src/graph/optimizer/rule 目录则是预设的规则集,其他的源文件则是优化器的具体实现。

优化器优化查询的入口则在 src/graph/service/QueryInstance.cpp 文件中

Status QueryInstance::validateAndOptimize() {
  auto *rctx = qctx()->rctx();
  auto &spaceName = rctx->session()->space().name;
  VLOG(1) << "Parsing query: " << rctx->query();
  // Result of parsing, get the parsing tree
  auto result = GQLParser(qctx()).parse(rctx->query());
  NG_RETURN_IF_ERROR(result);
  sentence_ = std::move(result).value();
  if (sentence_->kind() == Sentence::Kind::kSequential) {
    size_t num = static_cast<const SequentialSentences *>(sentence_.get())->numSentences();
    stats::StatsManager::addValue(kNumSentences, num);
    if (FLAGS_enable_space_level_metrics && spaceName != "") {
      stats::StatsManager::addValue(
          stats::StatsManager::counterWithLabels(kNumSentences, {{"space", spaceName}}), num);
    }
  } else {
    stats::StatsManager::addValue(kNumSentences);
    if (FLAGS_enable_space_level_metrics && spaceName != "") {
      stats::StatsManager::addValue(
          stats::StatsManager::counterWithLabels(kNumSentences, {{"space", spaceName}}));
    }
  }
 
  // Validate the query, if failed, return
  NG_RETURN_IF_ERROR(Validator::validate(sentence_.get(), qctx()));
  // Optimize the query, and get the execution plan
  NG_RETURN_IF_ERROR(findBestPlan());
  stats::StatsManager::addValue(kOptimizerLatencyUs, *(qctx_->plan()->optimizeTimeInUs()));
  if (FLAGS_enable_space_level_metrics && spaceName != "") {
    stats::StatsManager::addValue(
        stats::StatsManager::histoWithLabels(kOptimizerLatencyUs, {{"space", spaceName}}));
  }
 
  return Status::OK();
}

2.4、executor

调度器的源码在 src/graph/scheduler 目录下

在执行阶段,执行引擎通过 Scheduler(调度器)将 Planner 生成的物理执行计划转换为一系列 Executor,驱动 Executor 的执行。

Scheduler 抽象类定义了调度器的公共接口,可以继承该类实现多种调度器。

目前实现了 AsyncMsgNotifyBasedScheduler 调度器,它基于异步消息通信与广度优先搜索避免栈溢出。

执行器的源码在 src/graph/executor 目录下

Executor,即执行器,物理执行计划中的每个 PlanNode 都会对应一个 Executor。

首先,调度器从执行计划的根节点开始通过使用广度优先搜索算法遍历整个执行计划并根据节点间的执行依赖关系,构建它们的消息通知机制。

执行时,每个节点收到它的所依赖的节点全部执行完毕的消息后,会被调度执行。一旦自身执行完成,又会发送消息给依赖自己的节点,直至整个计划执行完毕。

每个 Executor 会经历 create-open-execute-close 四个阶段:

  • create: 根据节点类型生成对应的 Executor
  • open: 在 Executor 正式执行前会做一些初始化操作,以及慢查询终止和内存水位的判断
  • execute: Query 类型的 Executor 的输入和输出都是一张表(DataSet)。Executor 的执行基于迭代器模型:每次计算时,调用输入表的迭代器的 next() 方法,获取一行数据,进行计算,直至输入表被遍历完毕。计算的结果构成一张新表,输出给后续的 Executor 作为输出。
  • close: Executor 执行完毕后,将收集到的一些执行信息如执行时间,输出表的行数等添加到 profiling stats 中。 用户可以在 profile 一个语句后显示的执行计划中查看这些统计信息。

3、Meta流程

3.1、对外接口

MetaService:

service MetaService {
    ExecResp createSpace(1: CreateSpaceReq req);
    ExecResp dropSpace(1: DropSpaceReq req);
    ExecResp clearSpace(1: ClearSpaceReq req);
    GetSpaceResp getSpace(1: GetSpaceReq req);
    ListSpacesResp listSpaces(1: ListSpacesReq req);
    ExecResp alterSpace(1: AlterSpaceReq req);
 
    ExecResp createSpaceAs(1: CreateSpaceAsReq req);
 
    ExecResp createTag(1: CreateTagReq req);
    ExecResp alterTag(1: AlterTagReq req);
    ExecResp dropTag(1: DropTagReq req);
    GetTagResp getTag(1: GetTagReq req);
    ListTagsResp listTags(1: ListTagsReq req);
 
    ExecResp createEdge(1: CreateEdgeReq req);
    ExecResp alterEdge(1: AlterEdgeReq req);
    ExecResp dropEdge(1: DropEdgeReq req);
    GetEdgeResp getEdge(1: GetEdgeReq req);
    ListEdgesResp listEdges(1: ListEdgesReq req);
 
    ExecResp       addHosts(1: AddHostsReq req);
    ExecResp       addHostsIntoZone(1: AddHostsIntoZoneReq req);
    ExecResp       dropHosts(1: DropHostsReq req);
    ListHostsResp  listHosts(1: ListHostsReq req);
 
    GetPartsAllocResp getPartsAlloc(1: GetPartsAllocReq req);
    ListPartsResp listParts(1: ListPartsReq req);
 
    GetWorkerIdResp getWorkerId(1: GetWorkerIdReq req);
 
    ExecResp             createTagIndex(1: CreateTagIndexReq req);
    ExecResp             dropTagIndex(1: DropTagIndexReq req );
    GetTagIndexResp      getTagIndex(1: GetTagIndexReq req);
    ListTagIndexesResp   listTagIndexes(1:ListTagIndexesReq req);
    ExecResp             rebuildTagIndex(1: RebuildIndexReq req);
    ListIndexStatusResp  listTagIndexStatus(1: ListIndexStatusReq req);
    ExecResp             createEdgeIndex(1: CreateEdgeIndexReq req);
    ExecResp             dropEdgeIndex(1: DropEdgeIndexReq req );
    GetEdgeIndexResp     getEdgeIndex(1: GetEdgeIndexReq req);
    ListEdgeIndexesResp  listEdgeIndexes(1: ListEdgeIndexesReq req);
    ExecResp             rebuildEdgeIndex(1: RebuildIndexReq req);
    ListIndexStatusResp  listEdgeIndexStatus(1: ListIndexStatusReq req);
 
    ExecResp createUser(1: CreateUserReq req);
    ExecResp dropUser(1: DropUserReq req);
    ExecResp alterUser(1: AlterUserReq req);
    ExecResp grantRole(1: GrantRoleReq req);
    ExecResp revokeRole(1: RevokeRoleReq req);
    ListUsersResp listUsers(1: ListUsersReq req);
    ListRolesResp listRoles(1: ListRolesReq req);
    ListRolesResp getUserRoles(1: GetUserRolesReq req);
    ExecResp changePassword(1: ChangePasswordReq req);
 
    HBResp           heartBeat(1: HBReq req);
    AgentHBResp  agentHeartbeat(1: AgentHBReq req);
 
    ExecResp regConfig(1: RegConfigReq req);
    GetConfigResp getConfig(1: GetConfigReq req);
    ExecResp setConfig(1: SetConfigReq req);
    ListConfigsResp listConfigs(1: ListConfigsReq req);
 
    ExecResp createSnapshot(1: CreateSnapshotReq req);
    ExecResp dropSnapshot(1: DropSnapshotReq req);
    ListSnapshotsResp listSnapshots(1: ListSnapshotsReq req);
 
    AdminJobResp runAdminJob(1: AdminJobReq req);
 
    ExecResp       mergeZone(1: MergeZoneReq req);
    ExecResp       dropZone(1: DropZoneReq req);
    ExecResp       divideZone(1: DivideZoneReq req);
    ExecResp       renameZone(1: RenameZoneReq req);
    GetZoneResp    getZone(1: GetZoneReq req);
    ListZonesResp  listZones(1: ListZonesReq req);
 
    ExecResp       addListener(1: AddListenerReq req);
    ExecResp       removeListener(1: RemoveListenerReq req);
    ListListenerResp listListener(1: ListListenerReq req);
 
    GetStatsResp  getStats(1: GetStatsReq req);
    ExecResp signInService(1: SignInServiceReq req);
    ExecResp signOutService(1: SignOutServiceReq req);
    ListServiceClientsResp listServiceClients(1: ListServiceClientsReq req);
 
    ExecResp createFTIndex(1: CreateFTIndexReq req);
    ExecResp dropFTIndex(1: DropFTIndexReq req);
    ListFTIndexesResp listFTIndexes(1: ListFTIndexesReq req);
 
    CreateSessionResp createSession(1: CreateSessionReq req);
    UpdateSessionsResp updateSessions(1: UpdateSessionsReq req);
    ListSessionsResp listSessions(1: ListSessionsReq req);
    GetSessionResp getSession(1: GetSessionReq req);
    ExecResp removeSession(1: RemoveSessionReq req);
    ExecResp killQuery(1: KillQueryReq req);
 
    ExecResp reportTaskFinish(1: ReportTaskReq req);
 
    // Interfaces for backup and restore
    CreateBackupResp createBackup(1: CreateBackupReq req);
    ExecResp       restoreMeta(1: RestoreMetaReq req);
    ListClusterInfoResp listCluster(1: ListClusterInfoReq req);
    GetMetaDirInfoResp getMetaDirInfo(1: GetMetaDirInfoReq req);
 
    VerifyClientVersionResp verifyClientVersion(1: VerifyClientVersionReq req)
    SaveGraphVersionResp saveGraphVersion(1: SaveGraphVersionReq req)
 
    GetSegmentIdResp getSegmentId(1: GetSegmentIdReq req);
}

4、Storage流程

storaged分三层:

  • Storage interface:Storage 服务的最上层,定义了一系列和图相关的 API。API 请求会在这一层被翻译成一组针对分片的 KV 操作,例如:getNeighbors :查询一批点的出边或者入边,返回边以及对应的属性,并且支持条件过滤。insert vertex/edge :插入一条点或者边及其属性。
  • Consensus:Storage 服务的中间层,实现了 Multi Group Raft
  • Store Engine :是一个单机版本地存储引擎,提供对本地数据的 get 、 put 、 scan 等操作。相关接口存储在 KVStore.h 和 KVEngine.h 文件

4.1、对外接口

storage.thrift:

service GraphStorageService {
    GetNeighborsResponse getNeighbors(1: GetNeighborsRequest req)
 
    // Get vertex or edge properties
    GetPropResponse getProps(1: GetPropRequest req);
 
    ExecResponse addVertices(1: AddVerticesRequest req);
    ExecResponse addEdges(1: AddEdgesRequest req);
 
    ExecResponse deleteEdges(1: DeleteEdgesRequest req);
    ExecResponse deleteVertices(1: DeleteVerticesRequest req);
    ExecResponse deleteTags(1: DeleteTagsRequest req);
 
    UpdateResponse updateVertex(1: UpdateVertexRequest req);
    UpdateResponse updateEdge(1: UpdateEdgeRequest req);
 
    ScanResponse scanVertex(1: ScanVertexRequest req)
    ScanResponse scanEdge(1: ScanEdgeRequest req)
 
    GetUUIDResp getUUID(1: GetUUIDReq req);
 
    // Interfaces for edge and vertex index scan
    LookupIndexResp lookupIndex(1: LookupIndexRequest req);
 
    GetNeighborsResponse lookupAndTraverse(1: LookupAndTraverseRequest req);
 
    UpdateResponse chainUpdateEdge(1: UpdateEdgeRequest req);
    ExecResponse chainAddEdges(1: AddEdgesRequest req);
    ExecResponse chainDeleteEdges(1: DeleteEdgesRequest req);
 
    KVGetResponse   get(1: KVGetRequest req);
    ExecResponse    put(1: KVPutRequest req);
    ExecResponse    remove(1: KVRemoveRequest req);
}
posted @ 2023-01-13 15:05  koko1996  阅读(388)  评论(0)    收藏  举报