etcd 的Put请求处理

在介绍etcdctl的内容中,我们知道了etcdctl实际上是向etcd服务端执行了grpc请求获取对应的结果,这一篇主要介绍当向etcd服务端执行Get/Put指令的时候究竟做了哪些工作。

Client发出请求

Put指令和之前介绍到的Get指令类似,通过grpc client发起请求并带上键值对的参数。在这里实际进行操作的是client类型中的未命名属性KV接口类型完成的

put command 代码:https://github.com/etcd-io/etcd/blob/v3.5.15/etcdctl/ctlv3/command/put_command.go

kv 代码:https://github.com/etcd-io/etcd/blob/v3.5.15/client/v3/kv.go

go 复制代码
type Client struct {
	Cluster
	KV
	Lease
	Watcher
	Auth
	Maintenance
        ...
}

type kv struct {
	remote   pb.KVClient
	callOpts []grpc.CallOption
}

kv 类型会将不同的操作包装成Op类型,由Do函数统一操作,这种命令模式有助于实现操作的解耦,对可能的新增命令有更好的扩展性。 在Do函数中通过对Op对象类型的判断由remote对象执行具体的请求。我们也可以从代码中看到目前etcd一共支持四种操作类型,get并不是对应了Get类型而是Range类型。

go 复制代码
const (
	// A default Op has opType 0, which is invalid.
	tRange opType = iota + 1
	tPut
	tDeleteRange
	tTxn
)

raft请求处理

通过pb.KVClient类型我们可以找到grpc定义的对应的Server类型。顺藤摸瓜在server文件夹下找到了对应的接口服务代码。

https://github.com/etcd-io/etcd/blob/v3.5.15/server/etcdserver/v3_server.go

go 复制代码
func (s *EtcdServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
	ctx = context.WithValue(ctx, traceutil.StartTimeKey{}, time.Now())
	resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{Put: r})
	if err != nil {
		return nil, err
	}
	return resp.(*pb.PutResponse), nil
}

func (s *EtcdServer) raftRequest(ctx context.Context, r pb.InternalRaftRequest) (proto.Message, error) {
	return s.raftRequestOnce(ctx, r)
}

func (s *EtcdServer) raftRequestOnce(ctx context.Context, r pb.InternalRaftRequest) (proto.Message, error) {
	result, err := s.processInternalRaftRequestOnce(ctx, r)
	if err != nil {
		return nil, err
	}
	if result.Err != nil {
		return nil, result.Err
	}
	if startTime, ok := ctx.Value(traceutil.StartTimeKey{}).(time.Time); ok && result.Trace != nil {
		applyStart := result.Trace.GetStartTime()
		// The trace object is created in toApply. Here reset the start time to trace
		// the raft request time by the difference between the request start time
		// and toApply start time
		result.Trace.SetStartTime(startTime)
		result.Trace.InsertStep(0, applyStart, "process raft request")
		result.Trace.LogIfLong(traceThreshold)
	}
	return result.Resp, nil
}

因此对请求的处理是:

  1. 将操作封装成raftRequest
  2. processInternalRaftRequestOnce方法里,将raftRequest提交给raft node进行处理。这时会检查一下apply index和commint index的gap,从代码上看就是先apply, 然后再commit。 后面会对权限做一些校验,通过后会将raftRequest序列化由raft node处理。这部分代码在https://github.com/etcd-io/raft 里实现。
go 复制代码
func (n *node) Propose(ctx context.Context, data []byte) error {
	return n.stepWait(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Data: data}}})
}
  1. stepWithWaitOption 方法中将消息扔给node的channel处理并等待结果, 可以看看node的这个channel propc 的接收者是如何处理的。
go 复制代码
    select {
    // TODO: maybe buffer the config propose if there exists one (the way
    // described in raft dissertation)
    // Currently it is dropped in Step silently.
    case pm := <-propc:
            m := pm.m
            m.From = r.id
            err := r.Step(m)
            if pm.result != nil {
                    pm.result <- err
                    close(pm.result)
            }
    }

Step方法中对不同类型的message进行了处理, 这里应该会有一些raft共识相关的信息类型, 但是对于前面生成的MsgProp消息,调用了raft.step方法,raft.step 是函数类型type stepFunc func(r *raft, m pb.Message) error, 在node成为leader, follower, 或是candidate的时候确定的。

go 复制代码
	func (r *raft) Step(m pb.Message) error {
	// Handle the message term, which may result in our stepping down to a follower.
	...
	switch m.Type {
	case pb.MsgHup:
            ...

	case pb.MsgStorageAppendResp:
            ...

	case pb.MsgStorageApplyResp:
            ...

	case pb.MsgVote, pb.MsgPreVote:
            ...

	default:
		err := r.step(r, m)
		if err != nil {
			return err
		}
	}
	return nil
}

如果node是leader的话,raft.step 对应stepLeader,会根据entries记录raftlog日志,将msg添加到node的msgs切片中,并通知其他节点当前的提交位置。而如果是candidate则会记录没有leader的错误日志并返回error。

go 复制代码
// 代码中对msgs的描述

// msgs contains the list of messages that should be sent out immediately to
// other nodes.
//
// Messages in this list must target other nodes.
msgs []pb.Message
  1. 最终会新创建Ready对象, 而相关的entries会被写入到存储中。 这里存储在内存中和数据库文件中是如何刷入的,后面有机会在看。

    if err := r.storage.Save(rd.HardState, rd.Entries); err != nil {
    r.lg.Fatal("failed to save Raft hard state and entries", zap.Error(err))
    }

相关推荐
蓝速科技8 分钟前
信创终端 POC 测试实战与选型避坑指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理
风哥2号23 分钟前
数据库教程FGMT29‑Linux平台MySQL5.7安装配置与管理入门
数据库
杨云龙UP1 小时前
MySQL Host is blocked because of many connection errors 导致 JDBC 连接失败排查与解决
linux·运维·网络·数据库·sql·mysql·登录失败
这个DBA有点耶2 小时前
数据库数据同步解决方案怎么选?6款主流工具横向对比与信创选型指南
数据库·架构·dba
刃神太酷啦3 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
小雷信息医学3 小时前
不会写复杂代码也能发 SCI?手把手教你用 InSpireR 交互系统一键提取、合并与导出临床科研宽表【第三章】
数据库
旺仔不是程序员3 小时前
复合索引最左前缀原则:PostgreSQL 的 WHERE 为什么必须命中第一列
数据库·后端·sql
旺仔不是程序员3 小时前
字段类型不一致:PostgreSQL 报错与索引失效的第一元凶
数据库·后端·sql
白远山3 小时前
货运跑腿搬家平台开发实战:从需求分析到落地部署指南
数据库·数据挖掘·需求分析
geovindu4 小时前
sql: Data Modeling Patterns
数据库·sql·设计模式·sqlserver