connection manager
连接管理,接受用户连接。
- 相关文件
main.go
pkg/cli/cli.go
pkg/cli/start.go
pkg/server/server.go
- 函数调用堆栈
|
|
Postgres Wire Protocol
pgwire.v3Conn
结构体接收客户端的连接,身份验证,按照pg通信协议解析语句。
v3Conn.serve()
实现读取SQL,执行SQL,返回结果集
的循环。
-
相关文件
pkg/sql/pgwire/v3.go
-
函数调用堆栈
|
|
SQL Executor
sql.Executor
具体负责解析SQL,执行SQL,返回结果集给pgwire.v3Conn
。
入口函数是Executor.execRequest()
, 接收一批SQL语句,将执行交给sql.Session
进行处理。
Parsing
解析是讲SQL语句根据YACC语法规则解析成抽象语法树(AST)。sql.y文件是从PG直接拷贝过来,做了一定的裁剪,然后又新增了一些语法。
AST node都定义在sql/parser
,分为两类, statements
和expressions
.AST最后会经过planner
转换为执行计划。
-
相关文件
pkg/sql/parser/sql.y
pkg/sql/executor.go
pkg/sql/parser/parse.go
-
函数调用堆栈
|
|
Statement Execution
重点调用runTxnAttempt
函数进入执行阶段,执行里面包含生成执行计划。
-
相关文件
pkg/sql/executor.go
-
函数调用堆栈
|
|
Building execution plans
crdb的执行计划是planNode
节点组成的树形结构,类似AST,但是包含了语义信息和运行状态。计划树是通过planner.makePlan()
生成的,接收经过parsed的语句,返回planNode
树的根节点,这个过程中会进行语义检查以及各种表达式变化。
planner
查看AST根节点从而知道语句类型,根据不同的语句类型,调用不同的计划生成函数。比如SELECT
语句的计划树是通过planner.SelectClause()
生成。
SELECT
计划树包含各种处理逻辑:scanNode
进行全表扫描,filterNode
进行过滤WHERE条件, sortNode
进行ORDER BY操作。
最后计划树会经过简化和优化,比如去掉不必要的节点。
-
相关文件
pkg/sql/executor.go
pkg/sql/plan.go
-
函数调用堆栈
|
|
Expressions
parser.Expr
是AST的子集,表示各种表达式, 出现在WHERE、LIMIT、ORDER BY条件中。所有的表达式都要经过planner.analyzeExpr
处理。每个planNode
负责调用analyzeExpr
分析本节点所包含的表达式。
planner.analyzeExpr
执行如下流程:
resolving names
normalization
type checking
- 使用
sql.subquery
替换sub-query
Notable planNodes
每个planNode
包行两个方法:
Start
: 初始化Next
: 生成下一行数据
renderNode
的start方法调用堆栈
|
|
有两种执行方式:
- executor.execClassic()
- executor.execDistSQL()
execClassic
函数调用堆栈
|
|
KV
crdb kv层处理请求的执行,请求和响应统一使用基于protocol buffer的API,消息格式定义在pkg/roachpb/api.proto
。实际上,KV client发送的是批量请求BatchRequest s, 所有的请求都有一个Header,包含消息的目标以及事务信息。
The KV client interface
KV client包含启动事务的接口starting a (KV) transaction, Txn
是事务上下文结构体。
Txn.Run()
最终调用txn.db.sender.Send(..., batch)
:- 请求最终会传递到需要执行的副本节点。
Send()
最终传递到最底层接口。TxnCoordSender -> DistSender -> Node -> Stores -> Store -> Replica
TxnCoordSender
TxnCoordSender是最上层的client.Sender
. TxnCoordSender负责处理事务状态,一个事务开启后,TxnCoordSender异步发送心跳信号给txn record
, 保持连接, 同时会记录整个事务的所有写入的key。事务提交或回滚时,会清理write intents
。事务中所有的语句都要经过TxnCoordSender, 然后将请求传递给DistSender。
DistSender
DistSender
这一层任务繁重,需要处理gateway节点和range节点之间的通信,接受BatchRequests
,将请求路由到不同的range node,然后从不同的range node接收结果集进行组装。
- DistSender.Send() 调用
DistSender.divideAndSendBatchToRanges()
将请求按照range进行细分。 - 每个细分的请求,调用
DistSender.sendPartialBatchAsync()
将请求发到对应的range所在的node。 - 将细分请求发送到对应range的正确的副本,是通过RPC请求进行的。
sendToReplicas
函数用于初始化RPC请求,并发送请求。- RPC的发送隐藏在
Transport interface
,grpcTransport.SendNext()
进行gRPC的调用。 - 从不同副本返回的响应合并成单独的
BatchResponse
,最终返回给Send()
方法。
RPC server - Node and Stores
RPC服务是通过Node
结构体实现的。Node将请求直接传递给其成员Stores进行处理。
Stores.Send()
根据确定目标副本,并将请求路由到目标副本。
Store - intent resolution
每个store代表着一个物理磁盘设备. Store除了路由请求到目标副本外,还有个重要角色,处理write intents
记录,通过这个达到处理R-W,W-W冲突的目的。
碰到WriteIntentError
后,需要进行try to "resolve"
it,使用
intentResolver
解决冲突.
Replica - executing reads, proposing Raft commands
Replica
代表一个range, Replica是Sender的最后一层,其它上层的Sender最终都是将请求发送给LeaseHolder的Replica, 读请求和写请求处理逻辑不同,读直接返回数据,写请求需要通过raft一致性协议进行。
Read request path
读请求首先会检查请求是否真正发送到了LeaseHolder上,是通过replica.redirectOnOrAcquireLease()
函数进行检查,如果当前replica不是leaseholder,则把请求转给leaseholder,如果当前不存在leaseholder,则会发出申请当前节点为leaseholder的请求。
确定leaseholder后,如果所读的key正在被其它事务写,需要进行同步等待。同步通过
CommandQueue
进行
- 函数调用堆栈
|
|
|
|
Engine
Engine是crdb技术堆栈的最底层,负责物理存储。Engine
是接口层,底部实现目前只有RocksDB
, 是基于RocksDB C库的一个封装,使用了cgo与C进行交互。尽管RocksDB是crdb非常重要的组成部分,crdb并没有去修改RocksDB。
|
|
Write request path
写请求比读更加复杂,开始于replica.addWriteCmd()
.这个函数会不断调用replica.tryAddWriteCmd
. 其主要做如下事情:
- 等待当前其它正在修改的请求结束,将自己放到CommandQueue。
- 调用redirectOnOrAcquireLease检查leaseholder
- 与timestamp cache比较,如果不合格,写的时间戳需要提升。
- 将请求转换为raft command, 调用
replica.propose
,将代码权交给raft机制进行处理 - 等待
replica.propose
返回 - 调用返回,将写请求命令清除出CommandQueue。
Evaluation of requests and application of Raft commands
|
|