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 }