github.com/sequix/cortex@v1.1.6/pkg/chunk/cache/memcached_client.go (about)

     1  package cache
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"net"
     7  	"sort"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/bradfitz/gomemcache/memcache"
    12  	"github.com/sequix/cortex/pkg/util"
    13  	"github.com/go-kit/kit/log/level"
    14  )
    15  
    16  // MemcachedClient interface exists for mocking memcacheClient.
    17  type MemcachedClient interface {
    18  	GetMulti(keys []string) (map[string]*memcache.Item, error)
    19  	Set(item *memcache.Item) error
    20  }
    21  
    22  type serverSelector interface {
    23  	memcache.ServerSelector
    24  	SetServers(servers ...string) error
    25  }
    26  
    27  // memcachedClient is a memcache client that gets its server list from SRV
    28  // records, and periodically updates that ServerList.
    29  type memcachedClient struct {
    30  	*memcache.Client
    31  	serverList serverSelector
    32  	hostname   string
    33  	service    string
    34  
    35  	quit chan struct{}
    36  	wait sync.WaitGroup
    37  }
    38  
    39  // MemcachedClientConfig defines how a MemcachedClient should be constructed.
    40  type MemcachedClientConfig struct {
    41  	Host           string        `yaml:"host,omitempty"`
    42  	Service        string        `yaml:"service,omitempty"`
    43  	Timeout        time.Duration `yaml:"timeout,omitempty"`
    44  	MaxIdleConns   int           `yaml:"max_idle_conns,omitempty"`
    45  	UpdateInterval time.Duration `yaml:"update_interval,omitempty"`
    46  	ConsistentHash bool          `yaml:"consistent_hash,omitempty"`
    47  }
    48  
    49  // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet
    50  func (cfg *MemcachedClientConfig) RegisterFlagsWithPrefix(prefix, description string, f *flag.FlagSet) {
    51  	f.StringVar(&cfg.Host, prefix+"memcached.hostname", "", description+"Hostname for memcached service to use when caching chunks. If empty, no memcached will be used.")
    52  	f.StringVar(&cfg.Service, prefix+"memcached.service", "memcached", description+"SRV service used to discover memcache servers.")
    53  	f.IntVar(&cfg.MaxIdleConns, prefix+"memcached.max-idle-conns", 16, description+"Maximum number of idle connections in pool.")
    54  	f.DurationVar(&cfg.Timeout, prefix+"memcached.timeout", 100*time.Millisecond, description+"Maximum time to wait before giving up on memcached requests.")
    55  	f.DurationVar(&cfg.UpdateInterval, prefix+"memcached.update-interval", 1*time.Minute, description+"Period with which to poll DNS for memcache servers.")
    56  	f.BoolVar(&cfg.ConsistentHash, prefix+"memcached.consistent-hash", false, description+"Use consistent hashing to distribute to memcache servers.")
    57  }
    58  
    59  // NewMemcachedClient creates a new MemcacheClient that gets its server list
    60  // from SRV and updates the server list on a regular basis.
    61  func NewMemcachedClient(cfg MemcachedClientConfig) MemcachedClient {
    62  	var selector serverSelector
    63  	if cfg.ConsistentHash {
    64  		selector = &MemcachedJumpHashSelector{}
    65  	} else {
    66  		selector = &memcache.ServerList{}
    67  	}
    68  
    69  	client := memcache.NewFromSelector(selector)
    70  	client.Timeout = cfg.Timeout
    71  	client.MaxIdleConns = cfg.MaxIdleConns
    72  
    73  	newClient := &memcachedClient{
    74  		Client:     client,
    75  		serverList: selector,
    76  		hostname:   cfg.Host,
    77  		service:    cfg.Service,
    78  		quit:       make(chan struct{}),
    79  	}
    80  	err := newClient.updateMemcacheServers()
    81  	if err != nil {
    82  		level.Error(util.Logger).Log("msg", "error setting memcache servers to host", "host", cfg.Host, "err", err)
    83  	}
    84  
    85  	newClient.wait.Add(1)
    86  	go newClient.updateLoop(cfg.UpdateInterval)
    87  	return newClient
    88  }
    89  
    90  // Stop the memcache client.
    91  func (c *memcachedClient) Stop() {
    92  	close(c.quit)
    93  	c.wait.Wait()
    94  }
    95  
    96  func (c *memcachedClient) updateLoop(updateInterval time.Duration) error {
    97  	defer c.wait.Done()
    98  	ticker := time.NewTicker(updateInterval)
    99  	var err error
   100  	for {
   101  		select {
   102  		case <-ticker.C:
   103  			err = c.updateMemcacheServers()
   104  			if err != nil {
   105  				level.Warn(util.Logger).Log("msg", "error updating memcache servers", "err", err)
   106  			}
   107  		case <-c.quit:
   108  			ticker.Stop()
   109  		}
   110  	}
   111  }
   112  
   113  // updateMemcacheServers sets a memcache server list from SRV records. SRV
   114  // priority & weight are ignored.
   115  func (c *memcachedClient) updateMemcacheServers() error {
   116  	_, addrs, err := net.LookupSRV(c.service, "tcp", c.hostname)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	var servers []string
   121  	for _, srv := range addrs {
   122  		servers = append(servers, fmt.Sprintf("%s:%d", srv.Target, srv.Port))
   123  	}
   124  	// ServerList deterministically maps keys to _index_ of the server list.
   125  	// Since DNS returns records in different order each time, we sort to
   126  	// guarantee best possible match between nodes.
   127  	sort.Strings(servers)
   128  	return c.serverList.SetServers(servers...)
   129  }