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  }