github.com/volts-dev/volts@v0.0.0-20240120094013-5e9c65924106/registry/etcd/etcd.go (about)

     1  // Package etcd provides an etcd service registry
     2  package etcd
     3  
     4  import (
     5  	"context"
     6  	"crypto/tls"
     7  	"encoding/json"
     8  	"errors"
     9  	"net"
    10  	"path"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	hash "github.com/mitchellh/hashstructure"
    17  	"github.com/volts-dev/volts/logger"
    18  	"github.com/volts-dev/volts/registry"
    19  	"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
    20  	clientv3 "go.etcd.io/etcd/client/v3"
    21  	"go.uber.org/zap"
    22  )
    23  
    24  var log = registry.Logger()
    25  
    26  var (
    27  	prefix = "/volts/registry/"
    28  )
    29  
    30  type etcdRegistry struct {
    31  	client *clientv3.Client
    32  	config *registry.Config
    33  	sync.RWMutex
    34  	register map[string]uint64
    35  	leases   map[string]clientv3.LeaseID
    36  }
    37  
    38  func init() {
    39  	registry.Register("etcd", New)
    40  }
    41  
    42  func New(opts ...registry.Option) registry.IRegistry {
    43  	var defaultOpts []registry.Option
    44  	defaultOpts = append(defaultOpts,
    45  		registry.WithName("etcd"),
    46  		registry.Timeout(time.Millisecond*100),
    47  	)
    48  
    49  	reg := &etcdRegistry{
    50  		config:   registry.NewConfig(append(defaultOpts, opts...)...),
    51  		register: make(map[string]uint64),
    52  		leases:   make(map[string]clientv3.LeaseID),
    53  	}
    54  
    55  	configure(reg)
    56  	return reg
    57  }
    58  
    59  func configure(e *etcdRegistry) error {
    60  	config := clientv3.Config{
    61  		Endpoints: []string{"127.0.0.1:2379"}, // etcd 默认地址
    62  	}
    63  
    64  	e.config.Name = e.String()
    65  
    66  	if e.config.Timeout == 0 {
    67  		e.config.Timeout = 5 * time.Second
    68  	}
    69  
    70  	if e.config.Secure || e.config.TlsConfig != nil {
    71  		tlsConfig := e.config.TlsConfig
    72  		if tlsConfig == nil {
    73  			tlsConfig = &tls.Config{
    74  				InsecureSkipVerify: true,
    75  			}
    76  		}
    77  
    78  		config.TLS = tlsConfig
    79  	}
    80  
    81  	if e.config.Context != nil {
    82  		u, ok := e.config.Context.Value(authKey{}).(*authCreds)
    83  		if ok {
    84  			config.Username = u.Username
    85  			config.Password = u.Password
    86  		}
    87  		cfg, ok := e.config.Context.Value(logConfigKey{}).(*zap.Config)
    88  		if ok && cfg != nil {
    89  			config.LogConfig = cfg
    90  		}
    91  	}
    92  
    93  	var cAddrs []string
    94  	for _, address := range e.config.Addrs {
    95  		if len(address) == 0 {
    96  			continue
    97  		}
    98  		addr, port, err := net.SplitHostPort(address)
    99  		if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
   100  			port = "2379"
   101  			addr = address
   102  			cAddrs = append(cAddrs, net.JoinHostPort(addr, port))
   103  		} else if err == nil {
   104  			cAddrs = append(cAddrs, net.JoinHostPort(addr, port))
   105  		}
   106  	}
   107  
   108  	// if we got addrs then we'll update
   109  	if len(cAddrs) > 0 {
   110  		config.Endpoints = cAddrs
   111  	}
   112  
   113  	cli, err := clientv3.New(config)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	e.client = cli
   118  	return nil
   119  }
   120  
   121  func encode(s *registry.Service) string {
   122  	b, _ := json.Marshal(s)
   123  	return string(b)
   124  }
   125  
   126  func decode(ds []byte) *registry.Service {
   127  	var s *registry.Service
   128  	err := json.Unmarshal(ds, &s)
   129  	if err != nil {
   130  		log.Errf("decode service failed with error: %s - %s", err.Error(), string(ds))
   131  	}
   132  	return s
   133  }
   134  
   135  func nodePath(s, id string) string {
   136  	service := strings.Replace(s, "/", "-", -1)
   137  	node := strings.Replace(id, "/", "-", -1)
   138  	return path.Join(prefix, service, node)
   139  }
   140  
   141  func servicePath(s string) string {
   142  	return path.Join(prefix, strings.Replace(s, "/", "-", -1))
   143  }
   144  
   145  func (e *etcdRegistry) Init(opts ...registry.Option) error {
   146  	e.config.Init(opts...)
   147  	return configure(e)
   148  }
   149  
   150  func (e *etcdRegistry) Config() *registry.Config {
   151  	return e.config
   152  }
   153  
   154  func (e *etcdRegistry) registerNode(s *registry.Service, node *registry.Node, opts ...registry.Option) error {
   155  	// check existing lease cache
   156  	e.RLock()
   157  	leaseID, ok := e.leases[s.Name+node.Id]
   158  	e.RUnlock()
   159  
   160  	if !ok {
   161  		// missing lease, check if the key exists
   162  		ctx, cancel := context.WithTimeout(context.Background(), e.config.Timeout)
   163  		defer cancel()
   164  
   165  		// look for the existing key
   166  		rsp, err := e.client.Get(ctx, nodePath(s.Name, node.Id), clientv3.WithSerializable())
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		// get the existing lease
   172  		for _, kv := range rsp.Kvs {
   173  			if kv.Lease > 0 {
   174  				leaseID = clientv3.LeaseID(kv.Lease)
   175  
   176  				// decode the existing node
   177  				srv := decode(kv.Value)
   178  				if srv == nil || len(srv.Nodes) == 0 {
   179  					continue
   180  				}
   181  
   182  				// create hash of service; uint64
   183  				h, err := hash.Hash(srv.Nodes[0], nil)
   184  				if err != nil {
   185  					continue
   186  				}
   187  
   188  				// save the info
   189  				e.Lock()
   190  				e.leases[s.Name+node.Id] = leaseID
   191  				e.register[s.Name+node.Id] = h
   192  				e.Unlock()
   193  
   194  				break
   195  			}
   196  		}
   197  	}
   198  
   199  	var leaseNotFound bool
   200  
   201  	// renew the lease if it exists
   202  	if leaseID > 0 {
   203  		log.Tracef("Renewing existing lease for %s %d", s.Name, leaseID)
   204  		if _, err := e.client.KeepAliveOnce(context.TODO(), leaseID); err != nil {
   205  			if err != rpctypes.ErrLeaseNotFound {
   206  				return err
   207  			}
   208  
   209  			log.Tracef("Lease not found for %s %d", s.Name, leaseID)
   210  			// lease not found do register
   211  			leaseNotFound = true
   212  		}
   213  	}
   214  
   215  	// create hash of service; uint64
   216  	h, err := hash.Hash(node, nil)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	// get existing hash for the service node
   222  	e.Lock()
   223  	v, ok := e.register[s.Name+node.Id]
   224  	e.Unlock()
   225  
   226  	// the service is unchanged, skip registering
   227  	if ok && v == h && !leaseNotFound {
   228  		if log.GetLevel() == logger.LevelTrace {
   229  			log.Tracef("Service %s node %s unchanged skipping registration", s.Name, node.Id)
   230  		}
   231  		return nil
   232  	}
   233  
   234  	service := &registry.Service{
   235  		Name:      s.Name,
   236  		Version:   s.Version,
   237  		Metadata:  s.Metadata,
   238  		Endpoints: s.Endpoints,
   239  		Nodes:     []*registry.Node{node},
   240  	}
   241  
   242  	var options registry.Config
   243  	for _, o := range opts {
   244  		o(&options)
   245  	}
   246  
   247  	ctx, cancel := context.WithTimeout(context.Background(), e.config.Timeout)
   248  	defer cancel()
   249  
   250  	var lgr *clientv3.LeaseGrantResponse
   251  	if options.TTL.Seconds() > 0 {
   252  		// get a lease used to expire keys since we have a ttl
   253  		lgr, err = e.client.Grant(ctx, int64(options.TTL.Seconds()))
   254  		if err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	//if logger.V(logger.TraceLevel, logger.DefaultLogger) {
   260  	log.Dbgf("Registering %s id %s with lease %v and leaseID %v and ttl %v", service.Name, node.Id, lgr, lgr.ID, options.TTL)
   261  	//}
   262  	// create an entry for the node
   263  	if lgr != nil {
   264  		_, err = e.client.Put(ctx, nodePath(service.Name, node.Id), encode(service), clientv3.WithLease(lgr.ID))
   265  	} else {
   266  		_, err = e.client.Put(ctx, nodePath(service.Name, node.Id), encode(service))
   267  	}
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	e.Lock()
   273  	// save our hash of the service
   274  	e.register[s.Name+node.Id] = h
   275  	// save our leaseID of the service
   276  	if lgr != nil {
   277  		e.leases[s.Name+node.Id] = lgr.ID
   278  	}
   279  	e.Unlock()
   280  
   281  	return nil
   282  }
   283  
   284  func (e *etcdRegistry) Deregister(s *registry.Service, opts ...registry.Option) error {
   285  	if len(s.Nodes) == 0 {
   286  		return errors.New("Require at least one node")
   287  	}
   288  
   289  	for _, node := range s.Nodes {
   290  		e.Lock()
   291  		// delete our hash of the service
   292  		delete(e.register, s.Name+node.Id)
   293  		// delete our lease of the service
   294  		delete(e.leases, s.Name+node.Id)
   295  		e.Unlock()
   296  
   297  		ctx, cancel := context.WithTimeout(context.Background(), e.config.Timeout)
   298  		defer cancel()
   299  
   300  		//if logger.V(logger.TraceLevel, logger.DefaultLogger) {
   301  		log.Dbgf("Deregistering %s id %s", s.Name, node.Id)
   302  		//}
   303  		_, err := e.client.Delete(ctx, nodePath(s.Name, node.Id))
   304  		if err != nil {
   305  			return err
   306  		}
   307  	}
   308  
   309  	return nil
   310  }
   311  
   312  func (e *etcdRegistry) Register(s *registry.Service, opts ...registry.Option) error {
   313  	if len(s.Nodes) == 0 {
   314  		return errors.New("Require at least one node")
   315  	}
   316  	var gerr error
   317  
   318  	// register each node individually
   319  	for _, node := range s.Nodes {
   320  		if err := e.registerNode(s, node, opts...); err != nil {
   321  			gerr = err
   322  		}
   323  	}
   324  	if gerr == nil {
   325  		e.config.LocalServices = append(e.config.LocalServices, s)
   326  	}
   327  
   328  	return gerr
   329  }
   330  
   331  func (m *etcdRegistry) LocalServices() []*registry.Service {
   332  	return m.config.LocalServices
   333  }
   334  
   335  func (e *etcdRegistry) GetService(name string) ([]*registry.Service, error) {
   336  	ctx, cancel := context.WithTimeout(context.Background(), e.config.Timeout)
   337  	defer cancel()
   338  
   339  	rsp, err := e.client.Get(ctx, servicePath(name)+"/", clientv3.WithPrefix(), clientv3.WithSerializable())
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	if len(rsp.Kvs) == 0 {
   345  		return nil, registry.ErrNotFound
   346  	}
   347  
   348  	serviceMap := map[string]*registry.Service{}
   349  
   350  	for _, n := range rsp.Kvs {
   351  		if sn := decode(n.Value); sn != nil {
   352  			s, ok := serviceMap[sn.Version]
   353  			if !ok {
   354  				s = &registry.Service{
   355  					Name:      sn.Name,
   356  					Version:   sn.Version,
   357  					Metadata:  sn.Metadata,
   358  					Endpoints: sn.Endpoints,
   359  				}
   360  				serviceMap[s.Version] = s
   361  			}
   362  
   363  			s.Nodes = append(s.Nodes, sn.Nodes...)
   364  		}
   365  	}
   366  
   367  	services := make([]*registry.Service, 0, len(serviceMap))
   368  	for _, service := range serviceMap {
   369  		services = append(services, service)
   370  	}
   371  
   372  	return services, nil
   373  }
   374  
   375  func (e *etcdRegistry) ListServices() ([]*registry.Service, error) {
   376  	versions := make(map[string]*registry.Service)
   377  
   378  	ctx, cancel := context.WithTimeout(context.Background(), e.config.Timeout)
   379  	defer cancel()
   380  
   381  	rsp, err := e.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSerializable())
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  
   386  	if len(rsp.Kvs) == 0 {
   387  		return []*registry.Service{}, nil
   388  	}
   389  
   390  	for _, n := range rsp.Kvs {
   391  		sn := decode(n.Value)
   392  		if sn == nil {
   393  			continue
   394  		}
   395  		v, ok := versions[sn.Name+sn.Version]
   396  		if !ok {
   397  			versions[sn.Name+sn.Version] = sn
   398  			continue
   399  		}
   400  		// append to service:version nodes
   401  		v.Nodes = append(v.Nodes, sn.Nodes...)
   402  	}
   403  
   404  	services := make([]*registry.Service, 0, len(versions))
   405  	for _, service := range versions {
   406  		services = append(services, service)
   407  	}
   408  
   409  	// sort the services
   410  	sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
   411  
   412  	return services, nil
   413  }
   414  
   415  func (e *etcdRegistry) Watcher(opts ...registry.WatchOptions) (registry.Watcher, error) {
   416  	return newEtcdWatcher(e, e.config.Timeout, opts...)
   417  }
   418  
   419  func (e *etcdRegistry) String() string {
   420  	return e.config.Name
   421  }