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 //-----------------------------------------------------------------------------