1. DGraph 源码阅读 (1) - 架构简介
  2. DGraph 源码阅读 (2) - Raft
  3. DGraph 源码阅读 (3) - Mutation
  4. DGraph 源码阅读 (4) - Query

这篇文章是学习 DGraph 的第四篇(也是最后一篇),主要记录一下 DGraph 的读操作逻辑。

相对于 DGraph 的写操作,读操作比较简单。其中要注意的就是在执行 Query 的时候,怎么将 Query Dispatch 到其他 Alpha 节点上,以及怎么保证 ACID。首先我们来看看整个 Query 的执行流程。

读操作详细流程

读操作的入口函数是 Query,这个方法会被 Http 和 GRPC 服务调用。接下来是整个函数的详细流程。

  1. 解析请求,这一步主要是将 JSON 或者 GraphQL+- 格式的请求转化为 Gql。注意这个 Gql 中包含了 Query 和 Vars。关于 Vars 的详情,请查看 DGraph 的文档。它的主要作用是一个中间变量,方便你做类似 Join 的查询。
  2. 申请一个 Timestamp。注意这个 Timestamp 很重要,DGraph 用它来保证 Linearizable。
  3. 调用 Process 函数,这个函数又会调用 ProcessQuery 函数,这个函数是 Query 的核心逻辑。
  4. 等上一步返回之后,会把结果中的 Subgraph 转成 JSON 返回。

ProcessQuery 逻辑

  1. 遍历 QueryRequest 中 Query 字段,调用 ToSubGraph来生成对应的 SubGraph。注意这个函数会递归的调用嵌套的边,生成子 SubGraph。

  2. 给 SubGraph 设置 ReadTs (之前申请的 Timstamp),并添加到一个数组中。

  3. 循环遍历这个 SubGraph,调用 ProcessGraph 来执行可以执行的(所有依赖的 Var 都已经计算好)。不断重复这一步直到所有的 SubGraph 都执行完。ProcessGraph 的主要逻辑包括:

    1. 调用 createTaskQuery。这个方法就是生成一个对应的 Protobuf 格式的 Query。

    2. 调用 ProcessTaskOverNetwork来执行 query。和写操作一样,如果发现这个 Query 中包含的 Predicate 被自己所属的 Group 处理,则直接调用 processTask,否则发送一个网络请求。processTask会实际读取 Badger 来获得需要的数据,具体代码见 helpProcessTask

Source Function 的作用

Source Function 就是 Query 中最外层传入的 func 参数。DGraph 对于 Source Function 做了一些限制,比如不能用 and 等连接符。这是因为 Source Function 会直接转化为读 Badger 的 Query,而 Badger 作为一个 KV,查询能力比较有限。除了 Source Function 之外,其他的 Filter 都是做 Post Filtering,并不会减少读取的数据量。

Linearizable 保证

就像在最开始说的一样,DGraph 使用申请到的 Timestamp 来保证 Linearizability。具体的做法就是在 processTask中,会阻塞等待,直到超过请求中指定的 ReadTs。这也就意味着,在发起 Query 之前的所有操作都已经 Commit 了。而在从 Badger 读取的过程中,会读取所有在 ReadTs 之前提交的事务造成的改动。

总结一下

读操作的代码相对来说比较简单,我个人觉得最重要的部分包括:

  1. 对于 Var 的处理
  2. 保证 Linearizable

使用了一个全局一致的 Timestamp 来保证线性一致性,我觉得比较好理解。但是我没想明白的是,用于有阻塞等待的存在,是不是相当于所有的请求都必须等待之前的请求完成?这会不会造成系统并发的瓶颈?