From dbc468f7b68507633b32c9895cbfab72aabfed7d Mon Sep 17 00:00:00 2001 From: seeflood <349895584@qq.com> Date: Fri, 30 Jul 2021 10:50:02 +0800 Subject: [PATCH] Isolation and code reuse of components (#157) --- components/lock/etcd/etcd_lock.go | 149 +----------------- .../lock/redis/standalone_redis_lock.go | 93 +---------- components/pkg/utils/etcd.go | 144 +++++++++++++++++ components/pkg/utils/redis.go | 102 ++++++++++++ components/sequencer/const.go | 6 - components/sequencer/etcd/store.go | 145 ++--------------- components/sequencer/types.go | 4 - docs/_sidebar.md | 4 +- docs/en/api_reference/lock/reference.md | 78 ++++++++- docs/en/api_reference/pubsub/reference.md | 50 +++++- docs/en/api_reference/state/reference.md | 18 ++- docs/en/component_specs/lock/common.md | 8 +- docs/en/component_specs/pubsub/common.md | 31 ++++ docs/en/component_specs/sequencer/common.md | 8 +- docs/en/configuration/overview.md | 39 ++++- docs/img/configuration/layotto/img.png | Bin 0 -> 120174 bytes docs/zh/_sidebar.md | 6 +- docs/zh/api_reference/lock/reference.md | 78 ++++++++- docs/zh/api_reference/pubsub/reference.md | 51 +++++- docs/zh/api_reference/state/reference.md | 16 +- docs/zh/component_specs/lock/common.md | 8 +- docs/zh/component_specs/pubsub/common.md | 32 ++++ docs/zh/component_specs/sequencer/common.md | 8 +- docs/zh/configuration/overview.md | 39 ++++- pkg/grpc/api.go | 12 +- pkg/runtime/lock/lock_config.go | 14 +- pkg/runtime/lock/lock_config_test.go | 16 +- pkg/runtime/sequencer/utils.go | 26 +++ spec/proto/runtime/v1/runtime.proto | 14 +- 29 files changed, 767 insertions(+), 432 deletions(-) create mode 100644 components/pkg/utils/etcd.go create mode 100644 components/pkg/utils/redis.go delete mode 100644 components/sequencer/const.go create mode 100644 docs/en/component_specs/pubsub/common.md create mode 100644 docs/img/configuration/layotto/img.png create mode 100644 docs/zh/component_specs/pubsub/common.md create mode 100644 pkg/runtime/sequencer/utils.go diff --git a/components/lock/etcd/etcd_lock.go b/components/lock/etcd/etcd_lock.go index 4a6fc71e4d..d19a722989 100644 --- a/components/lock/etcd/etcd_lock.go +++ b/components/lock/etcd/etcd_lock.go @@ -2,38 +2,17 @@ package etcd import ( "context" - "crypto/tls" - "crypto/x509" - "errors" "fmt" - "io/ioutil" - "strconv" - "strings" - "time" - "go.etcd.io/etcd/client/v3" + "mosn.io/layotto/components/pkg/utils" "mosn.io/layotto/components/lock" "mosn.io/pkg/log" ) -const ( - defaultDialTimeout = 5 - defaultKeyPrefix = "/layotto/" - - prefixKey = "keyPrefixPath" - usernameKey = "username" - passwordKey = "password" - dialTimeoutKey = "dialTimeout" - endpointsKey = "endpoints" - tlsCertPathKey = "tlsCert" - tlsCertKeyPathKey = "tlsCertKey" - tlsCaPathKey = "tlsCa" -) - type EtcdLock struct { client *clientv3.Client - metadata metadata + metadata utils.EtcdMetadata features []lock.Feature logger log.ErrorLogger @@ -54,13 +33,13 @@ func NewEtcdLock(logger log.ErrorLogger) *EtcdLock { func (e *EtcdLock) Init(metadata lock.Metadata) error { // 1. parse config - m, err := parseEtcdMetadata(metadata) + m, err := utils.ParseEtcdMetadata(metadata.Properties) if err != nil { return err } e.metadata = m // 2. construct client - if e.client, err = e.newClient(m); err != nil { + if e.client, err = utils.NewEtcdClient(m); err != nil { return err } @@ -134,54 +113,8 @@ func (e *EtcdLock) Close() error { return e.client.Close() } -func (e *EtcdLock) newClient(meta metadata) (*clientv3.Client, error) { - - config := clientv3.Config{ - Endpoints: meta.endpoints, - DialTimeout: time.Second * time.Duration(meta.dialTimeout), - Username: meta.username, - Password: meta.password, - } - - if meta.tlsCa != "" || meta.tlsCert != "" || meta.tlsCertKey != "" { - //enable tls - cert, err := tls.LoadX509KeyPair(meta.tlsCert, meta.tlsCertKey) - if err != nil { - return nil, fmt.Errorf("error reading tls certificate, cert: %s, certKey: %s, err: %s", meta.tlsCert, meta.tlsCertKey, err) - } - - caData, err := ioutil.ReadFile(meta.tlsCa) - if err != nil { - return nil, fmt.Errorf("error reading tls ca %s, err: %s", meta.tlsCa, err) - } - - pool := x509.NewCertPool() - pool.AppendCertsFromPEM(caData) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: pool, - } - config.TLS = tlsConfig - } - - if client, err := clientv3.New(config); err != nil { - return nil, err - } else { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(meta.dialTimeout)) - defer cancel() - //ping - _, err = client.Get(ctx, "ping") - if err != nil { - return nil, fmt.Errorf("etcd lock error: connect to etcd timeoout %s", meta.endpoints) - } - - return client, nil - } -} - func (e *EtcdLock) getKey(resourceId string) string { - return fmt.Sprintf("%s%s", e.metadata.keyPrefix, resourceId) + return fmt.Sprintf("%s%s", e.metadata.KeyPrefix, resourceId) } func newInternalErrorUnlockResponse() *lock.UnlockResponse { @@ -189,75 +122,3 @@ func newInternalErrorUnlockResponse() *lock.UnlockResponse { Status: lock.INTERNAL_ERROR, } } - -func parseEtcdMetadata(meta lock.Metadata) (metadata, error) { - m := metadata{} - var err error - - if val, ok := meta.Properties[endpointsKey]; ok && val != "" { - m.endpoints = strings.Split(val, ";") - } else { - return m, errors.New("etcd lock error: missing endpoints address") - } - - if val, ok := meta.Properties[dialTimeoutKey]; ok && val != "" { - if m.dialTimeout, err = strconv.Atoi(val); err != nil { - return m, fmt.Errorf("etcd lock error: ncorrect dialTimeout value %s", val) - } - } else { - m.dialTimeout = defaultDialTimeout - } - - if val, ok := meta.Properties[prefixKey]; ok && val != "" { - m.keyPrefix = addPathSeparator(val) - } else { - m.keyPrefix = defaultKeyPrefix - } - - if val, ok := meta.Properties[usernameKey]; ok && val != "" { - m.username = val - } - - if val, ok := meta.Properties[passwordKey]; ok && val != "" { - m.password = val - } - - if val, ok := meta.Properties[tlsCaPathKey]; ok && val != "" { - m.tlsCa = val - } - - if val, ok := meta.Properties[tlsCertPathKey]; ok && val != "" { - m.tlsCert = val - } - - if val, ok := meta.Properties[tlsCertKeyPathKey]; ok && val != "" { - m.tlsCertKey = val - } - - return m, nil -} - -func addPathSeparator(p string) string { - if p == "" { - return "/" - } - if p[0] != '/' { - p = "/" + p - } - if p[len(p)-1] != '/' { - p = p + "/" - } - return p -} - -type metadata struct { - keyPrefix string - dialTimeout int - endpoints []string - username string - password string - - tlsCa string - tlsCert string - tlsCertKey string -} diff --git a/components/lock/redis/standalone_redis_lock.go b/components/lock/redis/standalone_redis_lock.go index 174466803c..a660730186 100644 --- a/components/lock/redis/standalone_redis_lock.go +++ b/components/lock/redis/standalone_redis_lock.go @@ -2,34 +2,18 @@ package redis import ( "context" - "crypto/tls" - "errors" "fmt" "github.com/go-redis/redis/v8" "mosn.io/layotto/components/lock" + "mosn.io/layotto/components/pkg/utils" "mosn.io/pkg/log" - "strconv" "time" ) -const ( - host = "redisHost" - password = "redisPassword" - enableTLS = "enableTLS" - maxRetries = "maxRetries" - maxRetryBackoff = "maxRetryBackoff" - defaultBase = 10 - defaultBitSize = 0 - defaultDB = 0 - defaultMaxRetries = 3 - defaultMaxRetryBackoff = time.Second * 2 - defaultEnableTLS = false -) - // Standalone Redis lock store.Any fail-over related features are not supported,such as Sentinel and Redis Cluster. type StandaloneRedisLock struct { client *redis.Client - metadata metadata + metadata utils.RedisMetadata replicas int features []lock.Feature @@ -51,37 +35,21 @@ func NewStandaloneRedisLock(logger log.ErrorLogger) *StandaloneRedisLock { func (p *StandaloneRedisLock) Init(metadata lock.Metadata) error { // 1. parse config - m, err := parseRedisMetadata(metadata) + m, err := utils.ParseRedisMetadata(metadata.Properties) if err != nil { return err } p.metadata = m // 2. construct client - p.client = p.newClient(m) + p.client = utils.NewRedisClient(m) p.ctx, p.cancel = context.WithCancel(context.Background()) // 3. connect to redis if _, err = p.client.Ping(p.ctx).Result(); err != nil { - return fmt.Errorf("[standaloneRedisLock]: error connecting to redis at %s: %s", m.host, err) + return fmt.Errorf("[standaloneRedisLock]: error connecting to redis at %s: %s", m.Host, err) } return err } -func (p *StandaloneRedisLock) newClient(m metadata) *redis.Client { - opts := &redis.Options{ - Addr: m.host, - Password: m.password, - DB: defaultDB, - MaxRetries: m.maxRetries, - MaxRetryBackoff: m.maxRetryBackoff, - } - if m.enableTLS { - opts.TLSConfig = &tls.Config{ - InsecureSkipVerify: m.enableTLS, - } - } - return redis.NewClient(opts) -} - func (p *StandaloneRedisLock) Features() []lock.Feature { return p.features } @@ -145,54 +113,3 @@ func (p *StandaloneRedisLock) Close() error { return p.client.Close() } - -func parseRedisMetadata(meta lock.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 maxRetryBackoff field: %s", err) - } - m.maxRetryBackoff = time.Duration(parsedVal) - } - - return m, nil -} - -type metadata struct { - host string - password string - maxRetries int - maxRetryBackoff time.Duration - enableTLS bool -} diff --git a/components/pkg/utils/etcd.go b/components/pkg/utils/etcd.go new file mode 100644 index 0000000000..29626bd534 --- /dev/null +++ b/components/pkg/utils/etcd.go @@ -0,0 +1,144 @@ +package utils + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + clientv3 "go.etcd.io/etcd/client/v3" + "io/ioutil" + "strconv" + "strings" + "time" +) + +const ( + defaultKeyPrefix = "/layotto/" + defaultDialTimeout = 5 + prefixKey = "keyPrefixPath" + usernameKey = "username" + passwordKey = "password" + dialTimeoutKey = "dialTimeout" + endpointsKey = "endpoints" + tlsCertPathKey = "tlsCert" + tlsCertKeyPathKey = "tlsCertKey" + tlsCaPathKey = "tlsCa" +) + +func ParseEtcdMetadata(properties map[string]string) (EtcdMetadata, error) { + m := EtcdMetadata{} + var err error + + if val, ok := properties[endpointsKey]; ok && val != "" { + m.Endpoints = strings.Split(val, ";") + } else { + return m, errors.New("etcd error: missing Endpoints address") + } + + if val, ok := properties[dialTimeoutKey]; ok && val != "" { + if m.DialTimeout, err = strconv.Atoi(val); err != nil { + return m, fmt.Errorf("etcd error: ncorrect DialTimeout value %s", val) + } + } else { + m.DialTimeout = defaultDialTimeout + } + + if val, ok := properties[prefixKey]; ok && val != "" { + m.KeyPrefix = addPathSeparator(val) + } else { + m.KeyPrefix = defaultKeyPrefix + } + + if val, ok := properties[usernameKey]; ok && val != "" { + m.Username = val + } + + if val, ok := properties[passwordKey]; ok && val != "" { + m.Password = val + } + + if val, ok := properties[tlsCaPathKey]; ok && val != "" { + m.TlsCa = val + } + + if val, ok := properties[tlsCertPathKey]; ok && val != "" { + m.TlsCert = val + } + + if val, ok := properties[tlsCertKeyPathKey]; ok && val != "" { + m.TlsCertKey = val + } + + return m, nil +} + +type EtcdMetadata struct { + KeyPrefix string + DialTimeout int + Endpoints []string + Username string + Password string + + TlsCa string + TlsCert string + TlsCertKey string +} + +func addPathSeparator(p string) string { + if p == "" { + return "/" + } + if p[0] != '/' { + p = "/" + p + } + if p[len(p)-1] != '/' { + p = p + "/" + } + return p +} + +func NewEtcdClient(meta EtcdMetadata) (*clientv3.Client, error) { + config := clientv3.Config{ + Endpoints: meta.Endpoints, + DialTimeout: time.Second * time.Duration(meta.DialTimeout), + Username: meta.Username, + Password: meta.Password, + } + + if meta.TlsCa != "" || meta.TlsCert != "" || meta.TlsCertKey != "" { + //enable tls + cert, err := tls.LoadX509KeyPair(meta.TlsCert, meta.TlsCertKey) + if err != nil { + return nil, fmt.Errorf("error reading tls certificate, cert: %s, certKey: %s, err: %s", meta.TlsCert, meta.TlsCertKey, err) + } + + caData, err := ioutil.ReadFile(meta.TlsCa) + if err != nil { + return nil, fmt.Errorf("error reading tls ca %s, err: %s", meta.TlsCa, err) + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(caData) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: pool, + } + config.TLS = tlsConfig + } + + if client, err := clientv3.New(config); err != nil { + return nil, err + } else { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(meta.DialTimeout)) + defer cancel() + //ping + _, err = client.Get(ctx, "ping") + if err != nil { + return nil, fmt.Errorf("etcd error: connect to etcd timeoout %s", meta.Endpoints) + } + + return client, nil + } +} diff --git a/components/pkg/utils/redis.go b/components/pkg/utils/redis.go new file mode 100644 index 0000000000..93eda38a33 --- /dev/null +++ b/components/pkg/utils/redis.go @@ -0,0 +1,102 @@ +package utils + +import ( + "crypto/tls" + "errors" + "fmt" + "github.com/go-redis/redis/v8" + "strconv" + "time" +) + +const ( + db = "db" + host = "redisHost" + password = "redisPassword" + enableTLS = "enableTLS" + maxRetries = "maxRetries" + maxRetryBackoff = "maxRetryBackoff" + defaultBase = 10 + defaultBitSize = 0 + defaultDB = 0 + defaultMaxRetries = 3 + defaultMaxRetryBackoff = time.Second * 2 + defaultEnableTLS = false +) + +func NewRedisClient(m RedisMetadata) *redis.Client { + opts := &redis.Options{ + Addr: m.Host, + Password: m.Password, + DB: m.DB, + MaxRetries: m.MaxRetries, + MaxRetryBackoff: m.MaxRetryBackoff, + } + if m.EnableTLS { + opts.TLSConfig = &tls.Config{ + InsecureSkipVerify: m.EnableTLS, + } + } + return redis.NewClient(opts) +} + +type RedisMetadata struct { + Host string + Password string + MaxRetries int + MaxRetryBackoff time.Duration + EnableTLS bool + DB int +} + +func ParseRedisMetadata(properties map[string]string) (RedisMetadata, error) { + m := RedisMetadata{} + + if val, ok := properties[host]; ok && val != "" { + m.Host = val + } else { + return m, errors.New("redis store error: missing host address") + } + + if val, ok := properties[password]; ok && val != "" { + m.Password = val + } + + m.EnableTLS = defaultEnableTLS + if val, ok := 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 := 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 := properties[maxRetryBackoff]; ok && val != "" { + parsedVal, err := strconv.ParseInt(val, defaultBase, defaultBitSize) + if err != nil { + return m, fmt.Errorf("redis store error: can't parse maxRetryBackoff field: %s", err) + } + m.MaxRetryBackoff = time.Duration(parsedVal) + } + + if val, ok := properties[db]; ok && val != "" { + parsedVal, err := strconv.Atoi(val) + if err != nil { + return m, fmt.Errorf("redis store error: can't parse db field: %s", err) + } + m.DB = parsedVal + } else { + m.DB = defaultDB + } + return m, nil +} diff --git a/components/sequencer/const.go b/components/sequencer/const.go deleted file mode 100644 index 53b22d800f..0000000000 --- a/components/sequencer/const.go +++ /dev/null @@ -1,6 +0,0 @@ -package sequencer - -const ( - BiggerThanKey = "biggerThan" - DefaultBiggerThan int64 = -1 -) diff --git a/components/sequencer/etcd/store.go b/components/sequencer/etcd/store.go index c712ada981..113603071f 100644 --- a/components/sequencer/etcd/store.go +++ b/components/sequencer/etcd/store.go @@ -2,35 +2,17 @@ package etcd import ( "context" - "crypto/tls" - "crypto/x509" - "errors" "fmt" clientv3 "go.etcd.io/etcd/client/v3" - "io/ioutil" + "mosn.io/layotto/components/pkg/utils" "mosn.io/layotto/components/sequencer" "mosn.io/pkg/log" - "strconv" - "strings" - "time" -) - -const ( - defaultDialTimeout = 5 - defaultKeyPrefix = "/layotto_sequencer/" - prefixKey = "keyPrefixPath" - usernameKey = "username" - passwordKey = "password" - dialTimeoutKey = "dialTimeout" - endpointsKey = "endpoints" - tlsCertPathKey = "tlsCert" - tlsCertKeyPathKey = "tlsCertKey" - tlsCaPathKey = "tlsCa" ) type EtcdSequencer struct { - client *clientv3.Client - metadata metadata + client *clientv3.Client + metadata utils.EtcdMetadata + biggerThan map[string]int64 logger log.ErrorLogger @@ -49,21 +31,23 @@ func NewEtcdSequencer(logger log.ErrorLogger) *EtcdSequencer { func (e *EtcdSequencer) Init(config sequencer.Configuration) error { // 1. parse config - m, err := parseEtcdMetadata(config) + m, err := utils.ParseEtcdMetadata(config.Properties) if err != nil { return err } e.metadata = m + e.biggerThan = config.BiggerThan + // 2. construct client - if e.client, err = e.newClient(m); err != nil { + if e.client, err = utils.NewEtcdClient(m); err != nil { return err } e.ctx, e.cancel = context.WithCancel(context.Background()) // 3. check biggerThan - if len(e.metadata.biggerThan) > 0 { + if len(e.biggerThan) > 0 { kv := clientv3.NewKV(e.client) - for k, bt := range e.metadata.biggerThan { + for k, bt := range e.biggerThan { if bt <= 0 { continue } @@ -118,102 +102,8 @@ func (e *EtcdSequencer) Close() error { return e.client.Close() } -func (e *EtcdSequencer) newClient(meta metadata) (*clientv3.Client, error) { - - config := clientv3.Config{ - Endpoints: meta.endpoints, - DialTimeout: time.Second * time.Duration(meta.dialTimeout), - Username: meta.username, - Password: meta.password, - } - - if meta.tlsCa != "" || meta.tlsCert != "" || meta.tlsCertKey != "" { - //enable tls - cert, err := tls.LoadX509KeyPair(meta.tlsCert, meta.tlsCertKey) - if err != nil { - return nil, fmt.Errorf("error reading tls certificate, cert: %s, certKey: %s, err: %s", meta.tlsCert, meta.tlsCertKey, err) - } - - caData, err := ioutil.ReadFile(meta.tlsCa) - if err != nil { - return nil, fmt.Errorf("error reading tls ca %s, err: %s", meta.tlsCa, err) - } - - pool := x509.NewCertPool() - pool.AppendCertsFromPEM(caData) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: pool, - } - config.TLS = tlsConfig - } - - if client, err := clientv3.New(config); err != nil { - return nil, err - } else { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(meta.dialTimeout)) - defer cancel() - //ping - _, err = client.Get(ctx, "ping") - if err != nil { - return nil, fmt.Errorf("etcd sequencer error: connect to etcd timeoout %s", meta.endpoints) - } - - return client, nil - } -} - func (e *EtcdSequencer) getKeyInEtcd(key string) string { - return fmt.Sprintf("%s%s", e.metadata.keyPrefix, key) -} - -func parseEtcdMetadata(config sequencer.Configuration) (metadata, error) { - m := metadata{} - var err error - - m.biggerThan = config.BiggerThan - if val, ok := config.Properties[endpointsKey]; ok && val != "" { - m.endpoints = strings.Split(val, ";") - } else { - return m, errors.New("etcd sequencer error: missing endpoints address") - } - - if val, ok := config.Properties[dialTimeoutKey]; ok && val != "" { - if m.dialTimeout, err = strconv.Atoi(val); err != nil { - return m, fmt.Errorf("etcd sequencer error: ncorrect dialTimeout value %s", val) - } - } else { - m.dialTimeout = defaultDialTimeout - } - - if val, ok := config.Properties[prefixKey]; ok && val != "" { - m.keyPrefix = addPathSeparator(val) - } else { - m.keyPrefix = defaultKeyPrefix - } - - if val, ok := config.Properties[usernameKey]; ok && val != "" { - m.username = val - } - - if val, ok := config.Properties[passwordKey]; ok && val != "" { - m.password = val - } - - if val, ok := config.Properties[tlsCaPathKey]; ok && val != "" { - m.tlsCa = val - } - - if val, ok := config.Properties[tlsCertPathKey]; ok && val != "" { - m.tlsCert = val - } - - if val, ok := config.Properties[tlsCertKeyPathKey]; ok && val != "" { - m.tlsCertKey = val - } - - return m, nil + return fmt.Sprintf("%s%s", e.metadata.KeyPrefix, key) } func addPathSeparator(p string) string { @@ -228,16 +118,3 @@ func addPathSeparator(p string) string { } return p } - -type metadata struct { - keyPrefix string - dialTimeout int - endpoints []string - username string - password string - - tlsCa string - tlsCert string - tlsCertKey string - biggerThan map[string]int64 -} diff --git a/components/sequencer/types.go b/components/sequencer/types.go index 9423cd35bd..38d421cd1c 100644 --- a/components/sequencer/types.go +++ b/components/sequencer/types.go @@ -4,7 +4,3 @@ type Config struct { BiggerThan map[string]int64 `json:"biggerThan"` Metadata map[string]string `json:"metadata"` } - -//type Metadata struct { -// Properties map[string]string `json:"properties"` -//} diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 36cf3fd9b8..7b7f008056 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -23,9 +23,9 @@ - [Actuator API](en/api_reference/actuator/actuator.md) - [State API](en/api_reference/state/reference.md) - [Sequencer API](en/api_reference/sequencer/reference.md) - - [Configuration API](en/api_reference/configuration/reference.md) - - [Pub/Sub API](en/api_reference/pubsub/reference.md) - [Distributed Lock API](en/api_reference/lock/reference.md) + - [Pub/Sub API](en/api_reference/pubsub/reference.md) + - [Configuration API](en/api_reference/configuration/reference.md) - [RPC API](en/api_reference/rpc/reference.md) - SDK reference - [go-sdk](en/sdk_reference/go/start.md) diff --git a/docs/en/api_reference/lock/reference.md b/docs/en/api_reference/lock/reference.md index c8905911b6..9c2d0e6f56 100644 --- a/docs/en/api_reference/lock/reference.md +++ b/docs/en/api_reference/lock/reference.md @@ -1 +1,77 @@ -Under Construction \ No newline at end of file +# Distributed Lock API +## What is distributed lock API +The distributed lock API is based on a certain storage system (such as Etcd, Zookeeper) to provide developers with a simple and easy-to-use distributed lock API. Developers can use the API to obtain locks and protect shared resources from concurrency problems. + +## How to use distributed lock API +You can call the distributed lock API through grpc. The API is defined in [runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto). + +The component needs to be configured before use. For detailed configuration instructions, see [Distributed Lock Component Document](en/component_specs/lock/common.md) + +### Example +Layotto client sdk encapsulates the logic of grpc calling. For an example of using sdk to call distributed lock API, please refer to [Quick Start: Using Distributed Lock API](en/start/lock/start.md) + + +### TryLock +```protobuf +// A non-blocking method trying to get a lock with ttl. +rpc TryLock(TryLockRequest)returns (TryLockResponse) {} + +message TryLockRequest { + // Required. The lock store name,e.g. `redis`. + string store_name = 1; + // Required. resource_id is the lock key. e.g. `order_id_111` + // It stands for "which resource I want to protect" + string resource_id = 2; + + // Required. lock_owner indicate the identifier of lock owner. + // You can generate a uuid as lock_owner.For example,in golang: + // + // req.LockOwner = uuid.New().String() + // + // This field is per request,not per process,so it is different for each request, + // which aims to prevent multi-thread in the same process trying the same lock concurrently. + // + // The reason why we don't make it automatically generated is: + // 1. If it is automatically generated,there must be a 'my_lock_owner_id' field in the response. + // This name is so weird that we think it is inappropriate to put it into the api spec + // 2. If we change the field 'my_lock_owner_id' in the response to 'lock_owner',which means the current lock owner of this lock, + // we find that in some lock services users can't get the current lock owner.Actually users don't need it at all. + // 3. When reentrant lock is needed,the existing lock_owner is required to identify client and check "whether this client can reenter this lock". + // So this field in the request shouldn't be removed. + string lock_owner = 3; + + // Required. expire is the time before expire.The time unit is second. + int32 expire = 4; +} + + +message TryLockResponse { + bool success = 1; +} +``` + +**Q: What is the time unit of the expire field?** + +A: Seconds. + +**Q: What would happen if different applications pass the same lock_owner?** + +case 1. If two apps with different app-id pass the same lock_owner,they won't conflict because lock_owner is grouped by 'app-id ',while 'app-id' is configurated in sidecar's static config(configurated in config.json) + +case 2.If two apps with same app-id pass the same lock_owner,they will conflict and the second app will obtained the same lock already used by the first app.Then the correctness property will be broken. + +So user has to care about the uniqueness property of lock_owner.You can generate a uuid as lock_owner.For example,in golang: + +```go +req.LockOwner = uuid.New().String() +``` + +### Unlock +```protobuf + rpc Unlock(UnlockRequest)returns (UnlockResponse) {} +``` + +To avoid inconsistencies between the documentation and the code, please refer to [proto file](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) for detailed input parameters and return values + +## Why is the distributed lock API designed like this +If you are interested in the implementation principle and design logic, you can refer to [Distributed Lock API Design Document](en/design/lock/lock-api-design) diff --git a/docs/en/api_reference/pubsub/reference.md b/docs/en/api_reference/pubsub/reference.md index c8905911b6..449b9b602b 100644 --- a/docs/en/api_reference/pubsub/reference.md +++ b/docs/en/api_reference/pubsub/reference.md @@ -1 +1,49 @@ -Under Construction \ No newline at end of file +# Pub/Sub API +## What is Pub/Sub API +Pub/Sub API is used to implement the publish/subscribe model. The publish/subscribe model allows microservices to communicate with each other using messages. **The producer or publisher** sends the message to the specified topic and does not know the application receiving the message. The **consumer** will subscribe to the topic and receive its messages, and does not know what application produced these messages. The message broker acts as an intermediary and is responsible for forwarding each message. This mode is especially useful when you need to decouple microservices. + +Pub/Sub API provides at-least-once guarantee and integrates with various message brokers and queue systems. Your application can use the same set of Pub/Sub API to operate different message queues. +## When to use Pub/Sub API and what are the benefits? +If your application needs to access the message queue for event publishing and subscription, then using Pub/Sub API is a good choice. It has the following benefits: + +- Multi (cloud) environment deployment: the same application code can be deployed in different environments + +A neutral API can help your application decouple from storage vendors and cloud vendors, and be able to deploy on different clouds without changing the code. + +- Multi-language reuse middleware: the same set of message middleware can support applications in different languages + +If your company has applications developed in different languages (for example, there are both java and python applications), then the traditional approach is to develop a set of SDKs for each language. + +Using Pub/Sub API can help you avoid the trouble of maintaining multilingual SDKs. Applications in different languages can use the same set of grpc API to interact with Layotto. + +## How to use Pub/Sub API +You can call Pub/Sub API through grpc. The API is defined in [runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto). + +The component needs to be configured before use. For detailed configuration instructions, see [publish/subscribe component documentation](zh/component_specs/pubsub/common.md) + +### Example +Layotto client sdk encapsulates the logic of grpc call. For examples of using sdk to call Pub/Sub API, please refer to [Quick Start: Use Pub/Sub API](en/start/pubsub/start.md) + +### PublishEvent +Used to publish events to the specified topic + +```protobuf +// Publishes events to the specific topic. +rpc PublishEvent(PublishEventRequest) returns (google.protobuf.Empty) {} +``` +To avoid inconsistencies between the documentation and the code, please refer to [runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) for detailed input parameters and return values + +### Subscribe to events +To subscribe to events, the application needs to implement two grpc APIs for Layotto to call back: + + +```protobuf + // Lists all topics subscribed by this app. + rpc ListTopicSubscriptions(google.protobuf.Empty) returns (ListTopicSubscriptionsResponse) {} + + // Subscribes events from Pubsub + rpc OnTopicEvent(TopicEventRequest) returns (TopicEventResponse) {} + +``` + +To avoid inconsistencies between the documentation and the code, please refer to [appcallback.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/appcallback.proto) for detailed input parameters and return values \ No newline at end of file diff --git a/docs/en/api_reference/state/reference.md b/docs/en/api_reference/state/reference.md index 1790928c7d..1dff89c0ac 100644 --- a/docs/en/api_reference/state/reference.md +++ b/docs/en/api_reference/state/reference.md @@ -4,15 +4,27 @@ State API is a set of APIs for adding, deleting, modifying and querying Key/Valu API supports batch CRUD operations and supports the declaration of requirements for concurrency security and data consistency. Layotto will help you deal with complex concurrency control and data consistency issues. -## When to use State API -If your application needs to do some CRUD operations on Key/Value storage, then using the State API is a good choice. It can decouple your application and the storage provider, and you can deploy it on different clouds without changing the code. +## When to use State API and what are the benefits? +If your application needs to do some CRUD operations on Key/Value storage, then using the State API is a good choice. It has the following benefits: + +- Multi (cloud) environment deployment: the same application code can be deployed in different environments + +A neutral API can help your application decouple from storage vendors and cloud vendors, and be able to deploy on different clouds without changing the code. + +- Multi-language reuse middleware: the same DB (and data middleware) can support applications in different languages + +If your company has applications developed in different languages (for example, both java and python applications), then the traditional approach is to develop a set of data middleware SDKs for each language(used for routing,traffic control or some other custom purposes). + +Using State API can help you avoid the trouble of maintaining multilingual SDKs. Applications in different languages can interact with Layotto using the same set of grpc API. ## How to use State API You can call the State API through grpc. The API is defined in [runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto). +The component needs to be configured before use. For detailed configuration items, see [State Component Document](en/component_specs/state/common.md) + +### Example Layotto client sdk encapsulates the logic of grpc call. For examples of using sdk to call State API, please refer to [Quick Start: Use State API](en/start/state/start.md) -The component needs to be configured before use. For detailed configuration items, see [State Component Document](en/component_specs/state/common.md) ### Save state Used to save a batch of status data diff --git a/docs/en/component_specs/lock/common.md b/docs/en/component_specs/lock/common.md index f29a1d691e..cb8a3dda53 100644 --- a/docs/en/component_specs/lock/common.md +++ b/docs/en/component_specs/lock/common.md @@ -34,13 +34,13 @@ You can configure the key/value configuration items that the component cares abo the `keyPrefix` field supports the following key prefix strategies: -* **`appid`** - This is the default policy. The resource_id passed in by the user will eventually be saved as `current appid||resource_id` +* **`appid`** - This is the default policy. The resource_id passed in by the user will eventually be saved as `lock|||current appid||resource_id` -* **`name`** - This setting uses the name of the component as a prefix. For example, the redis component will store the resource_id passed in by the user as `redis||resource_id` +* **`name`** - This setting uses the name of the component as a prefix. For example, the redis component will store the resource_id passed in by the user as `lock|||redis||resource_id` -* **`none`** - No prefix will be added. +* **`none`** - The resource_id passed in by the user will eventually be saved as `lock|||resource_id`. -* Any other string that does not contain `||`. For example, if the keyPrefix is configured as "abc", the resource_id passed in by the user will eventually be saved as `abc||resource_id` +* Any other string that does not contain `||`. For example, if the keyPrefix is configured as "abc", the resource_id passed in by the user will eventually be saved as `lock|||abc||resource_id` **Other configuration items** diff --git a/docs/en/component_specs/pubsub/common.md b/docs/en/component_specs/pubsub/common.md new file mode 100644 index 0000000000..fee409f5d8 --- /dev/null +++ b/docs/en/component_specs/pubsub/common.md @@ -0,0 +1,31 @@ +# Pub/Sub component +**Configuration file structure** + +The json configuration file has the following structure: +```json +"pub_subs": { + "": { + "metadata": { + "": "", + "": "" + } + } +} +``` +You can configure the key/value configuration items that the component cares about in the metadata. For example, [redis component configuration](https://github.com/mosn/layotto/blob/main/configs/config_apollo_health_mq.json) is as follows: + +```json +"pub_subs": { + "redis": { + "metadata": { + "redisHost": "localhost:6380", + "redisPassword": "" + } + } +}, +``` + + +**Configuration item description** + +Each component has its own special configuration items. Please refer to the documentation for each component. \ No newline at end of file diff --git a/docs/en/component_specs/sequencer/common.md b/docs/en/component_specs/sequencer/common.md index 391227d4fa..c079902508 100644 --- a/docs/en/component_specs/sequencer/common.md +++ b/docs/en/component_specs/sequencer/common.md @@ -4,11 +4,11 @@ The json configuration file has the following structure: ```json "sequencer": { - "biggerThan": { - "": "", - "": "" - }, "": { + "biggerThan": { + "": "", + "": "" + }, "metadata": { "": "", "": "" diff --git a/docs/en/configuration/overview.md b/docs/en/configuration/overview.md index 55248739fc..282d439c93 100644 --- a/docs/en/configuration/overview.md +++ b/docs/en/configuration/overview.md @@ -1,3 +1,38 @@ -Under Construction +# Configuration reference +Example: configs/config_apollo.json -refer to https://mosn.io/en/docs/configuration/ \ No newline at end of file +Currently, Layotto uses a MOSN layer 4 filter to integrate with MOSN and run on MOSN, so the configuration file used by Layotto is actually a MOSN configuration file. + +![img.png](../../img/configuration/layotto/img.png) + +As shown in the example above, most of the configurations are MOSN configuration items, please refer to [MOSN configuration instructions](https://mosn.io/docs/configuration/); + +Among them, the filter corresponding to `"type":"grpc"` is a layer 4 filter of MOSN, which is used to integrate Layotto and MOSN. + +The configuration item in `grpc_config` is Layotto's component configuration, the structure is: + +```json +"grpc_config": { + "": { + "": { + "": "", + "metadata": { + "": "", + "": "" + } + } + }, + "": { + "": { + "": "", + "metadata": { + "": "", + "": "" + } + } + } +} + +``` + +As for what to fill in each ``, what is each ``, and which `"": ""` configuration items can be configured with the components, you can refer to [Component specs](en/component_specs/overview) . diff --git a/docs/img/configuration/layotto/img.png b/docs/img/configuration/layotto/img.png new file mode 100644 index 0000000000000000000000000000000000000000..98b25270d6b2e5d23a94913ce785bf34cdd0f05e GIT binary patch literal 120174 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": { + "metadata": { + "": "", + "": "" + } + } +} +``` + +您可以在metadata里配置组件关心的key/value配置。例如[redis组件的配置](https://github.com/mosn/layotto/blob/main/configs/config_apollo_health_mq.json) 如下: + +```json +"pub_subs": { + "redis": { + "metadata": { + "redisHost": "localhost:6380", + "redisPassword": "" + } + } +}, +``` + + +**配置项说明** + +每个State组件有自己的特殊配置项,请参考每个组件的说明文档。 \ No newline at end of file diff --git a/docs/zh/component_specs/sequencer/common.md b/docs/zh/component_specs/sequencer/common.md index 09110aadf3..2ac1d5c459 100644 --- a/docs/zh/component_specs/sequencer/common.md +++ b/docs/zh/component_specs/sequencer/common.md @@ -4,11 +4,11 @@ json配置文件有如下结构: ```json "sequencer": { - "biggerThan": { - "": "", - "": "" - }, "": { + "biggerThan": { + "": "", + "": "" + }, "metadata": { "": "", "": "" diff --git a/docs/zh/configuration/overview.md b/docs/zh/configuration/overview.md index 1d7132fbf6..0cec01e4ed 100644 --- a/docs/zh/configuration/overview.md +++ b/docs/zh/configuration/overview.md @@ -1,3 +1,38 @@ -Under Construction +# 配置说明 +示例配置文件:configs/config_apollo.json -参考https://mosn.io/docs/configuration/ \ No newline at end of file +目前,Layotto使用一个MOSN 4层filter与MOSN集成、跑在MOSN上,所以Layotto用到的配置文件其实就是MOSN配置文件 + +![img.png](../../img/configuration/layotto/img.png) + +如上图示例,大部分配置是MOSN的配置项,参考[MOSN的配置说明](https://mosn.io/docs/configuration/) ; + +其中`"type":"grpc"`对应的filter是MOSN的一个4层filter,用于把Layotto和MOSN集成到一起。 + +而`grpc_config`里面的配置项是Layotto的组件配置,结构为: + +```json +"grpc_config": { + "": { + "": { + "": "", + "metadata": { + "": "", + "": "" + } + } + }, + "": { + "": { + "": "", + "metadata": { + "": "", + "": "" + } + } + } +} + +``` + +至于每个API NAME填啥、每个组件名是啥、组件能配哪些Key/Value配置项,您可以查阅[组件文档](zh/component_specs/overview) \ No newline at end of file diff --git a/pkg/grpc/api.go b/pkg/grpc/api.go index c0a538b541..ee4d9f1cfb 100644 --- a/pkg/grpc/api.go +++ b/pkg/grpc/api.go @@ -60,10 +60,10 @@ var ( type API interface { SayHello(ctx context.Context, in *runtimev1pb.SayHelloRequest) (*runtimev1pb.SayHelloResponse, error) - // GetConfiguration gets configuration from configuration store. - GetConfiguration(context.Context, *runtimev1pb.GetConfigurationRequest) (*runtimev1pb.GetConfigurationResponse, error) // InvokeService do rpc calls. InvokeService(ctx context.Context, in *runtimev1pb.InvokeServiceRequest) (*runtimev1pb.InvokeResponse, error) + // GetConfiguration gets configuration from configuration store. + GetConfiguration(context.Context, *runtimev1pb.GetConfigurationRequest) (*runtimev1pb.GetConfigurationResponse, error) // SaveConfiguration saves configuration into configuration store. SaveConfiguration(context.Context, *runtimev1pb.SaveConfigurationRequest) (*emptypb.Empty, error) // DeleteConfiguration deletes configuration from configuration store. @@ -802,7 +802,7 @@ func (a *api) GetNextId(ctx context.Context, req *runtimev1pb.GetNextIdRequest) // 1. validate if len(a.sequencers) == 0 { err := status.Error(codes.FailedPrecondition, messages.ErrSequencerStoresNotConfigured) - log.DefaultLogger.Errorf("[runtime] [grpc.TryLock] error: %v", err) + log.DefaultLogger.Errorf("[runtime] [grpc.GetNextId] error: %v", err) return &runtimev1pb.GetNextIdResponse{}, err } if req.Key == "" { @@ -814,6 +814,12 @@ func (a *api) GetNextId(ctx context.Context, req *runtimev1pb.GetNextIdRequest) if err != nil { return &runtimev1pb.GetNextIdResponse{}, err } + // modify key + compReq.Key, err = runtime_sequencer.GetModifiedKey(compReq.Key, req.StoreName, a.appId) + if err != nil { + log.DefaultLogger.Errorf("[runtime] [grpc.GetNextId] error: %v", err) + return &runtimev1pb.GetNextIdResponse{}, err + } // 3. find store component store, ok := a.sequencers[req.StoreName] if !ok { diff --git a/pkg/runtime/lock/lock_config.go b/pkg/runtime/lock/lock_config.go index fd9937fdf8..19d7261ee9 100644 --- a/pkg/runtime/lock/lock_config.go +++ b/pkg/runtime/lock/lock_config.go @@ -15,7 +15,9 @@ const ( strategyNone = "none" strategyDefault = strategyAppid - separator = "||" + apiPrefix = "lock" + apiSeparator = "|||" + separator = "||" ) var lockConfiguration = map[string]*StoreConfiguration{} @@ -46,16 +48,16 @@ func GetModifiedLockKey(key, storeName, appID string) (string, error) { config := getConfiguration(storeName) switch config.keyPrefixStrategy { case strategyNone: - return key, nil + return fmt.Sprintf("%s%s%s", apiPrefix, apiSeparator, key), nil case strategyStoreName: - return fmt.Sprintf("%s%s%s", storeName, separator, key), nil + return fmt.Sprintf("%s%s%s%s%s", apiPrefix, apiSeparator, storeName, separator, key), nil case strategyAppid: if appID == "" { - return key, nil + return fmt.Sprintf("%s%s%s", apiPrefix, apiSeparator, key), nil } - return fmt.Sprintf("%s%s%s", appID, separator, key), nil + return fmt.Sprintf("%s%s%s%s%s", apiPrefix, apiSeparator, appID, separator, key), nil default: - return fmt.Sprintf("%s%s%s", config.keyPrefixStrategy, separator, key), nil + return fmt.Sprintf("%s%s%s%s%s", apiPrefix, apiSeparator, config.keyPrefixStrategy, separator, key), nil } } diff --git a/pkg/runtime/lock/lock_config_test.go b/pkg/runtime/lock/lock_config_test.go index d22e4e5ac4..e2c7c9ce86 100644 --- a/pkg/runtime/lock/lock_config_test.go +++ b/pkg/runtime/lock/lock_config_test.go @@ -63,43 +63,43 @@ func TestGetModifiedLockKey(t *testing.T) { func TestNonePrefix(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store1", "appid1") - require.Equal(t, key, modifiedLockKey) + require.Equal(t, "lock|||"+key, modifiedLockKey) } func TestAppidPrefix(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store2", "appid1") - require.Equal(t, "appid1||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||appid1||lock-key-1234567", modifiedLockKey) } func TestAppidPrefix_WithEnptyAppid(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store2", "") - require.Equal(t, "lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||lock-key-1234567", modifiedLockKey) } func TestDefaultPrefix(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store3", "appid1") - require.Equal(t, "appid1||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||appid1||lock-key-1234567", modifiedLockKey) } func TestStoreNamePrefix(t *testing.T) { key := "lock-key-1234567" modifiedLockKey, _ := GetModifiedLockKey(key, "store4", "appid1") - require.Equal(t, "store4||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||store4||lock-key-1234567", modifiedLockKey) } func TestOtherFixedPrefix(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store5", "appid1") - require.Equal(t, "other-fixed-prefix||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||other-fixed-prefix||lock-key-1234567", modifiedLockKey) } func TestLegacyPrefix(t *testing.T) { modifiedLockKey, _ := GetModifiedLockKey(key, "store6", "appid1") - require.Equal(t, "appid1||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||appid1||lock-key-1234567", modifiedLockKey) } func TestPrefix_StoreNotInitial(t *testing.T) { // no config for store999 modifiedLockKey, _ := GetModifiedLockKey(key, "store999", "appid99") - require.Equal(t, "appid99||lock-key-1234567", modifiedLockKey) + require.Equal(t, "lock|||appid99||lock-key-1234567", modifiedLockKey) } diff --git a/pkg/runtime/sequencer/utils.go b/pkg/runtime/sequencer/utils.go new file mode 100644 index 0000000000..859a7c8423 --- /dev/null +++ b/pkg/runtime/sequencer/utils.go @@ -0,0 +1,26 @@ +package sequencer + +import ( + "fmt" + "github.com/pkg/errors" + "strings" +) + +const ( + separator = "|||" + commonPrefix = "sequencer" +) + +func GetModifiedKey(key, storeName, appID string) (string, error) { + if err := checkKeyIllegal(key); err != nil { + return "", err + } + return fmt.Sprintf("%s%s%s", commonPrefix, separator, key), nil +} + +func checkKeyIllegal(key string) error { + if strings.Contains(key, separator) { + return errors.Errorf("input key/keyPrefix '%s' can't contain '%s'", key, separator) + } + return nil +} diff --git a/spec/proto/runtime/v1/runtime.proto b/spec/proto/runtime/v1/runtime.proto index 863cb31753..b4581074d2 100644 --- a/spec/proto/runtime/v1/runtime.proto +++ b/spec/proto/runtime/v1/runtime.proto @@ -108,11 +108,15 @@ message GetNextIdResponse{ } message TryLockRequest { + // Required. The lock store name,e.g. `redis`. string store_name = 1; - // resource_id is the lock key. + + // Required. resource_id is the lock key. e.g. `order_id_111` + // It stands for "which resource I want to protect" string resource_id = 2; - // lock_owner indicate the identifier of lock owner. - // This field is required.You can generate a uuid as lock_owner.For example,in golang: + + // Required. lock_owner indicate the identifier of lock owner. + // You can generate a uuid as lock_owner.For example,in golang: // // req.LockOwner = uuid.New().String() // @@ -127,12 +131,12 @@ message TryLockRequest { // 3. When reentrant lock is needed,the existing lock_owner is required to identify client and check "whether this client can reenter this lock". // So this field in the request shouldn't be removed. string lock_owner = 3; - // expire is the time before expire.The time unit is second. + + // Required. expire is the time before expire.The time unit is second. int32 expire = 4; } message TryLockResponse { - bool success = 1; }