Change redis client library to go-redis for redis component (#283)
* Change redis client library to go-redis for redis component to support TLS
This commit is contained in:
parent
910a8931f7
commit
fe9b90075a
|
|
@ -0,0 +1,16 @@
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
host string
|
||||||
|
password string
|
||||||
|
enableTLS bool
|
||||||
|
maxRetries int
|
||||||
|
maxRetryBackoff time.Duration
|
||||||
|
}
|
||||||
|
|
@ -7,10 +7,9 @@ package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -18,9 +17,9 @@ import (
|
||||||
"github.com/dapr/components-contrib/state"
|
"github.com/dapr/components-contrib/state"
|
||||||
"github.com/dapr/dapr/pkg/logger"
|
"github.com/dapr/dapr/pkg/logger"
|
||||||
|
|
||||||
"github.com/joomcode/redispipe/redis"
|
|
||||||
"github.com/joomcode/redispipe/redisconn"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
|
||||||
|
redis "github.com/go-redis/redis/v7"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -28,22 +27,30 @@ const (
|
||||||
delQuery = "local var1 = redis.pcall(\"HGET\", KEYS[1], \"version\"); if not var1 or type(var1)==\"table\" or var1 == ARGV[1] or var1 == \"\" or ARGV[1] == \"0\" then return redis.call(\"DEL\", KEYS[1]) else return error(\"failed to delete \" .. KEYS[1]) end"
|
delQuery = "local var1 = redis.pcall(\"HGET\", KEYS[1], \"version\"); if not var1 or type(var1)==\"table\" or var1 == ARGV[1] or var1 == \"\" or ARGV[1] == \"0\" then return redis.call(\"DEL\", KEYS[1]) else return error(\"failed to delete \" .. KEYS[1]) end"
|
||||||
connectedSlavesReplicas = "connected_slaves:"
|
connectedSlavesReplicas = "connected_slaves:"
|
||||||
infoReplicationDelimiter = "\r\n"
|
infoReplicationDelimiter = "\r\n"
|
||||||
|
host = "redisHost"
|
||||||
|
password = "redisPassword"
|
||||||
|
enableTLS = "enableTLS"
|
||||||
|
maxRetries = "maxRetries"
|
||||||
|
maxRetryBackoff = "maxRetryBackoff"
|
||||||
|
defaultBase = 10
|
||||||
|
defaultBitSize = 0
|
||||||
|
defaultDB = 0
|
||||||
|
defaultExpirationTime = 0
|
||||||
|
defaultMaxRetries = 3
|
||||||
|
defaultMaxRetryBackoff = time.Second * 2
|
||||||
|
defaultEnableTLS = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateStore is a Redis state store
|
// StateStore is a Redis state store
|
||||||
type StateStore struct {
|
type StateStore struct {
|
||||||
client *redis.SyncCtx
|
client *redis.Client
|
||||||
json jsoniter.API
|
json jsoniter.API
|
||||||
|
metadata metadata
|
||||||
replicas int
|
replicas int
|
||||||
|
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type credentials struct {
|
|
||||||
Host string `json:"redisHost"`
|
|
||||||
Password string `json:"redisPassword"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRedisStateStore returns a new redis state store
|
// NewRedisStateStore returns a new redis state store
|
||||||
func NewRedisStateStore(logger logger.Logger) *StateStore {
|
func NewRedisStateStore(logger logger.Logger) *StateStore {
|
||||||
return &StateStore{
|
return &StateStore{
|
||||||
|
|
@ -52,34 +59,77 @@ func NewRedisStateStore(logger logger.Logger) *StateStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseRedisMetadata(meta state.Metadata) (metadata, error) {
|
||||||
|
m := metadata{}
|
||||||
|
|
||||||
|
if val, ok := meta.Properties[host]; ok && val != "" {
|
||||||
|
m.host = val
|
||||||
|
} else {
|
||||||
|
return m, errors.New("redis store error: missing host address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := meta.Properties[password]; ok && val != "" {
|
||||||
|
m.password = val
|
||||||
|
}
|
||||||
|
|
||||||
|
m.enableTLS = defaultEnableTLS
|
||||||
|
if val, ok := meta.Properties[enableTLS]; ok && val != "" {
|
||||||
|
tls, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("redis store error: can't parse enableTLS field: %s", err)
|
||||||
|
}
|
||||||
|
m.enableTLS = tls
|
||||||
|
}
|
||||||
|
|
||||||
|
m.maxRetries = defaultMaxRetries
|
||||||
|
if val, ok := meta.Properties[maxRetries]; ok && val != "" {
|
||||||
|
parsedVal, err := strconv.ParseInt(val, defaultBase, defaultBitSize)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("redis store error: can't parse maxRetries field: %s", err)
|
||||||
|
}
|
||||||
|
m.maxRetries = int(parsedVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.maxRetryBackoff = defaultMaxRetryBackoff
|
||||||
|
if val, ok := meta.Properties[maxRetryBackoff]; ok && val != "" {
|
||||||
|
parsedVal, err := strconv.ParseInt(val, defaultBase, defaultBitSize)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("redis store error: can't parse maxRetries field: %s", err)
|
||||||
|
}
|
||||||
|
m.maxRetryBackoff = time.Duration(parsedVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Init does metadata and connection parsing
|
// Init does metadata and connection parsing
|
||||||
func (r *StateStore) Init(metadata state.Metadata) error {
|
func (r *StateStore) Init(metadata state.Metadata) error {
|
||||||
rand.Seed(time.Now().Unix())
|
|
||||||
|
|
||||||
connInfo := metadata.Properties
|
m, err := parseRedisMetadata(metadata)
|
||||||
b, err := json.Marshal(connInfo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.metadata = m
|
||||||
|
|
||||||
var redisCreds credentials
|
opts := &redis.Options{
|
||||||
err = json.Unmarshal(b, &redisCreds)
|
Addr: m.host,
|
||||||
|
Password: m.password,
|
||||||
|
DB: defaultDB,
|
||||||
|
MaxRetries: m.maxRetries,
|
||||||
|
MaxRetryBackoff: m.maxRetryBackoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #nosec */
|
||||||
|
if m.enableTLS {
|
||||||
|
opts.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: m.enableTLS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = redis.NewClient(opts)
|
||||||
|
_, err = r.client.Ping().Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("redis store: error connecting to redis at %s: %s", m.host, err)
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
opts := redisconn.Opts{
|
|
||||||
DB: 0,
|
|
||||||
Password: redisCreds.Password,
|
|
||||||
}
|
|
||||||
conn, err := redisconn.Connect(ctx, redisCreds.Host, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = &redis.SyncCtx{
|
|
||||||
S: conn,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.replicas, err = r.getConnectedSlaves()
|
r.replicas, err = r.getConnectedSlaves()
|
||||||
|
|
@ -88,8 +138,8 @@ func (r *StateStore) Init(metadata state.Metadata) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StateStore) getConnectedSlaves() (int, error) {
|
func (r *StateStore) getConnectedSlaves() (int, error) {
|
||||||
res := r.client.Do(context.Background(), "INFO replication")
|
res, err := r.client.DoContext(context.Background(), "INFO", "replication").Result()
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,9 +169,9 @@ func (r *StateStore) deleteValue(req *state.DeleteRequest) error {
|
||||||
if req.ETag == "" {
|
if req.ETag == "" {
|
||||||
req.ETag = "0"
|
req.ETag = "0"
|
||||||
}
|
}
|
||||||
res := r.client.Do(context.Background(), "EVAL", delQuery, 1, req.Key, req.ETag)
|
_, err := r.client.DoContext(context.Background(), "EVAL", delQuery, 1, req.Key, req.ETag).Result()
|
||||||
|
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete key '%s' due to ETag mismatch", req.Key)
|
return fmt.Errorf("failed to delete key '%s' due to ETag mismatch", req.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,8 +200,8 @@ func (r *StateStore) BulkDelete(req []state.DeleteRequest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StateStore) directGet(req *state.GetRequest) (*state.GetResponse, error) {
|
func (r *StateStore) directGet(req *state.GetRequest) (*state.GetResponse, error) {
|
||||||
res := r.client.Do(context.Background(), "GET", req.Key)
|
res, err := r.client.DoContext(context.Background(), "GET", req.Key).Result()
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,8 +217,8 @@ func (r *StateStore) directGet(req *state.GetRequest) (*state.GetResponse, error
|
||||||
|
|
||||||
// Get retrieves state from redis with a key
|
// Get retrieves state from redis with a key
|
||||||
func (r *StateStore) Get(req *state.GetRequest) (*state.GetResponse, error) {
|
func (r *StateStore) Get(req *state.GetRequest) (*state.GetResponse, error) {
|
||||||
res := r.client.Do(context.Background(), "HGETALL", req.Key) // Prefer values with ETags
|
res, err := r.client.DoContext(context.Background(), "HGETALL", req.Key).Result() // Prefer values with ETags
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return r.directGet(req) //Falls back to original get
|
return r.directGet(req) //Falls back to original get
|
||||||
}
|
}
|
||||||
if res == nil {
|
if res == nil {
|
||||||
|
|
@ -211,14 +261,14 @@ func (r *StateStore) setValue(req *state.SetRequest) error {
|
||||||
bt, _ = r.json.Marshal(req.Value)
|
bt, _ = r.json.Marshal(req.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := r.client.Do(context.Background(), "EVAL", setQuery, 1, req.Key, ver, bt)
|
_, err = r.client.DoContext(context.Background(), "EVAL", setQuery, 1, req.Key, ver, bt).Result()
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to set key %s: %s", req.Key, err)
|
return fmt.Errorf("failed to set key %s: %s", req.Key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Options.Consistency == state.Strong && r.replicas > 0 {
|
if req.Options.Consistency == state.Strong && r.replicas > 0 {
|
||||||
res = r.client.Do(context.Background(), "WAIT", r.replicas, 1000)
|
_, err = r.client.DoContext(context.Background(), "WAIT", r.replicas, 1000).Result()
|
||||||
if err := redis.AsError(res); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("timed out while waiting for %v replicas to acknowledge write", r.replicas)
|
return fmt.Errorf("timed out while waiting for %v replicas to acknowledge write", r.replicas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -245,19 +295,20 @@ func (r *StateStore) BulkSet(req []state.SetRequest) error {
|
||||||
|
|
||||||
// Multi performs a transactional operation. succeeds only if all operations succeed, and fails if one or more operations fail
|
// Multi performs a transactional operation. succeeds only if all operations succeed, and fails if one or more operations fail
|
||||||
func (r *StateStore) Multi(operations []state.TransactionalRequest) error {
|
func (r *StateStore) Multi(operations []state.TransactionalRequest) error {
|
||||||
redisReqs := []redis.Request{}
|
|
||||||
|
pipe := r.client.TxPipeline()
|
||||||
for _, o := range operations {
|
for _, o := range operations {
|
||||||
if o.Operation == state.Upsert {
|
if o.Operation == state.Upsert {
|
||||||
req := o.Request.(state.SetRequest)
|
req := o.Request.(state.SetRequest)
|
||||||
b, _ := r.json.Marshal(req.Value)
|
b, _ := r.json.Marshal(req.Value)
|
||||||
redisReqs = append(redisReqs, redis.Req("SET", req.Key, b))
|
pipe.Set(req.Key, b, defaultExpirationTime)
|
||||||
} else if o.Operation == state.Delete {
|
} else if o.Operation == state.Delete {
|
||||||
req := o.Request.(state.DeleteRequest)
|
req := o.Request.(state.DeleteRequest)
|
||||||
redisReqs = append(redisReqs, redis.Req("DEL", req.Key))
|
pipe.Del(req.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := r.client.SendTransaction(context.Background(), redisReqs)
|
_, err := pipe.Exec()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue