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  }