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 0000000000..98b25270d6 Binary files /dev/null and b/docs/img/configuration/layotto/img.png differ diff --git a/docs/zh/_sidebar.md b/docs/zh/_sidebar.md index a76b6ad3c9..34de76c253 100644 --- a/docs/zh/_sidebar.md +++ b/docs/zh/_sidebar.md @@ -23,9 +23,9 @@ - [Actuator API](zh/api_reference/actuator/actuator.md) - [State API](zh/api_reference/state/reference.md) - [Sequencer API](zh/api_reference/sequencer/reference.md) - - [Configuration API](zh/api_reference/configuration/reference.md) - - [Pub/Sub API](zh/api_reference/pubsub/reference.md) - [Distributed Lock API](zh/api_reference/lock/reference.md) + - [Pub/Sub API](zh/api_reference/pubsub/reference.md) + - [Configuration API](zh/api_reference/configuration/reference.md) - [RPC API](zh/api_reference/rpc/reference.md) - SDK文档 - [go-sdk](zh/sdk_reference/go/start.md) @@ -35,7 +35,7 @@ - [State](zh/component_specs/state/common.md) - [Redis](zh/component_specs/state/redis.md) - [其他组件](zh/component_specs/state/others.md) - - Pub/Sub + - [Pub/Sub](zh/component_specs/pubsub/common.md) - [Redis](zh/component_specs/pubsub/redis.md) - [其他组件](zh/component_specs/pubsub/others.md) - [Distributed Lock](zh/component_specs/lock/common.md) diff --git a/docs/zh/api_reference/lock/reference.md b/docs/zh/api_reference/lock/reference.md index c8905911b6..f1a1f38423 100644 --- a/docs/zh/api_reference/lock/reference.md +++ b/docs/zh/api_reference/lock/reference.md @@ -1 +1,77 @@ -Under Construction \ No newline at end of file +# Distributed Lock API +## 什么是分布式锁 API +分布式锁 API基于某种存储系统(比如Etcd,Zookeeper)为开发者提供简单、易用的分布式锁API接口,开发者可以用该API获取锁、保护共享资源免受并发问题的烦扰。 + +## 如何使用分布式锁 API +您可以通过grpc调用分布式锁 API,接口定义在[runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) 中。 + +使用前需要先对组件进行配置,详细的配置说明见[分布式锁组件文档](zh/component_specs/lock/common.md) + +### 使用示例 +Layotto client sdk封装了grpc调用的逻辑,使用sdk调用分布式锁 API的示例可以参考[快速开始:使用分布式锁API](zh/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: expire字段的时间单位?** + +A: 秒。 + +**Q: 如果多个客户端传相同的LockOwner会怎么样?** + +case 1. 两个客户端app-id不一样,传的LockOwner相同,不会发生冲突 + +case 2. 两个客户端app-id一样,传的LockOwner相同,会发生冲突。可能会出现抢到别人的锁、释放别人的锁等异常情况 + +因此用户需要保证LockOwner的唯一性,例如给每个请求分配一个UUID,golang写法: + +```go +import "github.com/google/uuid" +//... +req.LockOwner = uuid.New().String() +``` +### Unlock +```protobuf + rpc Unlock(UnlockRequest)returns (UnlockResponse) {} +``` + +为避免文档和代码不一致,详细入参和返回值请参考[proto文件](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) + +## 为什么分布式锁 API被设计成这样 +如果您对实现原理、设计逻辑感兴趣,可以查阅[分布式锁API设计文档](zh/design/lock/lock-api-design) \ No newline at end of file diff --git a/docs/zh/api_reference/pubsub/reference.md b/docs/zh/api_reference/pubsub/reference.md index c8905911b6..846b1ab1bc 100644 --- a/docs/zh/api_reference/pubsub/reference.md +++ b/docs/zh/api_reference/pubsub/reference.md @@ -1 +1,50 @@ -Under Construction \ No newline at end of file +# 发布/订阅 API +## 什么是Pub/Sub API +Pub/Sub API用于实现发布/订阅模式。发布/订阅模式允许微服务使用消息相互通信。 **生产者或发布者**将消息发送至指定Topic,并且不知道接收消息的应用程序。而**消费者**将订阅该主题并收到它的消息,并且不知道什么应用程序生产了这些消息。消息队列(message broker)作为中间人,负责将每条消息的转发。 当您需要将微服务解偶时,此模式特别有用。 + +Pub/Sub API 提供至少一次(at-least-once)的保证,并与各种消息代理和队列系统集成。 您的应用程序可以使用同一套Pub/Sub API操作不同的消息队列。 + +## 何时使用Pub/Sub API,好处是什么? +如果您的应用需要访问消息队列(message queue)进行事件发布订阅,那么使用Pub/Sub API是一个不错的选择,它有以下好处: + +- 多(云)环境部署:同一套业务代码部署在不同环境 + +中立的API可以帮助您的应用和MQ供应商、云厂商解耦,能够不改代码部署在不同的云上。 + +- 多语言复用中间件:同一套消息中间件能支持不同语言的应用 + +如果您的公司内部有不同语言开发的应用(例如同时有java和python应用),那么传统做法是为每种语言开发一套sdk。 + +使用Pub/Sub API可以帮助您免去维护多语言sdk的烦恼,不同语言的应用可以用同一套grpc API和Layotto交互。 + +## 如何使用Pub/Sub API +您可以通过grpc调用Pub/Sub API,接口定义在[runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) 中。 + +使用前需要先对组件进行配置,详细的配置说明见[发布/订阅组件文档](zh/component_specs/pubsub/common.md) + +### 使用示例 +Layotto client sdk封装了grpc调用的逻辑,使用sdk调用Pub/Sub API的示例可以参考[快速开始:使用Pub/Sub API](zh/start/pubsub/start.md) + +### PublishEvent +用于发布事件到指定topic + +```protobuf +// Publishes events to the specific topic. +rpc PublishEvent(PublishEventRequest) returns (google.protobuf.Empty) {} +``` +为避免文档和代码不一致,详细入参和返回值请参考[runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) + +### 订阅事件 +订阅事件需要应用实现两个grpc接口,供Layotto回调: + + +```protobuf + // Lists all topics subscribed by this app. + rpc ListTopicSubscriptions(google.protobuf.Empty) returns (ListTopicSubscriptionsResponse) {} + + // Subscribes events from Pubsub + rpc OnTopicEvent(TopicEventRequest) returns (TopicEventResponse) {} + +``` + +为避免文档和代码不一致,详细入参和返回值请参考[appcallback.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/appcallback.proto) \ No newline at end of file diff --git a/docs/zh/api_reference/state/reference.md b/docs/zh/api_reference/state/reference.md index 7406af30b4..36247eaaa5 100644 --- a/docs/zh/api_reference/state/reference.md +++ b/docs/zh/api_reference/state/reference.md @@ -5,14 +5,26 @@ State API是一套对Key/Value数据进行增删改查的API。您的应用程 API支持批量CRUD操作,支持声明对并发安全和数据一致性的要求,由Layotto帮您处理复杂的并发安全和数据一致性问题。 ## 何时使用State API -如果您的应用需要访问Key/Value存储、进行增删改查,那么使用State API是一个不错的选择,它可以帮助您的应用和存储供应商解耦,能够不改代码部署在不同的云上。 +如果您的应用需要访问Key/Value存储、进行增删改查,那么使用State API是一个不错的选择,它有以下好处: + +- 多(云)环境部署:同一套业务代码部署在不同环境 + +中立的API可以帮助您的应用和存储供应商、云厂商解耦,能够不改代码部署在不同的云上。 + +- 多语言复用中间件:同一个DB(和数据中间件)能支持不同语言的应用 + +如果您的公司内部有不同语言开发的应用(例如同时有java和python应用),那么传统做法是为每种语言开发一套数据中间件sdk(用于路由,容灾,流量管理等目的)。 + +使用State API可以帮助您免去维护多语言sdk的烦恼,不同语言的应用可以用同一套grpc API和Layotto交互。 ## 如何使用State API 您可以通过grpc调用State API,接口定义在[runtime.proto](https://github.com/mosn/layotto/blob/main/spec/proto/runtime/v1/runtime.proto) 中。 +使用前需要先对组件进行配置,详细的配置说明见[状态管理组件文档](zh/component_specs/state/common.md) + +### 使用示例 Layotto client sdk封装了grpc调用的逻辑,使用sdk调用State API的示例可以参考[快速开始:使用State API](zh/start/state/start.md) -使用前需要先对组件进行配置,详细的配置说明见[状态管理组件文档](zh/component_specs/state/common.md) ### Save state 用于保存一批状态数据 diff --git a/docs/zh/component_specs/lock/common.md b/docs/zh/component_specs/lock/common.md index e4df96941e..adecb61454 100644 --- a/docs/zh/component_specs/lock/common.md +++ b/docs/zh/component_specs/lock/common.md @@ -35,13 +35,13 @@ json配置文件有如下结构: keyPrefix支持以下键前缀策略: -* **`appid`** - 这是默认策略。用户传入的resource_id最终将被保存为`当前appid||resource_id` +* **`appid`** - 这是默认策略。用户传入的resource_id最终将被保存为`lock|||当前appid||resource_id` -* **`name`** - 此设置使用组件名称作为前缀。 比如redis组件会将用户传入的resource_id存储为`redis||resource_id` +* **`name`** - 此设置使用组件名称作为前缀。 比如redis组件会将用户传入的resource_id存储为`lock|||redis||resource_id` -* **`none`** - 此设置不使用前缀。 +* **`none`** - 用户传入的resource_id最终将被保存为`lock|||resource_id`。 -* 其他任意不含||的字符串.比如keyPrefix配置成"abc",那么用户传入的resource_id最终将被保存为`abc||resource_id` +* 其他任意不含||的字符串.比如keyPrefix配置成"abc",那么用户传入的resource_id最终将被保存为`lock|||abc||resource_id` **其他配置项** diff --git a/docs/zh/component_specs/pubsub/common.md b/docs/zh/component_specs/pubsub/common.md new file mode 100644 index 0000000000..af5df8b2a6 --- /dev/null +++ b/docs/zh/component_specs/pubsub/common.md @@ -0,0 +1,32 @@ +# 发布/订阅组件 +**配置文件结构** + +json配置文件有如下结构: +```json +"pub_subs": { + "": { + "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; }