github.com/dubbogo/gost@v1.14.0/database/kv/etcd/v3/client.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package gxetcd 19 20 import ( 21 "context" 22 "log" 23 "strings" 24 "sync" 25 "time" 26 ) 27 28 import ( 29 perrors "github.com/pkg/errors" 30 31 "go.etcd.io/etcd/client/v3" 32 "go.etcd.io/etcd/client/v3/concurrency" 33 34 "google.golang.org/grpc" 35 ) 36 37 var ( 38 // ErrNilETCDV3Client raw client nil 39 ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR 40 // ErrKVPairNotFound not found key 41 ErrKVPairNotFound = perrors.New("k/v pair not found") 42 // ErrKVListSizeIllegal k/v list empty or not equal size 43 ErrKVListSizeIllegal = perrors.New("k/v List is empty or kList's size is not equal to the size of vList") 44 // ErrCompareFail txn compare fail 45 ErrCompareFail = perrors.New("txn compare fail") 46 // ErrRevision revision when error 47 ErrRevision int64 = -1 48 ) 49 50 // NewConfigClient create new Client 51 func NewConfigClient(opts ...Option) *Client { 52 newClient, err := NewConfigClientWithErr(opts...) 53 54 if err != nil { 55 log.Printf("new etcd client = error{%v}", err) 56 } 57 return newClient 58 } 59 60 // NewConfigClientWithErr create new Client,error 61 func NewConfigClientWithErr(opts ...Option) (*Client, error) { 62 options := &Options{ 63 Heartbeat: 1, // default Heartbeat 64 } 65 for _, opt := range opts { 66 opt(options) 67 } 68 69 newClient, err := NewClient(options.Name, options.Endpoints, options.Timeout, options.Heartbeat) 70 if err != nil { 71 log.Printf("new etcd client (Name{%s}, etcd addresses{%v}, Timeout{%d}) = error{%v}", 72 options.Name, options.Endpoints, options.Timeout, err) 73 } 74 75 return newClient, err 76 } 77 78 // Client represents etcd client Configuration 79 type Client struct { 80 lock sync.RWMutex 81 quitOnce sync.Once 82 83 // these properties are only set once when they are started. 84 name string 85 endpoints []string 86 timeout time.Duration 87 heartbeat int 88 89 ctx context.Context // if etcd server connection lose, the ctx.Done will be sent msg 90 cancel context.CancelFunc // cancel the ctx, all watcher will stopped 91 rawClient *clientv3.Client 92 93 exit chan struct{} 94 Wait sync.WaitGroup 95 } 96 97 // NewClient create a client instance with name, endpoints etc. 98 func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) { 99 ctx, cancel := context.WithCancel(context.Background()) 100 101 rawClient, err := clientv3.New(clientv3.Config{ 102 Context: ctx, 103 Endpoints: endpoints, 104 DialTimeout: timeout, 105 DialOptions: []grpc.DialOption{grpc.WithBlock()}, 106 }) 107 if err != nil { 108 cancel() 109 return nil, perrors.WithMessage(err, "new raw client block connect to server") 110 } 111 112 c := &Client{ 113 name: name, 114 timeout: timeout, 115 endpoints: endpoints, 116 heartbeat: heartbeat, 117 118 ctx: ctx, 119 cancel: cancel, 120 rawClient: rawClient, 121 122 exit: make(chan struct{}), 123 } 124 125 if err := c.keepSession(); err != nil { 126 cancel() 127 return nil, perrors.WithMessage(err, "client keep session") 128 } 129 return c, nil 130 } 131 132 // NOTICE: need to get the lock before calling this method 133 func (c *Client) clean() { 134 // close raw client 135 c.rawClient.Close() 136 137 // cancel ctx for raw client 138 c.cancel() 139 140 // clean raw client 141 c.rawClient = nil 142 } 143 144 func (c *Client) stop() bool { 145 select { 146 case <-c.exit: 147 return false 148 default: 149 ret := false 150 c.quitOnce.Do(func() { 151 ret = true 152 close(c.exit) 153 }) 154 return ret 155 } 156 } 157 158 // GetCtx return client context 159 func (c *Client) GetCtx() context.Context { 160 return c.ctx 161 } 162 163 // Close close client 164 func (c *Client) Close() { 165 if c == nil { 166 return 167 } 168 169 // stop the client 170 if ret := c.stop(); !ret { 171 return 172 } 173 174 // wait client keep session stop 175 c.Wait.Wait() 176 177 c.lock.Lock() 178 defer c.lock.Unlock() 179 if c.rawClient != nil { 180 c.clean() 181 } 182 log.Printf("etcd client{Name:%s, Endpoints:%s} exit now.", c.name, c.endpoints) 183 } 184 185 func (c *Client) keepSession() error { 186 s, err := concurrency.NewSession(c.rawClient, concurrency.WithTTL(c.heartbeat)) 187 if err != nil { 188 return perrors.WithMessage(err, "new session with server") 189 } 190 191 // must add wg before go keep session goroutine 192 c.Wait.Add(1) 193 go c.keepSessionLoop(s) 194 return nil 195 } 196 197 func (c *Client) keepSessionLoop(s *concurrency.Session) { 198 defer func() { 199 c.Wait.Done() 200 log.Printf("etcd client {Endpoints:%v, Name:%s} keep goroutine game over.", c.endpoints, c.name) 201 }() 202 203 for { 204 select { 205 case <-c.Done(): 206 // Client be stopped, will clean the client hold resources 207 return 208 case <-s.Done(): 209 log.Print("etcd server stopped") 210 c.lock.Lock() 211 // when etcd server stopped, cancel ctx, stop all watchers 212 c.clean() 213 // when connection lose, stop client, trigger reconnect to etcd 214 c.stop() 215 c.lock.Unlock() 216 return 217 } 218 } 219 } 220 221 // GetRawClient return etcd raw client 222 func (c *Client) GetRawClient() *clientv3.Client { 223 c.lock.RLock() 224 defer c.lock.RUnlock() 225 226 return c.rawClient 227 } 228 229 // GetEndPoints return etcd endpoints 230 func (c *Client) GetEndPoints() []string { 231 return c.endpoints 232 } 233 234 // if k not exist will put k/v in etcd, otherwise return ErrCompareFail 235 func (c *Client) create(k string, v string, opts ...clientv3.OpOption) error { 236 rawClient := c.GetRawClient() 237 if rawClient == nil { 238 return ErrNilETCDV3Client 239 } 240 241 resp, err := rawClient.Txn(c.ctx). 242 If(clientv3.Compare(clientv3.CreateRevision(k), "=", 0)). 243 Then(clientv3.OpPut(k, v, opts...)). 244 Commit() 245 if err != nil { 246 return err 247 } 248 if !resp.Succeeded { 249 return ErrCompareFail 250 } 251 252 return nil 253 } 254 255 // if k in bulk insertion not exist all, then put all k/v in etcd, otherwise return error 256 func (c *Client) batchCreate(kList []string, vList []string, opts ...clientv3.OpOption) error { 257 rawClient := c.GetRawClient() 258 if rawClient == nil { 259 return ErrNilETCDV3Client 260 } 261 262 kLen := len(kList) 263 vLen := len(vList) 264 if kLen == 0 || vLen == 0 || kLen != vLen { 265 return ErrKVListSizeIllegal 266 } 267 268 var cs []clientv3.Cmp 269 var ops []clientv3.Op 270 271 for i, k := range kList { 272 v := vList[i] 273 cs = append(cs, clientv3.Compare(clientv3.CreateRevision(k), "=", 0)) 274 ops = append(ops, clientv3.OpPut(k, v, opts...)) 275 } 276 277 resp, err := rawClient.Txn(c.ctx). 278 If(cs...). 279 Then(ops...). 280 Commit() 281 if err != nil { 282 return err 283 } 284 if !resp.Succeeded { 285 return ErrCompareFail 286 } 287 288 return nil 289 } 290 291 // put k/v in etcd, if fail return error 292 func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error { 293 rawClient := c.GetRawClient() 294 if rawClient == nil { 295 return ErrNilETCDV3Client 296 } 297 298 _, err := rawClient.Put(c.ctx, k, v, opts...) 299 return err 300 } 301 302 // put k/v in etcd when ModRevision equal with rev, if not return ErrCompareFail or other err 303 func (c *Client) updateWithRev(k string, v string, rev int64, opts ...clientv3.OpOption) error { 304 rawClient := c.GetRawClient() 305 if rawClient == nil { 306 return ErrNilETCDV3Client 307 } 308 309 resp, err := rawClient.Txn(c.ctx). 310 If(clientv3.Compare(clientv3.ModRevision(k), "=", rev)). 311 Then(clientv3.OpPut(k, v, opts...)). 312 Commit() 313 if err != nil { 314 return err 315 } 316 if !resp.Succeeded { 317 return ErrCompareFail 318 } 319 320 return nil 321 } 322 323 func (c *Client) delete(k string) error { 324 rawClient := c.GetRawClient() 325 if rawClient == nil { 326 return ErrNilETCDV3Client 327 } 328 329 _, err := rawClient.Delete(c.ctx, k) 330 return err 331 } 332 333 // getValAndRev get value and revision 334 func (c *Client) getValAndRev(k string) (string, int64, error) { 335 rawClient := c.GetRawClient() 336 if rawClient == nil { 337 return "", ErrRevision, ErrNilETCDV3Client 338 } 339 340 resp, err := rawClient.Get(c.ctx, k) 341 if err != nil { 342 return "", ErrRevision, err 343 } 344 345 if len(resp.Kvs) == 0 { 346 return "", ErrRevision, ErrKVPairNotFound 347 } 348 349 return string(resp.Kvs[0].Value), resp.Header.Revision, nil 350 } 351 352 // CleanKV delete all key and value 353 func (c *Client) CleanKV() error { 354 rawClient := c.GetRawClient() 355 if rawClient == nil { 356 return ErrNilETCDV3Client 357 } 358 359 _, err := rawClient.Delete(c.ctx, "", clientv3.WithPrefix()) 360 return err 361 } 362 363 // GetChildren return node children 364 func (c *Client) GetChildren(k string) ([]string, []string, error) { 365 rawClient := c.GetRawClient() 366 if rawClient == nil { 367 return nil, nil, ErrNilETCDV3Client 368 } 369 370 if !strings.HasSuffix(k, "/") { 371 k += "/" 372 } 373 374 resp, err := rawClient.Get(c.ctx, k, clientv3.WithPrefix()) 375 if err != nil { 376 return nil, nil, err 377 } 378 379 if len(resp.Kvs) == 0 { 380 return nil, nil, ErrKVPairNotFound 381 } 382 383 kList := make([]string, 0, len(resp.Kvs)) 384 vList := make([]string, 0, len(resp.Kvs)) 385 for _, kv := range resp.Kvs { 386 kList = append(kList, string(kv.Key)) 387 vList = append(vList, string(kv.Value)) 388 } 389 return kList, vList, nil 390 } 391 392 // watchWithOption watch 393 func (c *Client) watchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) { 394 rawClient := c.GetRawClient() 395 if rawClient == nil { 396 return nil, ErrNilETCDV3Client 397 } 398 399 return rawClient.Watch(c.ctx, k, opts...), nil 400 } 401 402 func (c *Client) keepAliveKV(k string, v string) error { 403 rawClient := c.GetRawClient() 404 if rawClient == nil { 405 return ErrNilETCDV3Client 406 } 407 408 // make lease time longer, since 1 second is too short 409 lease, err := rawClient.Grant(c.ctx, int64(30*time.Second.Seconds())) 410 if err != nil { 411 return perrors.WithMessage(err, "grant lease") 412 } 413 414 keepAlive, err := rawClient.KeepAlive(c.ctx, lease.ID) 415 if err != nil || keepAlive == nil { 416 rawClient.Revoke(c.ctx, lease.ID) 417 if err != nil { 418 return perrors.WithMessage(err, "keep alive lease") 419 } 420 return perrors.New("keep alive lease") 421 } 422 423 _, err = rawClient.Put(c.ctx, k, v, clientv3.WithLease(lease.ID)) 424 return perrors.WithMessage(err, "put k/v with lease") 425 } 426 427 // Done return exit chan 428 func (c *Client) Done() <-chan struct{} { 429 return c.exit 430 } 431 432 // Valid check client 433 func (c *Client) Valid() bool { 434 select { 435 case <-c.exit: 436 return false 437 default: 438 } 439 440 c.lock.RLock() 441 defer c.lock.RUnlock() 442 return c.rawClient != nil 443 } 444 445 // Create key value ... 446 func (c *Client) Create(k string, v string) error { 447 err := c.create(k, v) 448 return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) 449 } 450 451 // BatchCreate bulk insertion 452 func (c *Client) BatchCreate(kList []string, vList []string) error { 453 err := c.batchCreate(kList, vList) 454 return perrors.WithMessagef(err, "batch put k/v error ") 455 } 456 457 // Update key value ... 458 func (c *Client) Update(k, v string) error { 459 err := c.put(k, v) 460 return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) 461 } 462 463 // Put key value ... 464 func (c *Client) Put(k, v string, opts ...clientv3.OpOption) error { 465 err := c.put(k, v, opts...) 466 return perrors.WithMessagef(err, "Put k/v (key: %s value %s)", k, v) 467 } 468 469 // Update key value ... 470 func (c *Client) UpdateWithRev(k, v string, rev int64, opts ...clientv3.OpOption) error { 471 err := c.updateWithRev(k, v, rev, opts...) 472 return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) 473 } 474 475 // Delete key 476 func (c *Client) Delete(k string) error { 477 err := c.delete(k) 478 return perrors.WithMessagef(err, "delete k/v (key %s)", k) 479 } 480 481 // RegisterTemp registers a temporary node 482 func (c *Client) RegisterTemp(k, v string) error { 483 err := c.keepAliveKV(k, v) 484 return perrors.WithMessagef(err, "keepalive kv (key %s)", k) 485 } 486 487 // GetChildrenKVList gets children kv list by @k 488 func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { 489 kList, vList, err := c.GetChildren(k) 490 return kList, vList, perrors.WithMessagef(err, "get key children (key %s)", k) 491 } 492 493 // GetValAndRev gets value and revision by @k 494 func (c *Client) GetValAndRev(k string) (string, int64, error) { 495 v, rev, err := c.getValAndRev(k) 496 return v, rev, perrors.WithMessagef(err, "get key value (key %s)", k) 497 } 498 499 // Get gets value by @k 500 func (c *Client) Get(k string) (string, error) { 501 v, _, err := c.getValAndRev(k) 502 return v, perrors.WithMessagef(err, "get key value (key %s)", k) 503 } 504 505 // Watch watches on spec key 506 func (c *Client) Watch(k string) (clientv3.WatchChan, error) { 507 wc, err := c.watchWithOption(k) 508 return wc, perrors.WithMessagef(err, "watch (key %s)", k) 509 } 510 511 // WatchWithPrefix watches on spec prefix 512 func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) { 513 if !strings.HasSuffix(prefix, "/") { 514 prefix += "/" 515 } 516 517 wc, err := c.watchWithOption(prefix, clientv3.WithPrefix()) 518 return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) 519 } 520 521 // Watch watches on spc key with OpOption 522 func (c *Client) WatchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) { 523 wc, err := c.watchWithOption(k, opts...) 524 return wc, perrors.WithMessagef(err, "watch (key %s)", k) 525 }