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 := ®istry.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 = ®istry.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 }