在上一篇本地启动etcd之后,通过etcdctl命令来进行相关操作。这一篇记录一下etcdctl是如何实现的。
在源码中很容易找到相关目录:https://github.com/etcd-io/etcd/tree/main/etcdctl
目录下的README
也有对这个命令行工具的相关介绍,不过我暂时比较感兴趣的是命令行是如何跟服务端进行交互的,因此现在先不对每个命令一个个都看一遍, 还是以基础的 GET/PUT 入手。
从目录下的main.go
文件看进去, 命令行的实现都在ctlv3
这个文件夹中,文件夹里的ctl.go
是命令行工具的初始化,而具体命令的实现在command文件夹下。
ctl.go 初始化命令行工具
etcdctl 使用cobra来实现命令行工具, 在init函数中解析参数并添加了支持的具体命令。
func init() {
rootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379"}, "gRPC endpoints")
rootCmd.PersistentFlags().BoolVar(&globalFlags.Debug, "debug", false, "enable client-side debug logging")
......
rootCmd.AddCommand(
command.NewGetCommand(),
command.NewPutCommand(),
command.NewDelCommand(),
command.NewTxnCommand(),
......
)
}
在执行函数中加载了命令行帮助模板并执行命令。
func usageFunc(c *cobra.Command) error {
return cobrautl.UsageFunc(c, version.Version, version.APIVersion)
}
func Start() error {
rootCmd.SetUsageFunc(usageFunc)
// Make help just show the usage
rootCmd.SetHelpTemplate(`{{.UsageString}}`)
return rootCmd.Execute()
}
command 的实现
GetCommand
在command/get_command.go
中定义了Get命令的实现,包括命令参数的解析和具体的执行逻辑函数。
cmd := &cobra.Command{
Use: "get [options] <key> [range_end]",
Short: "Gets the key or a range of keys",
Run: getCommandFunc,
}
在getCommandFunc
中先通过getGetOp
解析命令, 得到查询的key
和命令的相关选项,每个操作选项都被封装成一个函数类型, 传入参数为Op的指针并在函数内部对Op的属性进行修改。
// OpOption configures Operations like Get, Put, Delete.
type OpOption func(*Op)
// Op represents an Operation that kv can execute.
type Op struct {
t opType
key []byte
end []byte
// for range
limit int64
sort *SortOption
serializable bool
keysOnly bool
countOnly bool
minModRev int64
maxModRev int64
minCreateRev int64
maxCreateRev int64
// for range, watch
rev int64
// for watch, put, delete
prevKV bool
// for watch
// fragmentation should be disabled by default
// if true, split watch events when total exceeds
// "--max-request-bytes" flag value + 512-byte
fragment bool
// for put
ignoreValue bool
ignoreLease bool
// progressNotify is for progress updates.
progressNotify bool
// createdNotify is for created event
createdNotify bool
// filters for watchers
filterPut bool
filterDelete bool
// for put
val []byte
leaseID LeaseID
// txn
cmps []Cmp
thenOps []Op
elseOps []Op
isOptsWithFromKey bool
isOptsWithPrefix bool
}
得到key 和 Options的数组后, 会创建grpc client来处理grpc请求, 并通过display将请求结果打印出来。
// getCommandFunc executes the "get" command.
func getCommandFunc(cmd *cobra.Command, args []string) {
key, opts := getGetOp(args)
ctx, cancel := commandCtx(cmd)
resp, err := mustClientFromCmd(cmd).Get(ctx, key, opts...)
cancel()
...
display.Get(*resp)
}
针对command
文件夹下的其他命令也都是大概差不多的流程:解析命令->grpc请求->展示结果
,主要的实现就是通过cobra
构建的命令行工具和对应的grpc
请求。后面会看一下在服务端, PUT和GET请求究竟是如何做的, 既然GET查询支持排序,键值的内容是否通过什么索引结构进行存储的。