github.heygears.com/openimsdk/tools@v0.0.49/discovery/zookeeper/zk.go (about) 1 // Copyright © 2023 OpenIM. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package zookeeper 16 17 import ( 18 "context" 19 "net" 20 "strconv" 21 "sync" 22 "time" 23 24 "github.com/go-zookeeper/zk" 25 "github.com/openimsdk/tools/errs" 26 "github.com/openimsdk/tools/log" 27 "google.golang.org/grpc" 28 "google.golang.org/grpc/resolver" 29 ) 30 31 const ( 32 defaultFreq = time.Minute * 30 33 timeout = 5 34 ) 35 36 type ZkClient struct { 37 ZkServers []string 38 zkRoot string 39 username string 40 password string 41 42 rpcRegisterName string 43 rpcRegisterAddr string 44 isRegistered bool 45 scheme string 46 47 timeout int 48 conn *zk.Conn 49 eventChan <-chan zk.Event 50 node string 51 ticker *time.Ticker 52 53 lock sync.Locker 54 options []grpc.DialOption 55 56 resolvers map[string]*Resolver 57 localConns map[string][]*grpc.ClientConn 58 cancel context.CancelFunc 59 isStateDisconnected bool 60 balancerName string 61 62 logger log.Logger 63 } 64 65 // NewZkClient initializes a new ZkClient with provided options and establishes a Zookeeper connection. 66 func NewZkClient(ZkServers []string, scheme string, options ...ZkOption) (*ZkClient, error) { 67 client := &ZkClient{ 68 ZkServers: ZkServers, 69 zkRoot: "/", 70 scheme: scheme, 71 timeout: timeout, 72 localConns: make(map[string][]*grpc.ClientConn), 73 resolvers: make(map[string]*Resolver), 74 lock: &sync.Mutex{}, 75 logger: nilLog{}, 76 } 77 for _, option := range options { 78 option(client) 79 } 80 81 // Establish a Zookeeper connection with a specified timeout and handle authentication. 82 conn, eventChan, err := zk.Connect(ZkServers, time.Duration(client.timeout)*time.Second, zk.WithLogger(nilLog{})) 83 if err != nil { 84 return nil, errs.WrapMsg(err, "connect failed", "ZkServers", ZkServers) 85 } 86 87 ctx, cancel := context.WithCancel(context.Background()) 88 client.cancel = cancel 89 client.ticker = time.NewTicker(defaultFreq) 90 91 // Ensure authentication is set if credentials are provided. 92 if client.username != "" && client.password != "" { 93 auth := []byte(client.username + ":" + client.password) 94 if err := conn.AddAuth("digest", auth); err != nil { 95 conn.Close() 96 return nil, errs.WrapMsg(err, "AddAuth failed", "username", client.username, "password", client.password) 97 } 98 } 99 100 client.zkRoot += scheme 101 client.eventChan = eventChan 102 client.conn = conn 103 104 // Verify root node existence and create if missing. 105 if err := client.ensureRoot(); err != nil { 106 conn.Close() 107 return nil, err 108 } 109 110 resolver.Register(client) 111 go client.refresh(ctx) 112 go client.watch(ctx) 113 114 return client, nil 115 } 116 117 func (s *ZkClient) Close() { 118 s.logger.Info(context.Background(), "close zk called") 119 s.cancel() 120 s.ticker.Stop() 121 s.conn.Close() 122 } 123 124 func (s *ZkClient) ensureAndCreate(node string) error { 125 exists, _, err := s.conn.Exists(node) 126 if err != nil { 127 return errs.WrapMsg(err, "Exists failed", "node", node) 128 } 129 if !exists { 130 _, err = s.conn.Create(node, []byte(""), 0, zk.WorldACL(zk.PermAll)) 131 if err != nil && err != zk.ErrNodeExists { 132 return errs.WrapMsg(err, "Create failed", "node", node) 133 } 134 } 135 return nil 136 } 137 138 func (s *ZkClient) refresh(ctx context.Context) { 139 for range s.ticker.C { 140 s.logger.Debug(ctx, "zk refresh local conns") 141 s.lock.Lock() 142 for rpcName := range s.resolvers { 143 s.flushResolver(rpcName) 144 } 145 for rpcName := range s.localConns { 146 delete(s.localConns, rpcName) 147 } 148 s.lock.Unlock() 149 s.logger.Debug(ctx, "zk refresh local conns success") 150 } 151 } 152 153 func (s *ZkClient) flushResolverAndDeleteLocal(serviceName string) { 154 s.logger.Debug(context.Background(), "zk start flush", "serviceName", serviceName) 155 s.flushResolver(serviceName) 156 delete(s.localConns, serviceName) 157 } 158 159 func (s *ZkClient) flushResolver(serviceName string) { 160 r, ok := s.resolvers[serviceName] 161 if ok { 162 r.ResolveNowZK(resolver.ResolveNowOptions{}) 163 } 164 } 165 166 func (s *ZkClient) GetZkConn() *zk.Conn { 167 return s.conn 168 } 169 170 func (s *ZkClient) GetRootPath() string { 171 return s.zkRoot 172 } 173 174 func (s *ZkClient) GetNode() string { 175 return s.node 176 } 177 178 func (s *ZkClient) ensureRoot() error { 179 return s.ensureAndCreate(s.zkRoot) 180 } 181 182 func (s *ZkClient) ensureName(rpcRegisterName string) error { 183 return s.ensureAndCreate(s.getPath(rpcRegisterName)) 184 } 185 186 func (s *ZkClient) getPath(rpcRegisterName string) string { 187 return s.zkRoot + "/" + rpcRegisterName 188 } 189 190 func (s *ZkClient) getAddr(host string, port int) string { 191 return net.JoinHostPort(host, strconv.Itoa(port)) 192 } 193 194 func (s *ZkClient) AddOption(opts ...grpc.DialOption) { 195 s.options = append(s.options, opts...) 196 } 197 198 func (s *ZkClient) GetClientLocalConns() map[string][]*grpc.ClientConn { 199 return s.localConns 200 }