github.com/thanos-io/thanos@v0.32.5/internal/cortex/chunk/cache/redis_client.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package cache 5 6 import ( 7 "context" 8 "crypto/tls" 9 "flag" 10 "net" 11 "strings" 12 "time" 13 "unsafe" 14 15 "github.com/pkg/errors" 16 "github.com/redis/rueidis" 17 18 "github.com/thanos-io/thanos/internal/cortex/util/flagext" 19 ) 20 21 // RedisConfig defines how a RedisCache should be constructed. 22 type RedisConfig struct { 23 Endpoint string `yaml:"endpoint"` 24 MasterName string `yaml:"master_name"` 25 Timeout time.Duration `yaml:"timeout"` 26 Expiration time.Duration `yaml:"expiration"` 27 DB int `yaml:"db"` 28 Password flagext.Secret `yaml:"password"` 29 EnableTLS bool `yaml:"tls_enabled"` 30 InsecureSkipVerify bool `yaml:"tls_insecure_skip_verify"` 31 } 32 33 // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet 34 func (cfg *RedisConfig) RegisterFlagsWithPrefix(prefix, description string, f *flag.FlagSet) { 35 f.StringVar(&cfg.Endpoint, prefix+"redis.endpoint", "", description+"Redis Server endpoint to use for caching. A comma-separated list of endpoints for Redis Cluster or Redis Sentinel. If empty, no redis will be used.") 36 f.StringVar(&cfg.MasterName, prefix+"redis.master-name", "", description+"Redis Sentinel master name. An empty string for Redis Server or Redis Cluster.") 37 f.DurationVar(&cfg.Timeout, prefix+"redis.timeout", 500*time.Millisecond, description+"Maximum time to wait before giving up on redis requests.") 38 f.DurationVar(&cfg.Expiration, prefix+"redis.expiration", 0, description+"How long keys stay in the redis.") 39 f.IntVar(&cfg.DB, prefix+"redis.db", 0, description+"Database index.") 40 f.Var(&cfg.Password, prefix+"redis.password", description+"Password to use when connecting to redis.") 41 f.BoolVar(&cfg.EnableTLS, prefix+"redis.tls-enabled", false, description+"Enable connecting to redis with TLS.") 42 f.BoolVar(&cfg.InsecureSkipVerify, prefix+"redis.tls-insecure-skip-verify", false, description+"Skip validating server certificate.") 43 } 44 45 type RedisClient struct { 46 expiration time.Duration 47 timeout time.Duration 48 rdb rueidis.Client 49 } 50 51 // NewRedisClient creates Redis client 52 func NewRedisClient(cfg *RedisConfig) (*RedisClient, error) { 53 clientOpts := rueidis.ClientOption{ 54 InitAddress: strings.Split(cfg.Endpoint, ","), 55 ShuffleInit: true, 56 Password: cfg.Password.Value, 57 SelectDB: cfg.DB, 58 Dialer: net.Dialer{Timeout: cfg.Timeout}, 59 ConnWriteTimeout: cfg.Timeout, 60 DisableCache: true, 61 } 62 if cfg.EnableTLS { 63 clientOpts.TLSConfig = &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify} 64 } 65 if cfg.MasterName != "" { 66 clientOpts.Sentinel = rueidis.SentinelOption{ 67 MasterSet: cfg.MasterName, 68 } 69 } 70 71 client, err := rueidis.NewClient(clientOpts) 72 if err != nil { 73 return nil, err 74 } 75 76 return &RedisClient{ 77 expiration: cfg.Expiration, 78 timeout: cfg.Timeout, 79 rdb: client, 80 }, nil 81 } 82 83 func (c *RedisClient) Ping(ctx context.Context) error { 84 var cancel context.CancelFunc 85 if c.timeout > 0 { 86 ctx, cancel = context.WithTimeout(ctx, c.timeout) 87 defer cancel() 88 } 89 90 resp := c.rdb.Do(ctx, c.rdb.B().Ping().Build()) 91 pingResp, err := resp.ToString() 92 if err != nil { 93 return errors.New("converting PING response to string") 94 } 95 if pingResp != "PONG" { 96 return errors.Errorf("redis: Unexpected PING response %q", pingResp) 97 } 98 return nil 99 } 100 101 func (c *RedisClient) MSet(ctx context.Context, keys []string, values [][]byte) error { 102 var cancel context.CancelFunc 103 if c.timeout > 0 { 104 ctx, cancel = context.WithTimeout(ctx, c.timeout) 105 defer cancel() 106 } 107 108 if len(keys) != len(values) { 109 return errors.Errorf("MSet the length of keys and values not equal, len(keys)=%d, len(values)=%d", len(keys), len(values)) 110 } 111 112 cmds := make(rueidis.Commands, 0, len(keys)) 113 for i := range keys { 114 cmds = append(cmds, c.rdb.B().Set().Key(keys[i]).Value(rueidis.BinaryString(values[i])).Ex(c.expiration).Build()) 115 } 116 for _, resp := range c.rdb.DoMulti(ctx, cmds...) { 117 if err := resp.Error(); err != nil { 118 return err 119 } 120 } 121 return nil 122 } 123 124 func (c *RedisClient) MGet(ctx context.Context, keys []string) ([][]byte, error) { 125 var cancel context.CancelFunc 126 if c.timeout > 0 { 127 ctx, cancel = context.WithTimeout(ctx, c.timeout) 128 defer cancel() 129 } 130 131 ret := make([][]byte, 0, len(keys)) 132 133 mgetRet, err := rueidis.MGet(c.rdb, ctx, keys) 134 if err != nil { 135 return nil, err 136 } 137 for _, k := range keys { 138 m, ok := mgetRet[k] 139 if !ok { 140 return nil, errors.Errorf("not found key %s in results", k) 141 } 142 if m.IsNil() { 143 ret = append(ret, nil) 144 continue 145 } 146 r, err := m.ToString() 147 if err != nil { 148 return nil, errors.Errorf("failed to convert %s resp to string", k) 149 } 150 ret = append(ret, stringToBytes(r)) 151 } 152 153 return ret, nil 154 } 155 156 func (c *RedisClient) Close() error { 157 c.rdb.Close() 158 return nil 159 } 160 161 func stringToBytes(s string) []byte { 162 return *(*[]byte)(unsafe.Pointer( 163 &struct { 164 string 165 Cap int 166 }{s, len(s)}, 167 )) 168 }