code: func getInfo(ctx context.Context, etcdCli *clientv3.Client, key string, retryCnt int, timeout time.Duration, opts ...clientv3.OpOption) (map[string]*ServerInfo, error) {
  	var err error
  	var resp *clientv3.GetResponse
  	allInfo := make(map[string]*ServerInfo)
  	for i := 0; i < retryCnt; i++ {
  		select {
  		case <-ctx.Done():
  			err = errors.Trace(ctx.Err())
  			return nil, err
  		default:
  		}
  		childCtx, cancel := context.WithTimeout(ctx, timeout)
  		resp, err = etcdCli.Get(childCtx, key, opts...)
  		cancel()
  		if err != nil {
  			logutil.Logger(context.Background()).Info("get key failed", zap.String("key", key), zap.Error(err))
  			time.Sleep(200 * time.Millisecond)
  			continue
  		}
  		for _, kv := range resp.Kvs {
  			info := &ServerInfo{}
  			err = json.Unmarshal(kv.Value, info)
  			if err != nil {
  				logutil.Logger(context.Background()).Info("get key failed", zap.String("key", string(kv.Key)), zap.ByteString("value", kv.Value),
  					zap.Error(err))
  				return nil, errors.Trace(err)
  			}
  			allInfo[info.ID] = info
  		}
  		return allInfo, nil
  	}
  	return nil, errors.Trace(err)
  }
docs: // getInfo gets server information from etcd according to the key and opts.