github.com/Jeffail/benthos/v3@v3.65.0/lib/cache/redis.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/docs"
     8  	bredis "github.com/Jeffail/benthos/v3/internal/impl/redis"
     9  	"github.com/Jeffail/benthos/v3/lib/log"
    10  	"github.com/Jeffail/benthos/v3/lib/metrics"
    11  	"github.com/Jeffail/benthos/v3/lib/types"
    12  	"github.com/go-redis/redis/v7"
    13  )
    14  
    15  //------------------------------------------------------------------------------
    16  
    17  func init() {
    18  	Constructors[TypeRedis] = TypeSpec{
    19  		constructor:       NewRedis,
    20  		SupportsPerKeyTTL: true,
    21  		Summary: `
    22  Use a Redis instance as a cache. The expiration can be set to zero or an empty
    23  string in order to set no expiration.`,
    24  		FieldSpecs: bredis.ConfigDocs().Add(
    25  			docs.FieldCommon("prefix", "An optional string to prefix item keys with in order to prevent collisions with similar services."),
    26  			docs.FieldCommon("expiration", "An optional period after which cached items will expire."),
    27  			docs.FieldAdvanced("retries", "The maximum number of retry attempts to make before abandoning a request."),
    28  			docs.FieldAdvanced("retry_period", "The duration to wait between retry attempts."),
    29  		),
    30  	}
    31  }
    32  
    33  //------------------------------------------------------------------------------
    34  
    35  // RedisConfig is a config struct for a redis connection.
    36  type RedisConfig struct {
    37  	bredis.Config `json:",inline" yaml:",inline"`
    38  	Prefix        string `json:"prefix" yaml:"prefix"`
    39  	Expiration    string `json:"expiration" yaml:"expiration"`
    40  	Retries       int    `json:"retries" yaml:"retries"`
    41  	RetryPeriod   string `json:"retry_period" yaml:"retry_period"`
    42  }
    43  
    44  // NewRedisConfig returns a RedisConfig with default values.
    45  func NewRedisConfig() RedisConfig {
    46  	return RedisConfig{
    47  		Config:      bredis.NewConfig(),
    48  		Prefix:      "",
    49  		Expiration:  "24h",
    50  		Retries:     3,
    51  		RetryPeriod: "500ms",
    52  	}
    53  }
    54  
    55  //------------------------------------------------------------------------------
    56  
    57  // Redis is a cache that connects to redis servers.
    58  type Redis struct {
    59  	conf  Config
    60  	log   log.Modular
    61  	stats metrics.Type
    62  
    63  	mLatency       metrics.StatTimer
    64  	mGetCount      metrics.StatCounter
    65  	mGetRetry      metrics.StatCounter
    66  	mGetFailed     metrics.StatCounter
    67  	mGetSuccess    metrics.StatCounter
    68  	mGetLatency    metrics.StatTimer
    69  	mGetNotFound   metrics.StatCounter
    70  	mSetCount      metrics.StatCounter
    71  	mSetRetry      metrics.StatCounter
    72  	mSetFailed     metrics.StatCounter
    73  	mSetSuccess    metrics.StatCounter
    74  	mSetLatency    metrics.StatTimer
    75  	mAddCount      metrics.StatCounter
    76  	mAddDupe       metrics.StatCounter
    77  	mAddRetry      metrics.StatCounter
    78  	mAddFailedDupe metrics.StatCounter
    79  	mAddFailedErr  metrics.StatCounter
    80  	mAddSuccess    metrics.StatCounter
    81  	mAddLatency    metrics.StatTimer
    82  	mDelCount      metrics.StatCounter
    83  	mDelRetry      metrics.StatCounter
    84  	mDelFailedErr  metrics.StatCounter
    85  	mDelNotFound   metrics.StatCounter
    86  	mDelSuccess    metrics.StatCounter
    87  	mDelLatency    metrics.StatTimer
    88  
    89  	client      redis.UniversalClient
    90  	ttl         time.Duration
    91  	prefix      string
    92  	retryPeriod time.Duration
    93  }
    94  
    95  // NewRedis returns a Redis processor.
    96  func NewRedis(
    97  	conf Config, mgr types.Manager, log log.Modular, stats metrics.Type,
    98  ) (types.Cache, error) {
    99  	var ttl time.Duration
   100  	if len(conf.Redis.Expiration) > 0 {
   101  		var err error
   102  		if ttl, err = time.ParseDuration(conf.Redis.Expiration); err != nil {
   103  			return nil, fmt.Errorf("failed to parse expiration: %v", err)
   104  		}
   105  	}
   106  
   107  	var retryPeriod time.Duration
   108  	if tout := conf.Redis.RetryPeriod; len(tout) > 0 {
   109  		var err error
   110  		if retryPeriod, err = time.ParseDuration(tout); err != nil {
   111  			return nil, fmt.Errorf("failed to parse retry period string: %v", err)
   112  		}
   113  	}
   114  
   115  	client, err := conf.Redis.Config.Client()
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	return &Redis{
   121  		conf:  conf,
   122  		log:   log,
   123  		stats: stats,
   124  
   125  		mLatency:       stats.GetTimer("latency"),
   126  		mGetCount:      stats.GetCounter("get.count"),
   127  		mGetRetry:      stats.GetCounter("get.retry"),
   128  		mGetFailed:     stats.GetCounter("get.failed.error"),
   129  		mGetNotFound:   stats.GetCounter("get.failed.not_found"),
   130  		mGetSuccess:    stats.GetCounter("get.success"),
   131  		mGetLatency:    stats.GetTimer("get.latency"),
   132  		mSetCount:      stats.GetCounter("set.count"),
   133  		mSetRetry:      stats.GetCounter("set.retry"),
   134  		mSetFailed:     stats.GetCounter("set.failed.error"),
   135  		mSetSuccess:    stats.GetCounter("set.success"),
   136  		mSetLatency:    stats.GetTimer("set.latency"),
   137  		mAddCount:      stats.GetCounter("add.count"),
   138  		mAddDupe:       stats.GetCounter("add.failed.duplicate"),
   139  		mAddRetry:      stats.GetCounter("add.retry"),
   140  		mAddFailedDupe: stats.GetCounter("add.failed.duplicate"),
   141  		mAddFailedErr:  stats.GetCounter("add.failed.error"),
   142  		mAddSuccess:    stats.GetCounter("add.success"),
   143  		mAddLatency:    stats.GetTimer("add.latency"),
   144  		mDelCount:      stats.GetCounter("delete.count"),
   145  		mDelRetry:      stats.GetCounter("delete.retry"),
   146  		mDelFailedErr:  stats.GetCounter("delete.failed.error"),
   147  		mDelNotFound:   stats.GetCounter("delete.failed.not_found"),
   148  		mDelSuccess:    stats.GetCounter("delete.success"),
   149  		mDelLatency:    stats.GetTimer("delete.latency"),
   150  
   151  		retryPeriod: retryPeriod,
   152  		ttl:         ttl,
   153  		prefix:      conf.Redis.Prefix,
   154  		client:      client,
   155  	}, nil
   156  }
   157  
   158  //------------------------------------------------------------------------------
   159  
   160  // Get attempts to locate and return a cached value by its key, returns an error
   161  // if the key does not exist or if the operation failed.
   162  func (r *Redis) Get(key string) ([]byte, error) {
   163  	r.mGetCount.Incr(1)
   164  	tStarted := time.Now()
   165  
   166  	key = r.prefix + key
   167  
   168  	res, err := r.client.Get(key).Result()
   169  	if err == redis.Nil {
   170  		r.mGetNotFound.Incr(1)
   171  		return nil, types.ErrKeyNotFound
   172  	}
   173  
   174  	for i := 0; i < r.conf.Redis.Retries && err != nil; i++ {
   175  		r.log.Errorf("Get command failed: %v\n", err)
   176  		<-time.After(r.retryPeriod)
   177  		r.mGetRetry.Incr(1)
   178  		res, err = r.client.Get(key).Result()
   179  		if err == redis.Nil {
   180  			r.mGetNotFound.Incr(1)
   181  			return nil, types.ErrKeyNotFound
   182  		}
   183  	}
   184  
   185  	latency := int64(time.Since(tStarted))
   186  	r.mGetLatency.Timing(latency)
   187  	r.mLatency.Timing(latency)
   188  
   189  	if err != nil {
   190  		r.mGetFailed.Incr(1)
   191  		return nil, err
   192  	}
   193  
   194  	r.mGetSuccess.Incr(1)
   195  	return []byte(res), nil
   196  }
   197  
   198  // SetWithTTL attempts to set the value of a key.
   199  func (r *Redis) SetWithTTL(key string, value []byte, ttl *time.Duration) error {
   200  	r.mSetCount.Incr(1)
   201  	tStarted := time.Now()
   202  
   203  	key = r.prefix + key
   204  
   205  	var t time.Duration
   206  	if ttl != nil {
   207  		t = *ttl
   208  	} else {
   209  		t = r.ttl
   210  	}
   211  	err := r.client.Set(key, value, t).Err()
   212  	for i := 0; i < r.conf.Redis.Retries && err != nil; i++ {
   213  		r.log.Errorf("Set command failed: %v\n", err)
   214  		<-time.After(r.retryPeriod)
   215  		r.mSetRetry.Incr(1)
   216  		err = r.client.Set(key, value, t).Err()
   217  	}
   218  	if err != nil {
   219  		r.mSetFailed.Incr(1)
   220  	} else {
   221  		r.mSetSuccess.Incr(1)
   222  	}
   223  
   224  	latency := int64(time.Since(tStarted))
   225  	r.mSetLatency.Timing(latency)
   226  	r.mLatency.Timing(latency)
   227  
   228  	return err
   229  }
   230  
   231  // Set attempts to set the value of a key.
   232  func (r *Redis) Set(key string, value []byte) error {
   233  	return r.SetWithTTL(key, value, nil)
   234  }
   235  
   236  // SetMultiWithTTL attempts to set the value of multiple keys, returns an error if any
   237  // keys fail.
   238  func (r *Redis) SetMultiWithTTL(items map[string]types.CacheTTLItem) error {
   239  	// TODO: Come back and optimise this.
   240  	for k, v := range items {
   241  		if err := r.SetWithTTL(k, v.Value, v.TTL); err != nil {
   242  			return err
   243  		}
   244  	}
   245  	return nil
   246  }
   247  
   248  // SetMulti attempts to set the value of multiple keys, returns an error if any
   249  // keys fail.
   250  func (r *Redis) SetMulti(items map[string][]byte) error {
   251  	sitems := make(map[string]types.CacheTTLItem, len(items))
   252  	for k, v := range items {
   253  		sitems[k] = types.CacheTTLItem{
   254  			Value: v,
   255  		}
   256  	}
   257  	return r.SetMultiWithTTL(sitems)
   258  }
   259  
   260  // AddWithTTL attempts to set the value of a key only if the key does not already exist
   261  // and returns an error if the key already exists or if the operation fails.
   262  func (r *Redis) AddWithTTL(key string, value []byte, ttl *time.Duration) error {
   263  	r.mAddCount.Incr(1)
   264  	tStarted := time.Now()
   265  
   266  	key = r.prefix + key
   267  
   268  	var t time.Duration
   269  	if ttl != nil {
   270  		t = *ttl
   271  	} else {
   272  		t = r.ttl
   273  	}
   274  	set, err := r.client.SetNX(key, value, t).Result()
   275  	if err == nil && !set {
   276  		r.mAddFailedDupe.Incr(1)
   277  
   278  		latency := int64(time.Since(tStarted))
   279  		r.mAddLatency.Timing(latency)
   280  		r.mLatency.Timing(latency)
   281  
   282  		return types.ErrKeyAlreadyExists
   283  	}
   284  	for i := 0; i < r.conf.Redis.Retries && err != nil; i++ {
   285  		r.log.Errorf("Add command failed: %v\n", err)
   286  		<-time.After(r.retryPeriod)
   287  		r.mAddRetry.Incr(1)
   288  		if set, err = r.client.SetNX(key, value, t).Result(); err == nil && !set {
   289  			r.mAddFailedDupe.Incr(1)
   290  
   291  			latency := int64(time.Since(tStarted))
   292  			r.mAddLatency.Timing(latency)
   293  			r.mLatency.Timing(latency)
   294  
   295  			return types.ErrKeyAlreadyExists
   296  		}
   297  	}
   298  	if err != nil {
   299  		r.mAddFailedErr.Incr(1)
   300  	} else {
   301  		r.mAddSuccess.Incr(1)
   302  	}
   303  
   304  	latency := int64(time.Since(tStarted))
   305  	r.mAddLatency.Timing(latency)
   306  	r.mLatency.Timing(latency)
   307  
   308  	return err
   309  }
   310  
   311  // Add attempts to set the value of a key only if the key does not already exist
   312  // and returns an error if the key already exists or if the operation fails.
   313  func (r *Redis) Add(key string, value []byte) error {
   314  	return r.AddWithTTL(key, value, nil)
   315  }
   316  
   317  // Delete attempts to remove a key.
   318  func (r *Redis) Delete(key string) error {
   319  	r.mDelCount.Incr(1)
   320  	tStarted := time.Now()
   321  
   322  	key = r.prefix + key
   323  
   324  	deleted, err := r.client.Del(key).Result()
   325  	if deleted == 0 {
   326  		r.mDelNotFound.Incr(1)
   327  		err = nil
   328  	}
   329  
   330  	for i := 0; i < r.conf.Redis.Retries && err != nil; i++ {
   331  		r.log.Errorf("Delete command failed: %v\n", err)
   332  		<-time.After(r.retryPeriod)
   333  		r.mDelRetry.Incr(1)
   334  		if deleted, err = r.client.Del(key).Result(); deleted == 0 {
   335  			r.mDelNotFound.Incr(1)
   336  			err = nil
   337  		}
   338  	}
   339  	if err != nil {
   340  		r.mDelFailedErr.Incr(1)
   341  	} else {
   342  		r.mDelSuccess.Incr(1)
   343  	}
   344  
   345  	latency := int64(time.Since(tStarted))
   346  	r.mDelLatency.Timing(latency)
   347  	r.mLatency.Timing(latency)
   348  
   349  	return err
   350  }
   351  
   352  // CloseAsync shuts down the cache.
   353  func (r *Redis) CloseAsync() {
   354  }
   355  
   356  // WaitForClose blocks until the cache has closed down.
   357  func (r *Redis) WaitForClose(timeout time.Duration) error {
   358  	r.client.Close()
   359  	return nil
   360  }
   361  
   362  //-----------------------------------------------------------------------------