github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/registry/cache/cache.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/registry/cache/cache.go 14 15 // Package cache provides a registry cache 16 package cache 17 18 import ( 19 "math" 20 "math/rand" 21 "sync" 22 "time" 23 24 "github.com/tickoalcantara12/micro/v3/service/logger" 25 "github.com/tickoalcantara12/micro/v3/service/registry" 26 ) 27 28 // Cache is the registry cache interface 29 type Cache interface { 30 // embed the registry interface 31 registry.Registry 32 // stop the cache watcher 33 Stop() 34 } 35 36 type Options struct { 37 // TTL is the cache TTL 38 TTL time.Duration 39 } 40 41 type Option func(o *Options) 42 43 type cache struct { 44 registry.Registry 45 opts Options 46 47 // registry cache. services,ttls,watched,running are grouped by doman 48 sync.RWMutex 49 services map[string]services 50 ttls map[string]ttls 51 watched map[string]watched 52 running map[string]bool 53 54 // used to stop the caches 55 exit chan bool 56 57 // indicate whether its running status of the registry used to hold onto the cache in failure state 58 status error 59 } 60 61 type services map[string][]*registry.Service 62 type ttls map[string]time.Time 63 type watched map[string]bool 64 65 var defaultTTL = time.Minute 66 67 func backoff(attempts int) time.Duration { 68 if attempts == 0 { 69 return time.Duration(0) 70 } 71 return time.Duration(math.Pow(10, float64(attempts))) * time.Millisecond 72 } 73 74 func (c *cache) getStatus() error { 75 c.RLock() 76 defer c.RUnlock() 77 return c.status 78 } 79 80 func (c *cache) setStatus(err error) { 81 c.Lock() 82 c.status = err 83 c.Unlock() 84 } 85 86 // isValid checks if the service is valid 87 func (c *cache) isValid(services []*registry.Service, ttl time.Time) bool { 88 // no services exist 89 if len(services) == 0 { 90 return false 91 } 92 93 // ttl is invalid 94 if ttl.IsZero() { 95 return false 96 } 97 98 // time since ttl is longer than timeout 99 if time.Since(ttl) > 0 { 100 return false 101 } 102 103 // ok 104 return true 105 } 106 107 func (c *cache) quit() bool { 108 select { 109 case <-c.exit: 110 return true 111 default: 112 return false 113 } 114 } 115 116 func (c *cache) del(domain, service string) { 117 // don't blow away cache in error state 118 if err := c.getStatus(); err != nil { 119 return 120 } 121 122 c.Lock() 123 defer c.Unlock() 124 125 if _, ok := c.services[domain]; ok { 126 delete(c.services[domain], service) 127 } 128 129 if _, ok := c.ttls[domain]; ok { 130 delete(c.ttls[domain], service) 131 } 132 } 133 134 func (c *cache) get(domain, service string) ([]*registry.Service, error) { 135 var services []*registry.Service 136 var ttl time.Time 137 138 // lookup the values in the cache before calling the underlying registrry 139 c.RLock() 140 if srvs, ok := c.services[domain]; ok { 141 services = srvs[service] 142 } 143 if tt, ok := c.ttls[domain]; ok { 144 ttl = tt[service] 145 } 146 c.RUnlock() 147 148 // got services && within ttl so return a copy of the services 149 if c.isValid(services, ttl) { 150 return Copy(services), nil 151 } 152 153 // get does the actual request for a service and cache it 154 get := func(domain string, service string, cached []*registry.Service) ([]*registry.Service, error) { 155 // ask the registry 156 services, err := c.Registry.GetService(service, registry.GetDomain(domain)) 157 if err != nil { 158 // set the error status 159 c.setStatus(err) 160 161 // check the cache 162 if len(cached) > 0 { 163 return cached, nil 164 } 165 166 // otherwise return error 167 return nil, err 168 } 169 170 // reset the status 171 if err := c.getStatus(); err != nil { 172 c.setStatus(nil) 173 } 174 175 // cache results 176 c.set(domain, service, Copy(services)) 177 178 return services, nil 179 } 180 181 // watch service if not watched 182 c.RLock() 183 var ok bool 184 if _, d := c.watched[domain]; d { 185 if _, s := c.watched[domain][service]; s { 186 ok = true 187 } 188 } 189 c.RUnlock() 190 191 // check if its being watched 192 if !ok { 193 c.Lock() 194 195 // add domain if not registered 196 if _, ok := c.watched[domain]; !ok { 197 c.watched[domain] = make(map[string]bool) 198 } 199 200 // set to watched 201 c.watched[domain][service] = true 202 203 running := c.running[domain] 204 c.Unlock() 205 206 // only kick it off if not running 207 if !running { 208 go c.run(domain) 209 } 210 } 211 212 // get and return services 213 return get(domain, service, services) 214 } 215 216 func (c *cache) set(domain string, service string, srvs []*registry.Service) { 217 c.Lock() 218 defer c.Unlock() 219 220 if _, ok := c.services[domain]; !ok { 221 c.services[domain] = make(services) 222 } 223 if _, ok := c.ttls[domain]; !ok { 224 c.ttls[domain] = make(ttls) 225 } 226 227 c.services[domain][service] = srvs 228 c.ttls[domain][service] = time.Now().Add(c.opts.TTL) 229 } 230 231 func (c *cache) update(domain string, res *registry.Result) { 232 if res == nil || res.Service == nil { 233 return 234 } 235 236 // only save watched services since the service using the cache may only depend on a handful 237 // of other services 238 c.RLock() 239 if _, ok := c.watched[res.Service.Name]; !ok { 240 c.RUnlock() 241 return 242 } 243 244 // we're not going to cache anything unless there was already a lookup 245 services, ok := c.services[domain][res.Service.Name] 246 if !ok { 247 c.RUnlock() 248 return 249 } 250 251 c.RUnlock() 252 253 if len(res.Service.Nodes) == 0 { 254 switch res.Action { 255 case "delete": 256 c.del(domain, res.Service.Name) 257 } 258 return 259 } 260 261 // existing service found 262 var service *registry.Service 263 var index int 264 for i, s := range services { 265 if s.Version == res.Service.Version { 266 service = s 267 index = i 268 } 269 } 270 271 switch res.Action { 272 case "create", "update": 273 if service == nil { 274 c.set(domain, res.Service.Name, append(services, res.Service)) 275 return 276 } 277 278 // append old nodes to new service 279 for _, cur := range service.Nodes { 280 var seen bool 281 for _, node := range res.Service.Nodes { 282 if cur.Id == node.Id { 283 seen = true 284 break 285 } 286 } 287 if !seen { 288 res.Service.Nodes = append(res.Service.Nodes, cur) 289 } 290 } 291 292 services[index] = res.Service 293 c.set(domain, res.Service.Name, services) 294 case "delete": 295 if service == nil { 296 return 297 } 298 299 var nodes []*registry.Node 300 301 // filter cur nodes to remove the dead one 302 for _, cur := range service.Nodes { 303 var seen bool 304 for _, del := range res.Service.Nodes { 305 if del.Id == cur.Id { 306 seen = true 307 break 308 } 309 } 310 if !seen { 311 nodes = append(nodes, cur) 312 } 313 } 314 315 // still got nodes, save and return 316 if len(nodes) > 0 { 317 service.Nodes = nodes 318 services[index] = service 319 c.set(domain, service.Name, services) 320 return 321 } 322 323 // zero nodes left 324 325 // only have one thing to delete 326 // nuke the thing 327 if len(services) == 1 { 328 c.del(domain, service.Name) 329 return 330 } 331 332 // still have more than 1 service 333 // check the version and keep what we know 334 var srvs []*registry.Service 335 for _, s := range services { 336 if s.Version != service.Version { 337 srvs = append(srvs, s) 338 } 339 } 340 341 // save 342 c.set(domain, service.Name, srvs) 343 } 344 } 345 346 // run starts the cache watcher loop 347 // it creates a new watcher if there's a problem 348 func (c *cache) run(domain string) { 349 c.Lock() 350 c.running[domain] = true 351 c.Unlock() 352 353 // reset watcher on exit 354 defer func() { 355 c.Lock() 356 c.watched[domain] = make(map[string]bool) 357 c.running[domain] = false 358 c.Unlock() 359 }() 360 361 var a, b int 362 363 for { 364 // exit early if already dead 365 if c.quit() { 366 return 367 } 368 369 // jitter before starting 370 j := rand.Int63n(100) 371 time.Sleep(time.Duration(j) * time.Millisecond) 372 373 // create new watcher 374 w, err := c.Registry.Watch(registry.WatchDomain(domain)) 375 if err != nil { 376 if c.quit() { 377 return 378 } 379 380 d := backoff(a) 381 c.setStatus(err) 382 383 if a > 3 { 384 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 385 logger.Debug("rcache: ", err, " backing off ", d) 386 } 387 a = 0 388 } 389 390 time.Sleep(d) 391 a++ 392 393 continue 394 } 395 396 // reset a 397 a = 0 398 399 // watch for events 400 if err := c.watch(domain, w); err != nil { 401 if c.quit() { 402 return 403 } 404 405 d := backoff(b) 406 c.setStatus(err) 407 408 if b > 3 { 409 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 410 logger.Debug("rcache: ", err, " backing off ", d) 411 } 412 b = 0 413 } 414 415 time.Sleep(d) 416 b++ 417 418 continue 419 } 420 421 // reset b 422 b = 0 423 } 424 } 425 426 // watch loops the next event and calls update 427 // it returns if there's an error 428 func (c *cache) watch(domain string, w registry.Watcher) error { 429 // used to stop the watch 430 stop := make(chan bool) 431 432 // manage this loop 433 go func() { 434 defer w.Stop() 435 436 select { 437 // wait for exit 438 case <-c.exit: 439 return 440 // we've been stopped 441 case <-stop: 442 return 443 } 444 }() 445 446 for { 447 res, err := w.Next() 448 if err != nil { 449 close(stop) 450 return err 451 } 452 453 // reset the error status since we succeeded 454 if err := c.getStatus(); err != nil { 455 // reset status 456 c.setStatus(nil) 457 } 458 459 // for wildcard queries, the domain will be * and not the services domain, so we'll check to 460 // see if it was provided in the metadata. 461 dom := domain 462 if res.Service.Metadata != nil && len(res.Service.Metadata["domain"]) > 0 { 463 dom = res.Service.Metadata["domain"] 464 } 465 466 c.update(dom, res) 467 } 468 } 469 470 func (c *cache) GetService(service string, opts ...registry.GetOption) ([]*registry.Service, error) { 471 // parse the options, fallback to the default domain 472 var options registry.GetOptions 473 for _, o := range opts { 474 o(&options) 475 } 476 if len(options.Domain) == 0 { 477 options.Domain = registry.DefaultDomain 478 } 479 480 // get the service 481 services, err := c.get(options.Domain, service) 482 if err != nil { 483 return nil, err 484 } 485 486 // if there's nothing return err 487 if len(services) == 0 { 488 return nil, registry.ErrNotFound 489 } 490 491 // return services 492 return services, nil 493 } 494 495 func (c *cache) Stop() { 496 c.Lock() 497 defer c.Unlock() 498 499 select { 500 case <-c.exit: 501 return 502 default: 503 close(c.exit) 504 } 505 } 506 507 func (c *cache) String() string { 508 return "cache" 509 } 510 511 // New returns a new cache 512 func New(r registry.Registry, opts ...Option) Cache { 513 rand.Seed(time.Now().UnixNano()) 514 options := Options{ 515 TTL: defaultTTL, 516 } 517 518 for _, o := range opts { 519 o(&options) 520 } 521 522 return &cache{ 523 Registry: r, 524 opts: options, 525 running: make(map[string]bool), 526 watched: make(map[string]watched), 527 services: make(map[string]services), 528 ttls: make(map[string]ttls), 529 exit: make(chan bool), 530 } 531 }