github.com/XiaoMi/Gaea@v1.2.5/models/etcdv3/etcdv3.go (about) 1 // Copyright 2016 CodisLabs. All Rights Reserved. 2 // Licensed under the MIT (MIT-LICENSE.txt) license. 3 4 // Copyright 2019 The Gaea Authors. All Rights Reserved. 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package etcdclientv3 19 20 import ( 21 "context" 22 "errors" 23 "github.com/XiaoMi/Gaea/log" 24 "github.com/coreos/etcd/clientv3" 25 "strings" 26 "sync" 27 "time" 28 ) 29 30 // ErrClosedEtcdClient means etcd client closed 31 var ErrClosedEtcdClient = errors.New("use of closed etcd client") 32 33 const ( 34 defaultEtcdPrefix = "/gaea" 35 ) 36 37 // EtcdClientV3 为新版的 etcd client 38 type EtcdClientV3 struct { 39 sync.Mutex 40 kapi clientv3.Client // 就这里改成新版的 API 41 42 closed bool 43 timeout time.Duration 44 Prefix string 45 } 46 47 // New 建立新的 etcd v3 的客户端 48 func New(addr string, timeout time.Duration, username, passwd, root string) (*EtcdClientV3, error) { 49 endpoints := strings.Split(addr, ",") 50 for i, s := range endpoints { 51 if s != "" && !strings.HasPrefix(s, "http://") { 52 endpoints[i] = "http://" + s 53 } 54 } 55 config := clientv3.Config{ 56 Endpoints: endpoints, 57 Username: username, 58 Password: passwd, 59 DialTimeout: timeout, // 只设定第一次连线时间的逾时,之后不用太担心连线,连线失败后,会自动重连 60 DialKeepAliveTimeout: timeout, // 之后维持 etcd 连线的逾时 61 } 62 c, err := clientv3.New(config) 63 if err != nil { 64 return nil, err 65 } 66 if strings.TrimSpace(root) == "" { 67 root = defaultEtcdPrefix 68 } 69 return &EtcdClientV3{ 70 kapi: *c, 71 timeout: timeout, // 每个指令的逾时时间也是用同一个设定值 72 Prefix: root, 73 }, nil 74 } 75 76 // Close 关闭新的 etcd v3 的客户端 77 func (c *EtcdClientV3) Close() error { 78 c.Lock() 79 defer c.Unlock() 80 if c.closed { 81 return nil 82 } 83 err := c.kapi.Close() // 如果能够成功关闭 etcd 连线 84 if err != nil { 85 return err 86 } 87 c.closed = true // 如果没有错误,就特别标记连线已正确关闭 88 return nil 89 } 90 91 func (c *EtcdClientV3) contextWithTimeout() (context.Context, context.CancelFunc) { 92 if c.timeout == 0 { 93 return context.Background(), func() {} 94 } 95 return context.WithTimeout(context.Background(), c.timeout) 96 } 97 98 // isErrNodeExists (目前此函数并不支援 v3) 99 /*func isErrNoNode(err error) bool { 100 if err != nil { 101 if e, ok := err.(client.Error); ok { 102 return e.Code == client.ErrorCodeKeyNotFound 103 } 104 } 105 return false 106 }*/ 107 108 // isErrNodeExists (v3 版没有在使用此函数) 109 /*func isErrNodeExists(err error) bool { 110 if err != nil { 111 if e, ok := err.(client.Error); ok { 112 return e.Code == client.ErrorCodeNodeExist 113 } 114 } 115 return false 116 }*/ 117 118 // Mkdir create directory (v2 的版本也没在用这函数) 119 func (c *EtcdClientV3) Mkdir(dir string) error { 120 return nil 121 } 122 123 // mkdir create directory (v2 的版本也没在用这函数) 124 /*func (c *EtcdClientV3) mkdir(dir string) error { 125 if dir == "" || dir == "/" { 126 return nil 127 } 128 cntx, canceller := c.contextWithTimeout() 129 defer canceller() 130 _, err := c.kapi.Put(cntx, dir, "", nil) // 找不太到参数 131 if err != nil { 132 if isErrNodeExists(err) { 133 return nil 134 } 135 return err 136 } 137 return nil 138 }*/ 139 140 // Create create path with data (v2 的版本也没在用这函数) 141 func (c *EtcdClientV3) Create(path string, data []byte) error { 142 return nil 143 } 144 145 // 参考文件在 https://etcd.io/docs/v3.5/tutorials/how-to-get-key-by-prefix/ 146 // 1 WithLease 是用来定时删除 key 147 // 2 WithLimit 是用来限制 etcd 的回传数量 148 // 3 WithRev,Revision 为 etcd key 的唯一值,为在 Revision 找值 149 // 4 WithMaxCreateRev 为在小于某一个 Revision 中找最大 Revision 的值 150 // 5 WithSort 为使用 get 时,对回传结果进行排序 151 // 6 WithPrefix 为取出前缀为 key 的值,比如 前缀为 foo,会回传 foo1 foo2 152 // 7 WithRange 为取出 key 值的范围,end key 值语义上要大于 key 值 153 // 8 WithFromKey 为取出 key 值的范围,但回传的 end key 会等于参数 end key 154 // 9 WithSerializable,Linearizability 为用来增加资料的正确性,Serializable 为用来减少延迟 155 // 10 WithKeysOnly 为当使用 get 时,只回传 key 156 // 11 WithCountOnly 为当使用 get 时,只回传 key 157 // 12 WithMinModRev 为会过滤小于 Revision 的 修改 Key 158 // 13 WithMaxModRev 为会过滤大于 Revision 的 修改 Key 159 // 14 WithMinCreateRev 为会过滤小于 Revision 的 新建 Key 160 // 15 WithMaxCreateRev 为会过滤大于 Revision 的 新建 Key 161 162 // Update update path with data 163 func (c *EtcdClientV3) Update(path string, data []byte) error { 164 c.Lock() 165 defer c.Unlock() 166 if c.closed { 167 return ErrClosedEtcdClient 168 } 169 cntx, canceller := c.contextWithTimeout() 170 defer canceller() 171 _ = log.Debug("etcd update node %s", path) 172 173 _, err := c.kapi.Put(cntx, path, string(data)) 174 if err != nil { 175 _ = log.Debug("etcd update node %s failed: %s", path, err) 176 return err 177 } 178 _ = log.Debug("etcd update node OK") 179 return nil 180 } 181 182 // UpdateWithTTL update path with data and ttl 183 func (c *EtcdClientV3) UpdateWithTTL(path string, data []byte, ttl time.Duration) error { 184 c.Lock() 185 defer c.Unlock() 186 if c.closed { 187 return ErrClosedEtcdClient 188 } 189 cntx, canceller := c.contextWithTimeout() 190 defer canceller() 191 _ = log.Debug("etcd update node %s with ttl %f", path, ttl.Seconds()) 192 193 lse, err := c.kapi.Grant(cntx, int64(ttl.Seconds())) 194 if err != nil { 195 _ = log.Debug("etcd lease node with ttl %d failed: %s ", ttl.Seconds(), err) 196 return err 197 } 198 199 _, err = c.kapi.Put(cntx, path, string(data), clientv3.WithLease(lse.ID)) 200 if err != nil { 201 log.Debug("etcd update node %s failed: %s", path, err) 202 return err 203 } 204 log.Debug("etcd update node OK") 205 return nil 206 } 207 208 // Lease create lease in etcd 209 func (c *EtcdClientV3) Lease(ttl time.Duration) (clientv3.LeaseID, error) { 210 /*c.Lock() 211 defer c.Unlock() 212 if c.closed { 213 return -1, ErrClosedEtcdClient 214 }*/ 215 cntx, canceller := c.contextWithTimeout() 216 defer canceller() 217 _ = log.Debug("etcd lease node with ttl %d", ttl) 218 219 lse, err := c.kapi.Grant(cntx, int64(ttl.Seconds())) 220 if err != nil { 221 _ = log.Debug("etcd lease node with ttl %d failed: %s ", ttl, err) 222 return -1, err 223 } 224 _ = log.Debug("etcd create lease OK with ID %d", lse.ID) 225 return lse.ID, nil 226 } 227 228 // UpdateWithLease update path with data and ttl by using lease 229 func (c *EtcdClientV3) UpdateWithLease(path string, data []byte, leaseID clientv3.LeaseID) error { 230 c.Lock() 231 defer c.Unlock() 232 if c.closed { 233 return ErrClosedEtcdClient 234 } 235 cntx, canceller := c.contextWithTimeout() 236 defer canceller() 237 _ = log.Debug("etcd update node %s with lease %d", path, leaseID) 238 239 _, err := c.kapi.Put(cntx, path, string(data), clientv3.WithLease(leaseID)) 240 if err != nil { 241 _ = log.Debug("etcd update node %s failed: %s", path, err) 242 return err 243 } 244 _ = log.Debug("etcd update node OK") 245 return nil 246 } 247 248 // Delete delete path 249 func (c *EtcdClientV3) Delete(path string) error { 250 c.Lock() 251 defer c.Unlock() 252 if c.closed { 253 return ErrClosedEtcdClient 254 } 255 cntx, canceller := c.contextWithTimeout() 256 defer canceller() 257 _ = log.Debug("etcd delete node %s", path) 258 _, err := c.kapi.Delete(cntx, path, clientv3.WithPrevKV()) 259 if err != nil { 260 _ = log.Debug("etcd delete node %s failed: %s", path, err) 261 return err 262 } 263 _ = log.Debug("etcd delete node OK") 264 return nil 265 } 266 267 // Read read path data 268 func (c *EtcdClientV3) Read(path string) ([]byte, error) { 269 c.Lock() 270 defer c.Unlock() 271 if c.closed { 272 return nil, ErrClosedEtcdClient 273 } 274 cntx, canceller := c.contextWithTimeout() 275 defer canceller() 276 _ = log.Debug("etcd read node %s", path) 277 r, err := c.kapi.Get(cntx, path, clientv3.WithPrevKV()) 278 if err != nil { 279 return nil, err 280 } else { 281 if len(r.Kvs) > 0 { 282 return r.Kvs[0].Value, nil 283 } 284 } 285 return []byte{}, nil 286 } 287 288 // List list path, return slice of all paths 289 func (c *EtcdClientV3) List(path string) ([]string, error) { 290 c.Lock() 291 defer c.Unlock() 292 if c.closed { 293 return nil, ErrClosedEtcdClient 294 } 295 cntx, canceller := c.contextWithTimeout() 296 defer canceller() 297 _ = log.Debug("etcd list node %s", path) 298 r, err := c.kapi.Get(cntx, path, clientv3.WithPrefix()) 299 if err != nil { 300 return nil, err 301 } else if r == nil { 302 return nil, nil 303 } else { 304 var files []string 305 for _, node := range r.Kvs { 306 files = append(files, string(node.Key)) 307 } 308 return files, nil 309 } 310 } 311 312 // Watch watch path 313 func (c *EtcdClientV3) Watch(path string, ch chan string) error { 314 c.Lock() // 在这里上锁 315 // defer c.Unlock() // 移除此行,避免死结发生 316 if c.closed { 317 c.Unlock() // 上锁后记得解锁,去防止死结问题发生 318 panic(ErrClosedEtcdClient) 319 } 320 321 rch := c.kapi.Watch(context.Background(), path, clientv3.WithPrefix()) 322 323 c.Unlock() // 上锁后在适当时机解锁,去防止死结问题发生 324 // 在这里解锁是最好的,因为解锁后立刻可以进行监听 325 326 for wresp := range rch { 327 for _, ev := range wresp.Events { 328 ch <- string(ev.Kv.Key) 329 } 330 } 331 332 return nil 333 } 334 335 // BasePrefix return base prefix 336 func (c *EtcdClientV3) BasePrefix() string { 337 return c.Prefix 338 }