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  }