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

     1  // Package cache provides a registry cache
     2  package cacher
     3  
     4  import (
     5  	"math"
     6  	"math/rand"
     7  	"sync"
     8  	"time"
     9  
    10  	util "github.com/volts-dev/volts/internal/registry"
    11  	"github.com/volts-dev/volts/logger"
    12  	"github.com/volts-dev/volts/registry"
    13  	"golang.org/x/sync/singleflight"
    14  )
    15  
    16  var log = registry.Logger()
    17  
    18  // Cache is the registry cache interface
    19  type (
    20  	ICacher interface {
    21  		// embed the registry interface
    22  		registry.IRegistry
    23  		Match(endpoint string) ([]*registry.Service, error)
    24  		// stop the cache watcher
    25  		Stop()
    26  	}
    27  
    28  	cache struct {
    29  		registry.IRegistry
    30  		config *registry.Config
    31  
    32  		// registry cache
    33  		sync.RWMutex
    34  		cache       map[string][]*registry.Service
    35  		endpointMap map[string]string // 缓存ep
    36  		ttls        map[string]time.Time
    37  		watched     map[string]bool
    38  
    39  		// used to stop the cache
    40  		exit chan bool
    41  
    42  		// indicate whether its running
    43  		running bool
    44  		// status of the registry
    45  		// used to hold onto the cache
    46  		// in failure state
    47  		status error
    48  		// used to prevent cache breakdwon
    49  		sg singleflight.Group
    50  	}
    51  )
    52  
    53  var (
    54  	DefaultTTL = time.Minute
    55  )
    56  
    57  // New returns a new cache
    58  func New(r registry.IRegistry, opts ...registry.Option) ICacher {
    59  	rand.Seed(time.Now().UnixNano())
    60  
    61  	var defaultOpts []registry.Option
    62  	defaultOpts = append(defaultOpts,
    63  		registry.WithName(""),
    64  		registry.RegisterTTL(DefaultTTL))
    65  
    66  	return &cache{
    67  		IRegistry:   r,
    68  		config:      registry.NewConfig(append(defaultOpts, opts...)...),
    69  		watched:     make(map[string]bool),
    70  		cache:       make(map[string][]*registry.Service),
    71  		endpointMap: make(map[string]string),
    72  		ttls:        make(map[string]time.Time),
    73  		exit:        make(chan bool),
    74  	}
    75  }
    76  
    77  func backoff(attempts int) time.Duration {
    78  	if attempts == 0 {
    79  		return time.Duration(0)
    80  	}
    81  	return time.Duration(math.Pow(10, float64(attempts))) * time.Millisecond
    82  }
    83  
    84  func (c *cache) getStatus() error {
    85  	c.RLock()
    86  	defer c.RUnlock()
    87  	return c.status
    88  }
    89  
    90  func (c *cache) setStatus(err error) {
    91  	c.Lock()
    92  	c.status = err
    93  	c.Unlock()
    94  }
    95  
    96  // isValid checks if the service is valid
    97  func (c *cache) isValid(services []*registry.Service, ttl time.Time) bool {
    98  	// no services exist
    99  	if len(services) == 0 {
   100  		return false
   101  	}
   102  
   103  	// ttl is invalid
   104  	if ttl.IsZero() {
   105  		return false
   106  	}
   107  
   108  	// time since ttl is longer than timeout
   109  	if time.Since(ttl) > 0 {
   110  		return false
   111  	}
   112  
   113  	// ok
   114  	return true
   115  }
   116  
   117  func (c *cache) quit() bool {
   118  	select {
   119  	case <-c.exit:
   120  		return true
   121  	default:
   122  		return false
   123  	}
   124  }
   125  
   126  func (c *cache) del(service string) {
   127  	// don't blow away cache in error state
   128  	if err := c.status; err != nil {
   129  		return
   130  	}
   131  	// otherwise delete entries
   132  	delete(c.cache, service)
   133  	delete(c.ttls, service)
   134  }
   135  
   136  func (c *cache) Contain(service string) bool {
   137  	_, has := c.cache[service]
   138  	return has
   139  }
   140  
   141  func (c *cache) get(service string) ([]*registry.Service, error) {
   142  	// read lock
   143  	c.RLock()
   144  
   145  	// check the cache first
   146  	services := c.cache[service]
   147  	// get cache ttl
   148  	ttl := c.ttls[service]
   149  	// make a copy
   150  	cp := util.Copy(services)
   151  
   152  	// got services && within ttl so return cache
   153  	if c.isValid(cp, ttl) {
   154  		c.RUnlock()
   155  		// return services
   156  		return cp, nil
   157  	}
   158  
   159  	// get does the actual request for a service and cache it
   160  	get := func(service string, cached []*registry.Service) ([]*registry.Service, error) {
   161  		// ask the registry
   162  		val, err, _ := c.sg.Do(service, func() (interface{}, error) {
   163  			return c.IRegistry.GetService(service)
   164  		})
   165  		services, _ := val.([]*registry.Service)
   166  		if err != nil {
   167  			// check the cache
   168  			if len(cached) > 0 {
   169  				// set the error status
   170  				c.setStatus(err)
   171  
   172  				// return the stale cache
   173  				return cached, nil
   174  			}
   175  			// otherwise return error
   176  			return nil, err
   177  		}
   178  
   179  		// reset the status
   180  		if err := c.getStatus(); err != nil {
   181  			c.setStatus(nil)
   182  		}
   183  
   184  		// cache results
   185  		c.Lock()
   186  		c.set(service, util.Copy(services))
   187  		c.Unlock()
   188  
   189  		return services, nil
   190  	}
   191  
   192  	// watch service if not watched
   193  	_, ok := c.watched[service]
   194  
   195  	// unlock the read lock
   196  	c.RUnlock()
   197  
   198  	// check if its being watched
   199  	if !ok {
   200  		c.Lock()
   201  
   202  		// set to watched
   203  		c.watched[service] = true
   204  
   205  		// only kick it off if not running
   206  		if !c.running {
   207  			go c.run()
   208  		}
   209  
   210  		c.Unlock()
   211  	}
   212  
   213  	// get and return services
   214  	return get(service, cp)
   215  }
   216  
   217  func (c *cache) set(service string, services []*registry.Service) {
   218  	c.cache[service] = services
   219  	c.ttls[service] = time.Now().Add(c.config.TTL)
   220  }
   221  
   222  func (c *cache) update(res *registry.Result) {
   223  	if res == nil || res.Service == nil {
   224  		return
   225  	}
   226  
   227  	c.Lock()
   228  	defer c.Unlock()
   229  
   230  	// only save watched services
   231  	if _, ok := c.watched[res.Service.Name]; !ok {
   232  		return
   233  	}
   234  
   235  	services, ok := c.cache[res.Service.Name]
   236  	if !ok {
   237  		// we're not going to cache anything
   238  		// unless there was already a lookup
   239  		return
   240  	}
   241  
   242  	if len(res.Service.Nodes) == 0 {
   243  		switch res.Action {
   244  		case "delete":
   245  			c.del(res.Service.Name)
   246  		}
   247  		return
   248  	}
   249  
   250  	// existing service found
   251  	var service *registry.Service
   252  	var index int
   253  	for i, s := range services {
   254  		if s.Version == res.Service.Version {
   255  			service = s
   256  			index = i
   257  		}
   258  	}
   259  
   260  	switch res.Action {
   261  	case "create", "update":
   262  		if service == nil {
   263  			c.set(res.Service.Name, append(services, res.Service))
   264  			return
   265  		}
   266  
   267  		// append old nodes to new service
   268  		for _, cur := range service.Nodes {
   269  			var seen bool
   270  			for _, node := range res.Service.Nodes {
   271  				if cur.Id == node.Id {
   272  					seen = true
   273  					break
   274  				}
   275  			}
   276  			if !seen {
   277  				res.Service.Nodes = append(res.Service.Nodes, cur)
   278  			}
   279  		}
   280  
   281  		services[index] = res.Service
   282  		c.set(res.Service.Name, services)
   283  	case "delete":
   284  		if service == nil {
   285  			return
   286  		}
   287  
   288  		var nodes []*registry.Node
   289  
   290  		// filter cur nodes to remove the dead one
   291  		for _, cur := range service.Nodes {
   292  			var seen bool
   293  			for _, del := range res.Service.Nodes {
   294  				if del.Id == cur.Id {
   295  					seen = true
   296  					break
   297  				}
   298  			}
   299  			if !seen {
   300  				nodes = append(nodes, cur)
   301  			}
   302  		}
   303  
   304  		// still got nodes, save and return
   305  		if len(nodes) > 0 {
   306  			service.Nodes = nodes
   307  			services[index] = service
   308  			c.set(service.Name, services)
   309  			return
   310  		}
   311  
   312  		// zero nodes left
   313  
   314  		// only have one thing to delete
   315  		// nuke the thing
   316  		if len(services) == 1 {
   317  			c.del(service.Name)
   318  			return
   319  		}
   320  
   321  		// still have more than 1 service
   322  		// check the version and keep what we know
   323  		var srvs []*registry.Service
   324  		for _, s := range services {
   325  			if s.Version != service.Version {
   326  				srvs = append(srvs, s)
   327  			}
   328  		}
   329  
   330  		// save
   331  		c.set(service.Name, srvs)
   332  	case "override":
   333  		if service == nil {
   334  			return
   335  		}
   336  
   337  		c.del(service.Name)
   338  	}
   339  }
   340  
   341  // run starts the cache watcher loop
   342  // it creates a new watcher if there's a problem
   343  func (c *cache) run() {
   344  	c.Lock()
   345  	c.running = true
   346  	c.Unlock()
   347  
   348  	// reset watcher on exit
   349  	defer func() {
   350  		c.Lock()
   351  		c.watched = make(map[string]bool)
   352  		c.running = false
   353  		c.Unlock()
   354  	}()
   355  
   356  	var a, b int
   357  
   358  	for {
   359  		// exit early if already dead
   360  		if c.quit() {
   361  			return
   362  		}
   363  
   364  		// jitter before starting
   365  		j := rand.Int63n(100)
   366  		time.Sleep(time.Duration(j) * time.Millisecond)
   367  
   368  		// create new watcher
   369  		w, err := c.IRegistry.Watcher()
   370  		if err != nil {
   371  			if c.quit() {
   372  				return
   373  			}
   374  
   375  			d := backoff(a)
   376  			c.setStatus(err)
   377  
   378  			if a > 3 {
   379  				if log.GetLevel() == logger.LevelDebug {
   380  					log.Dbg("rcache: ", err, " backing off ", d)
   381  				}
   382  				a = 0
   383  			}
   384  
   385  			time.Sleep(d)
   386  			a++
   387  
   388  			continue
   389  		}
   390  
   391  		// reset a
   392  		a = 0
   393  
   394  		// watch for events
   395  		if err := c.watch(w); err != nil {
   396  			if c.quit() {
   397  				return
   398  			}
   399  
   400  			d := backoff(b)
   401  			c.setStatus(err)
   402  
   403  			if b > 3 {
   404  				if log.GetLevel() == logger.LevelDebug {
   405  					log.Dbg("rcache: ", err, " backing off ", d)
   406  				}
   407  				b = 0
   408  			}
   409  
   410  			time.Sleep(d)
   411  			b++
   412  
   413  			continue
   414  		}
   415  
   416  		// reset b
   417  		b = 0
   418  	}
   419  }
   420  
   421  // watch loops the next event and calls update
   422  // it returns if there's an error
   423  func (c *cache) watch(w registry.Watcher) error {
   424  	// not need watch
   425  	if w == nil {
   426  		return nil
   427  	}
   428  
   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  		c.update(res)
   460  	}
   461  }
   462  
   463  func (c *cache) match(endpoint string) ([]*registry.Service, error) {
   464  	c.RLock()
   465  	// check the cache first
   466  	service, has := c.endpointMap[endpoint]
   467  	if !has {
   468  		val, err, _ := c.sg.Do(endpoint, func() (interface{}, error) {
   469  			return c.IRegistry.ListServices()
   470  		})
   471  		if err != nil {
   472  			return nil, err
   473  		}
   474  
   475  		services, _ := val.([]*registry.Service)
   476  		// reset the status
   477  		if err := c.getStatus(); err != nil {
   478  			c.setStatus(nil)
   479  		}
   480  
   481  		for _, srv := range services {
   482  			for _, ep := range srv.Endpoints {
   483  				// 获取服务名称
   484  				if ep.Path == endpoint {
   485  					service = srv.Name
   486  				}
   487  
   488  				if srvName, has := c.endpointMap[ep.Path]; has {
   489  					if srvName != srv.Name {
   490  						log.Errf("same endpiont in diff service %s-%s", srvName, srv.Name)
   491  					}
   492  
   493  					continue
   494  				}
   495  				c.endpointMap[ep.Path] = srv.Name
   496  
   497  			}
   498  		}
   499  	}
   500  	c.RUnlock()
   501  	return c.get(service)
   502  }
   503  
   504  func (c *cache) Match(endpoint string) ([]*registry.Service, error) {
   505  	services, err := c.match(endpoint)
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  
   510  	// if there's nothing return err
   511  	if len(services) == 0 {
   512  		return nil, registry.ErrNotFound
   513  	}
   514  
   515  	// return services
   516  	return services, nil
   517  }
   518  
   519  func (c *cache) GetService(service string) ([]*registry.Service, error) {
   520  	// get the service
   521  	services, err := c.get(service)
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	// if there's nothing return err
   527  	if len(services) == 0 {
   528  		return nil, registry.ErrNotFound
   529  	}
   530  
   531  	// return services
   532  	return services, nil
   533  }
   534  
   535  func (c *cache) Stop() {
   536  	c.Lock()
   537  	defer c.Unlock()
   538  
   539  	select {
   540  	case <-c.exit:
   541  		return
   542  	default:
   543  		close(c.exit)
   544  	}
   545  }
   546  
   547  func (c *cache) String() string {
   548  	return c.config.Name
   549  }