在 ETCD 源码学习过程,不会讲解太多的源码知识,只讲解相关的实现机制,需要关注源码细节的朋友可以自行根据文章中的提示,找到相关源码进行学习。
主要文件
/etcdserver/api/v3rpc/watch.go watch 服务端实现
/mvcc/watcher.go 主要封装 watchStream 的实现
/mvcc/watchable_store.go watch 版本的 KV 存储实现
/mvcc/watchable_store_txn.go 主要实现事务提交后 End() 函数的处理
/mvcc/watcher_group.go
主要流程
主要数据结构
//文件:/etcdserver/api/v3rpc/watch.go
type watchServer struct {
...
watchable mvcc.WatchableKV // 键值存储
....
}
type serverWatchStream struct {
...
watchable mvcc.WatchableKV //kv 存储
...
gRPCStream pb.Watch_WatchServer //与客户端进行连接的 Stream
watchStream mvcc.WatchStream //key 变动的消息管道
ctrlStream chan *pb.WatchResponse //响应客户端请求的消息管道
...
progress map[mvcc.WatchID]bool //该类型的 watch,服务端会定时发送类似心跳消息
prevKV map[mvcc.WatchID]bool //该类型表明,对于/a/b 这样的监听范围, 如果 b 变化了, 前缀/a也需要通知
fragment map[mvcc.WatchID]bool //该类型表明,传输数据量大于阈值,需要拆分发送
}
//文件:/mvcc/watcher.go
//响应结构体
type WatchResponse struct {
WatchID WatchID
Revision int64 //当前 watchResponse 实例创建时对应的 revision 值
CompactRevision int64 //压缩操作对应的 revison
}
type watchStream struct {
watchable watchable //用来记录关联的 watchableStore
ch chan WatchResponse // event 事件写入通道
...
cancels map[WatchID]cancelFunc
watchers map[WatchID]*watcher //用来记录唯一标识与 watcher 的实例的关系
}
//文件:/mvcc/watcher_group.go
type eventBatch struct {
//其中记录的 Event 实例是按照 revision 排序的
evs []mvccpb.Event
//记录当前 eventBatch 中,有多少个来自不同的 main revison,
revs int
//当前 eventBatch 记录的 Event 个数达到上限之后,后续 Event 实例无法加入该 eventBatch 中
//该字段记录了无法加入该 eventBatch 实例的第一个main revision
moreRev int64
}
type watcherBatch map[*watcher]*eventBatch
type watcherSet map[*watcher]struct{}
type watcherSetByKey map[string]watcherSet
type watcherGroup struct {
keyWatchers watcherSetByKey //记录监听单个 Key 的 watch 实例
ranges adt.IntervalTree //记录进行范围监听的 watcher 实例。
watchers watcherSet //记录当前 wathcer 的全部实例
}
//文件:/mvcc/watchable_store.go
type watchableStore struct {
*store
mu sync.RWMutex
victims []watcherBatch //当ch被阻塞时,对应 watcherBatch 实例会暂时记录到这个字段
victimc chan struct{} //当有新的 watcherBatch 实例添加到 victims 字段时,会向该通道发送消息
//未同步的 watcher
unsynced watcherGroup
//已完成同步的 watcher
synced watcherGroup
stopc chan struct{}
wg sync.WaitGroup
}
type watcher struct {
key []byte //监听起始值
end []byte // 监听终止值, key 和 end 共同组成一个键值范围
victim bool //是否被阻塞
compacted bool //是否压缩
...
minRev int64 //最小的 revision main
id WatchID
...
ch chan<- WatchResponse
}
主要函数
1.函数:Watch
(1)创建 serverWatchStream 实例。
(2)启动 serverWatchStream.sendLoop 后台 goroutine。
(3)启动 serverWatchStream.recvLoop 后台 goroutine。
(4)等待关闭 创建 serverWatchStream。
func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
sws := serverWatchStream{
....
}
sws.wg.Add(1)
go func() {
sws.sendLoop()
sws.wg.Done()
}()
errc := make(chan error, 1)
go func() {
if rerr := sws.recvLoop(); rerr != nil {
...
}
}()
// ... 等待关闭
return err
}
2.函数:recvLoop
(1)接收客户端请求。
(2)根据请求类型处理。
func (sws *serverWatchStream) recvLoop() error {
for {
req, err := sws.gRPCStream.Recv()
...
switch uv := req.RequestUnion.(type) {
case *pb.WatchRequest_CreateRequest: //创建
if uv.CreateRequest == nil {
break
}
...
if !sws.isWatchPermitted(creq) { //权限判断
...
}
filters := FiltersFromRequest(creq)
//获取版本号
wsrev := sws.watchStream.Rev()
rev := creq.StartRevision
if rev == 0 {
rev = wsrev + 1
}
id, err := sws.watchStream.Watch(mvcc.WatchID(creq.WatchId), creq.Key, creq.RangeEnd, rev, filters...)
//... 根据监听结果做一些出来
case *pb.WatchRequest_CancelRequest:
//主要是删除相关数据
case *pb.WatchRequest_ProgressRequest:
//发送一个 Progress Response 给客户端
default:
continue
}
}
}
3.函数:sendLoop
(1)处理 watchStream 的事件(key 变动消息)。
(2)处理 ctrlStream 的消息(客户端请求,返回响应)。
(3)定时发送 RequestProgress 类似心跳包。
func (sws *serverWatchStream) sendLoop() {
ids := make(map[mvcc.WatchID]struct{})
pending := make(map[mvcc.WatchID][]*pb.WatchResponse)
//发送 Progress 的间隔时间
interval := GetProgressReportInterval()
progressTicker := time.NewTicker(interval)
//... defer 处理
for {
select {
/*
watchStream 监听的 key 有相关修改
该消息会通过 watchableStore.notify() 发送
即在每个 key 进行事务提交的时候 默认执行 watchable_store_txn.End()
End 调用 notify 进行通知
*/
case wresp, ok := <-sws.watchStream.Chan():
...
evs := wresp.Events
events := make([]*mvccpb.Event, len(evs))
for i := range evs {
events[i] = &evs[i]
if needPrevKV { //如果设置了 PrevKv,
...
}
}
//构建响应
wr := &pb.WatchResponse{
...
}
...
var serr error
//是否需要拆分发送
if !fragmented && !ok {
serr = sws.gRPCStream.Send(wr)
} else {
serr = sendFragments(wr, sws.maxRequestBytes, sws.gRPCStream.Send)
}
...
case c, ok := <-sws.ctrlStream:
...
case <-progressTicker.C:
...
case <-sws.closec:
return
}
}
}
4.watchStream.Watch
func (ws *watchStream) Watch(id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error) {
//防止 key>end 的情况
if len(end) != 0 && bytes.Compare(key, end) != -1 {
return -1, ErrEmptyWatcherRange
}
//生成 id(WatchId)
w, c := ws.watchable.watch(key, end, startRev, id, ws.ch, fcs...)
ws.cancels[id] = c
ws.watchers[id] = w
return id, nil
}
5.watchableStore.watch
func (s *watchableStore) watch(key, end []byte, startRev int64, id WatchID, ch chan<- WatchResponse, fcs ...FilterFunc) (*watcher, cancelFunc) {
wa := &watcher{
...
}
//比较 startRev 和 currentRev,决定添加的 watcher 实例是否已经同步
synced := startRev > s.store.currentRev || startRev == 0
if synced {
wa.minRev = s.store.currentRev + 1
if startRev > wa.minRev { //设置待添加 watcher 的 minRev 字段值
wa.minRev = startRev
}
}
if synced { //已同步完成
s.synced.add(wa)
} else { //未同步完成
slowWatcherGauge.Inc()
s.unsynced.add(wa)
}
...
}
在实例化 watchableStore 是会开启两个 goroutine, syncWatchersLoop 和 syncVictimsLoop 用于处理 unsynced 消息和 victims。
6.syncWatchersLoop
func (s *watchableStore) syncWatchersLoop() {
defer s.wg.Done()
for {
...
if lastUnsyncedWatchers > 0 {//如果 unsynced 中存在数据,进行同步
//存在需要同步的 watcher
unsyncedWatchers = s.syncWatchers()
}
...
select {
case <-time.After(waitDuration):
case <-s.stopc:
return
}
}
}
7.syncWatchers
(1)从 unsynced 中选择一批 watcher 进行实例,并获取这批实例中最小的版本号 minRev。
(2)从 KV 中查找 minRev - currRev 之间所有的数据。
(3)将这些数据转换为 events。
(4)将这些 events 广播给所有 watcher。
func (s *watchableStore) syncWatchers() int {
...
curRev := s.store.currentRev // 当前最大值
compactionRev := s.store.compactMainRev
//选择一批 watcher 实例,进行同步,并找到这批 watch 最小的 rev
wg, minRev := s.unsynced.choose(maxWatchersPerSync, curRev, compactionRev)
minBytes, maxBytes := newRevBytes(), newRevBytes()
//... 查找minRev - currentRev 所有的键值
revs, vs := tx.UnsafeRange(keyBucketName, minBytes, maxBytes, 0)
...
var evs []mvccpb.Event
//将 revs 转换为 Event 事件
evs = kvsToEvents(s.store.lg, wg, revs, vs)
var victims watcherBatch
wb := newWatcherBatch(wg, evs)
for w := range wg.watchers { //广播 watcher
w.minRev = curRev + 1 //更新 watcher 中的 minRev 字段,这样就不会被同一个修改操作触发两次
//该 watcher 实例监听的键值没有更新操作(minRev -- currentRev 之间)
//所以没有被触发,故同步完毕,
eb, ok := wb[w]
if !ok {
// bring un-notified watcher to synced
s.synced.add(w)
s.unsynced.delete(w)
continue
}
//更新操作太多,无法全部放入 eventbatch 中,这里 minRev 设置成 moreRev,
//下次处理 unsynced 时,会将其后的更新操作查询出来继续处理
if eb.moreRev != 0 {
w.minRev = eb.moreRev
}
//将前面创建的 Event 事件封装成 WatchResponse,然后写入 watcher.ch 通道中
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: curRev}) {
pendingEventsGauge.Add(float64(len(eb.evs)))
} else { //如果堵塞
if victims == nil {
victims = make(watcherBatch)
}
w.victim = true
}
if w.victim {
victims[w] = eb
} else {
if eb.moreRev != 0 { //继续读
// stay unsynced; more to read
continue
}
s.synced.add(w)
}
s.unsynced.delete(w)
}
//将 victims 变量中记录 watcherBatch 添加到 watchable.victims 中,等待处理
s.addVictim(victims)
...
}
9.syncVictimsLoop
func (s *watchableStore) syncVictimsLoop() {
defer s.wg.Done()
for {
for s.moveVictims() != 0 { //循环处理 victims 中缓存的 watcherBatch 中
// try to update all victim watchers
}
...
var tickc <-chan time.Time
select {
case <-tickc:
case <-s.victimc:
case <-s.stopc:
return
}
}
}
10.moveVictims
(1)将 victims 中的数据尝试发送出去。
(2)如果发送仍然阻塞,需要重新放回 victims。
(3)判断这些发送完成的版本号是否小于当前版本号,如果是说明者个过程中有数据更新,还没有同步完成,需要添加到 unsynced 中,等待下次同步。如果不是,说明已经同步完成。
func (s *watchableStore) moveVictims() (moved int) {
s.mu.Lock()
victims := s.victims
s.victims = nil
s.mu.Unlock()
var newVictim watcherBatch
for _, wb := range victims {
for w, eb := range wb {
// watcher has observed the store up to, but not including, w.minRev
rev := w.minRev - 1
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) {
pendingEventsGauge.Add(float64(len(eb.evs)))
} else { //如果依然阻塞,重新放回去
if newVictim == nil {
newVictim = make(watcherBatch)
}
newVictim[w] = eb
continue
}
}
// assign completed victim watchers to unsync/sync
s.mu.Lock()
s.store.revMu.RLock()
curRev := s.store.currentRev
for w, eb := range wb { //变量 wb
...
if w.minRev <= curRev {
s.unsynced.add(w) //还有未同步的事件
} else { //同步完成
slowWatcherGauge.Dec()
s.synced.add(w)
}
}
...
}
....
return moved
}
总结
1.当 Watcher 与 服务器建立之后,首先需要进行版本的同步,主要通过 watchableStore 中的 syncVictimsLoop 和 syncWatchersLoop 这两个后台g oroutine 实现。
2.WatchableStoreTxnWrite 重新实现 KV 存储的事务提交回调函数 End(), End 中会调用 watchableStore .notify() 通知 key 的变化。Notify 最终将消息推送的 watchStream.ch 中等待处理。
3.serverWatchStream 会开启两个后台gorotine,recvLoop 和 sendLoop 分别用于接收请求和发送服务消息给客户端。