作者:@daemon365
本文为作者原创,转载请注明出处:https://www.cnblogs.com/daemon365/p/18240641
介绍
在 etcd 中,watch 是一个非常重要的特性,它可以让客户端监控 etcd 中的 key 或者一组 key,当 key 发生变化时,etcd 会通知客户端。本文将介绍 etcd watch 的实现原理。
| etcdctl watch /test |
| |
| PUT |
| /test |
| a |
| PUT |
| /test |
| b |
| DELETE |
| /test |
watch 的 api
etcd watch api 是由 grpc stream 实现的,客户端通过 grpc stream 发送 watch 请求,etcd 会将 key 的变化通过 stream 返回给客户端。
| rpc Watch(stream WatchRequest) returns (stream WatchResponse) { |
| option (google.api.http) = { |
| post: "/v3/watch" |
| body: "*" |
| }; |
| } |
api 实现
| func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) { |
| sws := serverWatchStream{ |
| lg: ws.lg, |
| |
| clusterID: ws.clusterID, |
| memberID: ws.memberID, |
| |
| maxRequestBytes: ws.maxRequestBytes, |
| |
| sg: ws.sg, |
| watchable: ws.watchable, |
| ag: ws.ag, |
| |
| gRPCStream: stream, |
| watchStream: ws.watchable.NewWatchStream(), |
| |
| ctrlStream: make(chan *pb.WatchResponse, ctrlStreamBufLen), |
| |
| progress: make(map[mvcc.WatchID]bool), |
| prevKV: make(map[mvcc.WatchID]bool), |
| fragment: make(map[mvcc.WatchID]bool), |
| |
| closec: make(chan struct{}), |
| } |
| |
| sws.wg.Add(1) |
| go func() { |
| |
| sws.sendLoop() |
| sws.wg.Done() |
| }() |
| |
| errc := make(chan error, 1) |
| |
| go func() { |
| |
| if rerr := sws.recvLoop(); rerr != nil { |
| if isClientCtxErr(stream.Context().Err(), rerr) { |
| sws.lg.Debug("failed to receive watch request from gRPC stream", zap.Error(rerr)) |
| } else { |
| sws.lg.Warn("failed to receive watch request from gRPC stream", zap.Error(rerr)) |
| streamFailures.WithLabelValues("receive", "watch").Inc() |
| } |
| errc <- rerr |
| } |
| }() |
| |
| |
| select { |
| case err = <-errc: |
| if err == context.Canceled { |
| err = rpctypes.ErrGRPCWatchCanceled |
| } |
| close(sws.ctrlStream) |
| case <-stream.Context().Done(): |
| err = stream.Context().Err() |
| if err == context.Canceled { |
| err = rpctypes.ErrGRPCWatchCanceled |
| } |
| } |
| |
| sws.close() |
| return err |
| } |
| |
这里 主要的逻辑是开启两个 goroutine,一个用于处理客户端发送的 watch 请求,另一个用于处理新的 event 然后发送给客户端。
sendLoop
| func (sws *serverWatchStream) sendLoop() { |
| |
| ids := make(map[mvcc.WatchID]struct{}) |
| |
| pending := make(map[mvcc.WatchID][]*pb.WatchResponse) |
| |
| interval := GetProgressReportInterval() |
| progressTicker := time.NewTicker(interval) |
| |
| defer func() { |
| progressTicker.Stop() |
| |
| for ws := range sws.watchStream.Chan() { |
| mvcc.ReportEventReceived(len(ws.Events)) |
| } |
| for _, wrs := range pending { |
| for _, ws := range wrs { |
| mvcc.ReportEventReceived(len(ws.Events)) |
| } |
| } |
| }() |
| |
| for { |
| select { |
| case wresp, ok := <-sws.watchStream.Chan(): |
| |
| |
| if !ok { |
| return |
| } |
| |
| evs := wresp.Events |
| events := make([]*mvccpb.Event, len(evs)) |
| sws.mu.RLock() |
| needPrevKV := sws.prevKV[wresp.WatchID] |
| sws.mu.RUnlock() |
| for i := range evs { |
| events[i] = &evs[i] |
| if needPrevKV && !IsCreateEvent(evs[i]) { |
| opt := mvcc.RangeOptions{Rev: evs[i].Kv.ModRevision - 1} |
| r, err := sws.watchable.Range(context.TODO(), evs[i].Kv.Key, nil, opt) |
| if err == nil && len(r.KVs) != 0 { |
| events[i].PrevKv = &(r.KVs[0]) |
| } |
| } |
| } |
| |
| canceled := wresp.CompactRevision != 0 |
| wr := &pb.WatchResponse{ |
| Header: sws.newResponseHeader(wresp.Revision), |
| WatchId: int64(wresp.WatchID), |
| Events: events, |
| CompactRevision: wresp.CompactRevision, |
| Canceled: canceled, |
| } |
| |
| |
| |
| if wresp.WatchID != clientv3.InvalidWatchID { |
| if _, okID := ids[wresp.WatchID]; !okID { |
| |
| wrs := append(pending[wresp.WatchID], wr) |
| pending[wresp.WatchID] = wrs |
| continue |
| } |
| } |
| |
| mvcc.ReportEventReceived(len(evs)) |
| |
| sws.mu.RLock() |
| fragmented, ok := sws.fragment[wresp.WatchID] |
| sws.mu.RUnlock() |
| |
| var serr error |
| |
| if !fragmented && !ok { |
| serr = sws.gRPCStream.Send(wr) |
| } else { |
| serr = sendFragments(wr, sws.maxRequestBytes, sws.gRPCStream.Send) |
| } |
| |
| if serr != nil { |
| if isClientCtxErr(sws.gRPCStream.Context().Err(), serr) { |
| sws.lg.Debug("failed to send watch response to gRPC stream", zap.Error(serr)) |
| } else { |
| sws.lg.Warn("failed to send watch response to gRPC stream", zap.Error(serr)) |
| streamFailures.WithLabelValues("send", "watch").Inc() |
| } |
| return |
| } |
| |
| sws.mu.Lock() |
| if len(evs) > 0 && sws.progress[wresp.WatchID] { |
| |
| sws.progress[wresp.WatchID] = false |
| } |
| sws.mu.Unlock() |
| |
| case c, ok := <-sws.ctrlStream: |
| |
| if !ok { |
| return |
| } |
| |
| if err := sws.gRPCStream.Send(c); err != nil { |
| if isClientCtxErr(sws.gRPCStream.Context().Err(), err) { |
| sws.lg.Debug("failed to send watch control response to gRPC stream", zap.Error(err)) |
| } else { |
| sws.lg.Warn("failed to send watch control response to gRPC stream", zap.Error(err)) |
| streamFailures.WithLabelValues("send", "watch").Inc() |
| } |
| return |
| } |
| |
| |
| wid := mvcc.WatchID(c.WatchId) |
| |
| verify.Assert(!(c.Canceled && c.Created) || wid == clientv3.InvalidWatchID, "unexpected watchId: %d, wanted: %d, since both 'Canceled' and 'Created' are true", wid, clientv3.InvalidWatchID) |
| |
| if c.Canceled && wid != clientv3.InvalidWatchID { |
| delete(ids, wid) |
| continue |
| } |
| if c.Created { |
| |
| ids[wid] = struct{}{} |
| for _, v := range pending[wid] { |
| mvcc.ReportEventReceived(len(v.Events)) |
| if err := sws.gRPCStream.Send(v); err != nil { |
| if isClientCtxErr(sws.gRPCStream.Context().Err(), err) { |
| sws.lg.Debug("failed to send pending watch response to gRPC stream", zap.Error(err)) |
| } else { |
| sws.lg.Warn("failed to send pending watch response to gRPC stream", zap.Error(err)) |
| streamFailures.WithLabelValues("send", "watch").Inc() |
| } |
| return |
| } |
| } |
| delete(pending, wid) |
| } |
| |
| case <-progressTicker.C: |
| sws.mu.Lock() |
| for id, ok := range sws.progress { |
| if ok { |
| sws.watchStream.RequestProgress(id) |
| } |
| sws.progress[id] = true |
| } |
| sws.mu.Unlock() |
| |
| case <-sws.closec: |
| return |
| } |
| } |
| } |
| |
这里使用了 for select 循环:
- 从 watchStream.Chan() 中获取 event 然后发送给客户端。
- 处理客户端发送的 watch 请求。
- dispatch progress 事件。
- 处理结束。
recvLoop
| func (sws *serverWatchStream) recvLoop() error { |
| for { |
| req, err := sws.gRPCStream.Recv() |
| if err == io.EOF { |
| return nil |
| } |
| if err != nil { |
| return err |
| } |
| |
| switch uv := req.RequestUnion.(type) { |
| case *pb.WatchRequest_CreateRequest: |
| if uv.CreateRequest == nil { |
| break |
| } |
| |
| creq := uv.CreateRequest |
| if len(creq.Key) == 0 { |
| |
| creq.Key = []byte{0} |
| } |
| if len(creq.RangeEnd) == 0 { |
| |
| |
| creq.RangeEnd = nil |
| } |
| if len(creq.RangeEnd) == 1 && creq.RangeEnd[0] == 0 { |
| |
| creq.RangeEnd = []byte{} |
| } |
| |
| err := sws.isWatchPermitted(creq) |
| if err != nil { |
| var cancelReason string |
| switch err { |
| case auth.ErrInvalidAuthToken: |
| cancelReason = rpctypes.ErrGRPCInvalidAuthToken.Error() |
| case auth.ErrAuthOldRevision: |
| cancelReason = rpctypes.ErrGRPCAuthOldRevision.Error() |
| case auth.ErrUserEmpty: |
| cancelReason = rpctypes.ErrGRPCUserEmpty.Error() |
| default: |
| if err != auth.ErrPermissionDenied { |
| sws.lg.Error("unexpected error code", zap.Error(err)) |
| } |
| cancelReason = rpctypes.ErrGRPCPermissionDenied.Error() |
| } |
| |
| wr := &pb.WatchResponse{ |
| Header: sws.newResponseHeader(sws.watchStream.Rev()), |
| WatchId: clientv3.InvalidWatchID, |
| Canceled: true, |
| Created: true, |
| CancelReason: cancelReason, |
| } |
| |
| select { |
| case sws.ctrlStream <- wr: |
| continue |
| case <-sws.closec: |
| return nil |
| } |
| } |
| |
| 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...) |
| if err == nil { |
| sws.mu.Lock() |
| if creq.ProgressNotify { |
| sws.progress[id] = true |
| } |
| if creq.PrevKv { |
| sws.prevKV[id] = true |
| } |
| if creq.Fragment { |
| sws.fragment[id] = true |
| } |
| sws.mu.Unlock() |
| } else { |
| id = clientv3.InvalidWatchID |
| } |
| |
| wr := &pb.WatchResponse{ |
| Header: sws.newResponseHeader(wsrev), |
| WatchId: int64(id), |
| Created: true, |
| Canceled: err != nil, |
| } |
| if err != nil { |
| wr.CancelReason = err.Error() |
| } |
| select { |
| case sws.ctrlStream <- wr: |
| case <-sws.closec: |
| return nil |
| } |
| |
| case *pb.WatchRequest_CancelRequest: |
| if uv.CancelRequest != nil { |
| id := uv.CancelRequest.WatchId |
| err := sws.watchStream.Cancel(mvcc.WatchID(id)) |
| if err == nil { |
| sws.ctrlStream <- &pb.WatchResponse{ |
| Header: sws.newResponseHeader(sws.watchStream.Rev()), |
| WatchId: id, |
| Canceled: true, |
| } |
| sws.mu.Lock() |
| delete(sws.progress, mvcc.WatchID(id)) |
| delete(sws.prevKV, mvcc.WatchID(id)) |
| delete(sws.fragment, mvcc.WatchID(id)) |
| sws.mu.Unlock() |
| } |
| } |
| case *pb.WatchRequest_ProgressRequest: |
| if uv.ProgressRequest != nil { |
| sws.mu.Lock() |
| sws.watchStream.RequestProgressAll() |
| sws.mu.Unlock() |
| } |
| default: |
| |
| |
| |
| sws.lg.Sugar().Infof("invalid watch request type %T received in gRPC stream", uv) |
| continue |
| } |
| } |
| } |
这里主要处理客户端发送的 watch 请求,然后发送给 ctrlStream。sendLoop 会从 ctrlStream 中获取 event 然后发送给客户端。
WatchStream
这个 inferface 才是处理 watch 的主要逻辑
| |
| type WatchStream interface { |
| |
| |
| |
| |
| |
| |
| |
| Watch(id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error) |
| |
| |
| Chan() <-chan WatchResponse |
| |
| |
| |
| |
| RequestProgress(id WatchID) |
| |
| |
| |
| RequestProgressAll() bool |
| |
| |
| Cancel(id WatchID) error |
| |
| |
| Close() |
| |
| |
| Rev() int64 |
| } |
| |
| |
| type WatchResponse struct { |
| |
| WatchID WatchID |
| |
| |
| Events []mvccpb.Event |
| |
| |
| |
| |
| Revision int64 |
| |
| |
| CompactRevision int64 |
| } |
| |
| |
| |
| type watchStream struct { |
| |
| watchable watchable |
| |
| ch chan WatchResponse |
| |
| |
| mu sync.Mutex |
| |
| nextID WatchID |
| |
| closed bool |
| |
| cancels map[WatchID]cancelFunc |
| |
| watchers map[WatchID]*watcher |
| } |
| |
| |
| func (ws *watchStream) Watch(id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error) { |
| |
| |
| if len(end) != 0 && bytes.Compare(key, end) != -1 { |
| return -1, ErrEmptyWatcherRange |
| } |
| |
| |
| ws.mu.Lock() |
| defer ws.mu.Unlock() |
| |
| if ws.closed { |
| return -1, ErrEmptyWatcherRange |
| } |
| |
| |
| if id == clientv3.AutoWatchID { |
| for ws.watchers[ws.nextID] != nil { |
| ws.nextID++ |
| } |
| id = ws.nextID |
| ws.nextID++ |
| } else if _, ok := ws.watchers[id]; ok { |
| return -1, ErrWatcherDuplicateID |
| } |
| |
| |
| w, c := ws.watchable.watch(key, end, startRev, id, ws.ch, fcs...) |
| |
| |
| ws.cancels[id] = c |
| ws.watchers[id] = w |
| return id, nil |
| } |
| |
| func (ws *watchStream) Chan() <-chan WatchResponse { |
| return ws.ch |
| } |
| |
| func (ws *watchStream) Cancel(id WatchID) error { |
| |
| ws.mu.Lock() |
| cancel, ok := ws.cancels[id] |
| w := ws.watchers[id] |
| ok = ok && !ws.closed |
| ws.mu.Unlock() |
| |
| |
| if !ok { |
| return ErrWatcherNotExist |
| } |
| cancel() |
| |
| |
| ws.mu.Lock() |
| |
| if ww := ws.watchers[id]; ww == w { |
| delete(ws.cancels, id) |
| delete(ws.watchers, id) |
| } |
| ws.mu.Unlock() |
| |
| return nil |
| } |
| |
| func (ws *watchStream) Close() { |
| |
| ws.mu.Lock() |
| defer ws.mu.Unlock() |
| |
| |
| for _, cancel := range ws.cancels { |
| cancel() |
| } |
| |
| ws.closed = true |
| close(ws.ch) |
| watchStreamGauge.Dec() |
| } |
| |
| func (ws *watchStream) Rev() int64 { |
| |
| ws.mu.Lock() |
| defer ws.mu.Unlock() |
| return ws.watchable.rev() |
| } |
| |
| func (ws *watchStream) RequestProgress(id WatchID) { |
| |
| ws.mu.Lock() |
| w, ok := ws.watchers[id] |
| ws.mu.Unlock() |
| |
| if !ok { |
| return |
| } |
| |
| ws.watchable.progress(w) |
| } |
| |
| func (ws *watchStream) RequestProgressAll() bool { |
| |
| ws.mu.Lock() |
| defer ws.mu.Unlock() |
| return ws.watchable.progressAll(ws.watchers) |
| } |
- Watch 方法:创建一个新的观察者,如果指定的范围不正确或观察者ID重复,则返回错误。否则,创建观察者并保存取消函数和观察者实例。
- Chan 方法:返回用于接收watch响应的通道。
- Cancel 方法:取消给定ID的观察者,删除相关的取消函数和观察者实例。
- Close 方法:关闭所有观察者并释放资源。
- Rev 方法:返回当前观察到的KV修订版本。
- RequestProgress 方法:请求特定观察者的进度。
- RequestProgressAll 方法:请求所有观察者的进度通知。
可以可到 当调用 Watch 的时候 每个 watchId 都会调用 watchable.watch
并把自己 ch 放入进去
watchable

| |
| type watchable interface { |
| |
| |
| watch(key, end []byte, startRev int64, id WatchID, ch chan<- WatchResponse, fcs ...FilterFunc) (*watcher, cancelFunc) |
| |
| |
| progress(w *watcher) |
| |
| |
| |
| progressAll(watchers map[WatchID]*watcher) bool |
| |
| |
| rev() int64 |
| } |
| |
| |
| |
| type watchableStore struct { |
| |
| *store |
| |
| |
| mu sync.RWMutex |
| |
| |
| victims []watcherBatch |
| victimc chan struct{} |
| |
| |
| unsynced watcherGroup |
| |
| |
| |
| synced watcherGroup |
| |
| |
| stopc chan struct{} |
| |
| |
| wg sync.WaitGroup |
| } |
| |
| func (s *watchableStore) watch(key, end []byte, startRev int64, id WatchID, ch chan<- WatchResponse, fcs ...FilterFunc) (*watcher, cancelFunc) { |
| |
| wa := &watcher{ |
| key: key, |
| end: end, |
| minRev: startRev, |
| id: id, |
| ch: ch, |
| fcs: fcs, |
| } |
| |
| |
| s.mu.Lock() |
| |
| s.revMu.RLock() |
| |
| synced := startRev > s.store.currentRev || startRev == 0 |
| if synced { |
| |
| wa.minRev = s.store.currentRev + 1 |
| if startRev > wa.minRev { |
| wa.minRev = startRev |
| } |
| |
| s.synced.add(wa) |
| } else { |
| |
| slowWatcherGauge.Inc() |
| |
| s.unsynced.add(wa) |
| } |
| |
| s.revMu.RUnlock() |
| |
| s.mu.Unlock() |
| |
| |
| watcherGauge.Inc() |
| |
| |
| return wa, func() { s.cancelWatcher(wa) } |
| } |
newWatchableStore
| func newWatchableStore(lg *zap.Logger, b backend.Backend, le lease.Lessor, cfg StoreConfig) *watchableStore { |
| if lg == nil { |
| lg = zap.NewNop() |
| } |
| s := &watchableStore{ |
| store: NewStore(lg, b, le, cfg), |
| victimc: make(chan struct{}, 1), |
| unsynced: newWatcherGroup(), |
| synced: newWatcherGroup(), |
| stopc: make(chan struct{}), |
| } |
| s.store.ReadView = &readView{s} |
| s.store.WriteView = &writeView{s} |
| if s.le != nil { |
| |
| s.le.SetRangeDeleter(func() lease.TxnDelete { return s.Write(traceutil.TODO()) }) |
| } |
| s.wg.Add(2) |
| go s.syncWatchersLoop() |
| go s.syncVictimsLoop() |
| return s |
| } |
syncWatchersLoop
| |
| func (s *watchableStore) syncWatchersLoop() { |
| defer s.wg.Done() |
| |
| |
| waitDuration := 100 * time.Millisecond |
| delayTicker := time.NewTicker(waitDuration) |
| defer delayTicker.Stop() |
| |
| for { |
| |
| s.mu.RLock() |
| st := time.Now() |
| lastUnsyncedWatchers := s.unsynced.size() |
| s.mu.RUnlock() |
| |
| unsyncedWatchers := 0 |
| |
| if lastUnsyncedWatchers > 0 { |
| unsyncedWatchers = s.syncWatchers() |
| } |
| syncDuration := time.Since(st) |
| |
| |
| delayTicker.Reset(waitDuration) |
| |
| if unsyncedWatchers != 0 && lastUnsyncedWatchers > unsyncedWatchers { |
| |
| delayTicker.Reset(syncDuration) |
| } |
| |
| |
| select { |
| case <-delayTicker.C: |
| case <-s.stopc: |
| return |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| func (s *watchableStore) syncWatchers() int { |
| |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| |
| |
| if s.unsynced.size() == 0 { |
| return 0 |
| } |
| |
| |
| s.store.revMu.RLock() |
| defer s.store.revMu.RUnlock() |
| |
| |
| curRev := s.store.currentRev |
| compactionRev := s.store.compactMainRev |
| |
| |
| wg, minRev := s.unsynced.choose(maxWatchersPerSync, curRev, compactionRev) |
| minBytes, maxBytes := NewRevBytes(), NewRevBytes() |
| minBytes = RevToBytes(Revision{Main: minRev}, minBytes) |
| maxBytes = RevToBytes(Revision{Main: curRev + 1}, maxBytes) |
| |
| |
| tx := s.store.b.ReadTx() |
| tx.RLock() |
| revs, vs := tx.UnsafeRange(schema.Key, minBytes, maxBytes, 0) |
| evs := kvsToEvents(s.store.lg, wg, revs, vs) |
| |
| |
| |
| tx.RUnlock() |
| |
| |
| victims := make(watcherBatch) |
| wb := newWatcherBatch(wg, evs) |
| for w := range wg.watchers { |
| if w.minRev < compactionRev { |
| |
| continue |
| } |
| w.minRev = curRev + 1 |
| |
| eb, ok := wb[w] |
| if !ok { |
| |
| s.synced.add(w) |
| s.unsynced.delete(w) |
| continue |
| } |
| |
| if eb.moreRev != 0 { |
| w.minRev = eb.moreRev |
| } |
| |
| |
| if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: curRev}) { |
| pendingEventsGauge.Add(float64(len(eb.evs))) |
| } else { |
| w.victim = true |
| } |
| |
| |
| if w.victim { |
| victims[w] = eb |
| } else { |
| if eb.moreRev != 0 { |
| |
| continue |
| } |
| s.synced.add(w) |
| } |
| s.unsynced.delete(w) |
| } |
| s.addVictim(victims) |
| |
| |
| vsz := 0 |
| for _, v := range s.victims { |
| vsz += len(v) |
| } |
| slowWatcherGauge.Set(float64(s.unsynced.size() + vsz)) |
| |
| return s.unsynced.size() |
| } |
watcher & send
| type watcher struct { |
| |
| key []byte |
| |
| |
| end []byte |
| |
| |
| victim bool |
| |
| |
| compacted bool |
| |
| |
| |
| |
| |
| |
| |
| restore bool |
| |
| |
| minRev int64 |
| id WatchID |
| |
| fcs []FilterFunc |
| |
| |
| ch chan<- WatchResponse |
| } |
| |
| func (w *watcher) send(wr WatchResponse) bool { |
| progressEvent := len(wr.Events) == 0 |
| |
| if len(w.fcs) != 0 { |
| ne := make([]mvccpb.Event, 0, len(wr.Events)) |
| for i := range wr.Events { |
| filtered := false |
| for _, filter := range w.fcs { |
| if filter(wr.Events[i]) { |
| filtered = true |
| break |
| } |
| } |
| if !filtered { |
| ne = append(ne, wr.Events[i]) |
| } |
| } |
| wr.Events = ne |
| } |
| |
| |
| if !progressEvent && len(wr.Events) == 0 { |
| return true |
| } |
| select { |
| case w.ch <- wr: |
| return true |
| default: |
| return false |
| } |
| } |
| |
syncVictimsLoop
| |
| func (s *watchableStore) syncVictimsLoop() { |
| defer s.wg.Done() |
| |
| for { |
| |
| for s.moveVictims() != 0 { |
| |
| } |
| |
| |
| s.mu.RLock() |
| isEmpty := len(s.victims) == 0 |
| s.mu.RUnlock() |
| |
| var tickc <-chan time.Time |
| if !isEmpty { |
| tickc = time.After(10 * time.Millisecond) |
| } |
| |
| |
| select { |
| case <-tickc: |
| case <-s.victimc: |
| case <-s.stopc: |
| return |
| } |
| } |
| } |
| |
| |
| 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 { |
| |
| 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 |
| } |
| moved++ |
| } |
| |
| |
| s.mu.Lock() |
| s.store.revMu.RLock() |
| curRev := s.store.currentRev |
| for w, eb := range wb { |
| if newVictim != nil && newVictim[w] != nil { |
| |
| continue |
| } |
| w.victim = false |
| if eb.moreRev != 0 { |
| w.minRev = eb.moreRev |
| } |
| if w.minRev <= curRev { |
| s.unsynced.add(w) |
| } else { |
| slowWatcherGauge.Dec() |
| s.synced.add(w) |
| } |
| } |
| s.store.revMu.RUnlock() |
| s.mu.Unlock() |
| } |
| |
| |
| if len(newVictim) > 0 { |
| s.mu.Lock() |
| s.victims = append(s.victims, newVictim) |
| s.mu.Unlock() |
| } |
| |
| return moved |
| } |
Reference
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)