You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2021/12/29 09:42:51 UTC
[servicecomb-service-center] branch master updated: [feat] add dlock service, implemented etcd's dlock mechanism (#1191)
This is an automated email from the ASF dual-hosted git repository.
littlecui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-service-center.git
The following commit(s) were added to refs/heads/master by this push:
new 175853b [feat] add dlock service, implemented etcd's dlock mechanism (#1191)
175853b is described below
commit 175853bb8fdd460f44f79c67d81d75c8e1eee161
Author: robotljw <79...@qq.com>
AuthorDate: Wed Dec 29 17:42:42 2021 +0800
[feat] add dlock service, implemented etcd's dlock mechanism (#1191)
---
datasource/dlock/dlock.go | 33 +++++++++++++
datasource/dlock/dlock_test.go | 90 ++++++++++++++++++++++++++++++++++++
datasource/dlock/init.go | 56 +++++++++++++++++++++++
datasource/dlock/options.go | 23 ++++++++++
datasource/etcd/dlock.go | 90 ++++++++++++++++++++++++++++++++++++
datasource/manager.go | 9 ++++
go.mod | 2 +-
go.sum | 9 ----
server/service/dlock/dlock.go | 43 +++++++++++++++++
server/service/dlock/dlock_test.go | 94 ++++++++++++++++++++++++++++++++++++++
10 files changed, 439 insertions(+), 10 deletions(-)
diff --git a/datasource/dlock/dlock.go b/datasource/dlock/dlock.go
new file mode 100644
index 0000000..a2d0ea4
--- /dev/null
+++ b/datasource/dlock/dlock.go
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Package dlock provide distributed lock function
+package dlock
+
+import (
+ "errors"
+)
+
+var ErrDLockNotExists = errors.New("DLock do not exist")
+
+type DLock interface {
+ Lock(key string, ttl int64) error
+ TryLock(key string, ttl int64) error
+ Renew(key string) error
+ IsHoldLock(key string) bool
+ Unlock(key string) error
+}
diff --git a/datasource/dlock/dlock_test.go b/datasource/dlock/dlock_test.go
new file mode 100644
index 0000000..640020d
--- /dev/null
+++ b/datasource/dlock/dlock_test.go
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dlock_test
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/servicecomb-service-center/datasource/dlock"
+ _ "github.com/apache/servicecomb-service-center/test"
+)
+
+func TestDLock(t *testing.T) {
+ t.Run("test lock", func(t *testing.T) {
+ t.Run("lock the global key for 5s should pass", func(t *testing.T) {
+ err := dlock.Instance().Lock("global", 5)
+ assert.Nil(t, err)
+ isHold := dlock.Instance().IsHoldLock("global")
+ assert.Equal(t, true, isHold)
+ })
+ t.Run("two locks fight for the same lock 5s, one lock should pass, another lock should fail", func(t *testing.T) {
+ err := dlock.Instance().Lock("same-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.Instance().IsHoldLock("same-lock")
+ assert.Equal(t, true, isHold)
+ err = dlock.Instance().TryLock("same-lock", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test try lock", func(t *testing.T) {
+ t.Run("try lock the try key for 5s should pass", func(t *testing.T) {
+ err := dlock.Instance().TryLock("try-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.Instance().IsHoldLock("try-lock")
+ assert.Equal(t, true, isHold)
+ err = dlock.Instance().TryLock("try-lock", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test renew", func(t *testing.T) {
+ t.Run("renew the renew key for 5s should pass", func(t *testing.T) {
+ err := dlock.Instance().Lock("renew", 5)
+ assert.Nil(t, err)
+ isHold := dlock.Instance().IsHoldLock("renew")
+ assert.Equal(t, true, isHold)
+ time.Sleep(3 * time.Second)
+ err = dlock.Instance().Renew("renew")
+ time.Sleep(2 * time.Second)
+ err = dlock.Instance().TryLock("renew", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test isHoldLock", func(t *testing.T) {
+ t.Run("already owns the lock should pass", func(t *testing.T) {
+ err := dlock.Instance().Lock("hold-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.Instance().IsHoldLock("hold-lock")
+ assert.Equal(t, true, isHold)
+ })
+ t.Run("key does not exist should fail", func(t *testing.T) {
+ isHold := dlock.Instance().IsHoldLock("not-exist")
+ assert.Equal(t, false, isHold)
+ })
+ })
+ t.Run("test unlock", func(t *testing.T) {
+ t.Run("unlock the unlock key should pass", func(t *testing.T) {
+ err := dlock.Instance().Lock("unlock", 5)
+ assert.Nil(t, err)
+ err = dlock.Instance().Unlock("unlock")
+ assert.Nil(t, err)
+ })
+ })
+}
diff --git a/datasource/dlock/init.go b/datasource/dlock/init.go
new file mode 100644
index 0000000..179b255
--- /dev/null
+++ b/datasource/dlock/init.go
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dlock
+
+import (
+ "fmt"
+
+ "github.com/apache/servicecomb-service-center/pkg/log"
+)
+
+type initFunc func(opts Options) (DLock, error)
+
+var (
+ plugins = make(map[string]initFunc)
+ instance DLock
+)
+
+func Install(pluginImplName string, f initFunc) {
+ plugins[pluginImplName] = f
+}
+
+func Init(opts Options) error {
+ if opts.Kind == "" {
+ return nil
+ }
+ engineFunc, ok := plugins[opts.Kind]
+ if !ok {
+ return fmt.Errorf("plugin implement not supported [%s]", opts.Kind)
+ }
+ var err error
+ instance, err = engineFunc(opts)
+ if err != nil {
+ return err
+ }
+ log.Info(fmt.Sprintf("dlock plugin [%s] enabled", opts.Kind))
+ return nil
+}
+
+func Instance() DLock {
+ return instance
+}
diff --git a/datasource/dlock/options.go b/datasource/dlock/options.go
new file mode 100644
index 0000000..1dfe186
--- /dev/null
+++ b/datasource/dlock/options.go
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dlock
+
+// Options contains configuration for plugins
+type Options struct {
+ Kind string
+}
diff --git a/datasource/etcd/dlock.go b/datasource/etcd/dlock.go
new file mode 100644
index 0000000..13dcf29
--- /dev/null
+++ b/datasource/etcd/dlock.go
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package etcd
+
+import (
+ "sync"
+
+ "github.com/go-chassis/openlog"
+ "github.com/little-cui/etcdadpt"
+
+ "github.com/apache/servicecomb-service-center/datasource/dlock"
+)
+
+func init() {
+ dlock.Install("etcd", NewDLock)
+ dlock.Install("embeded_etcd", NewDLock)
+ dlock.Install("embedded_etcd", NewDLock)
+}
+
+func NewDLock(opts dlock.Options) (dlock.DLock, error) {
+ return &DB{lockMap: sync.Map{}}, nil
+}
+
+type DB struct {
+ lockMap sync.Map
+}
+
+func (d *DB) Lock(key string, ttl int64) error {
+ lock, err := etcdadpt.Lock(key, ttl)
+ if err == nil {
+ d.lockMap.Store(key, lock)
+ }
+ return err
+}
+
+func (d *DB) TryLock(key string, ttl int64) error {
+ lock, err := etcdadpt.TryLock(key, ttl)
+ if err == nil {
+ d.lockMap.Store(key, lock)
+ }
+ return err
+}
+
+func (d *DB) Renew(key string) error {
+ if lock, ok := d.lockMap.Load(key); ok {
+ err := lock.(*etcdadpt.DLock).Refresh()
+ if err != nil {
+ openlog.Error("fail to renew key")
+ d.lockMap.Delete(key)
+ }
+ return err
+ }
+ return dlock.ErrDLockNotExists
+}
+
+func (d *DB) IsHoldLock(key string) bool {
+ if lock, ok := d.lockMap.Load(key); ok {
+ if lock != nil {
+ return true
+ }
+ }
+ return false
+}
+
+func (d *DB) Unlock(key string) error {
+ if lock, ok := d.lockMap.Load(key); ok {
+ err := lock.(*etcdadpt.DLock).Unlock()
+ if err != nil {
+ openlog.Error("fail to unlock")
+ }
+ d.lockMap.Delete(key)
+ return err
+ }
+ return dlock.ErrDLockNotExists
+}
diff --git a/datasource/manager.go b/datasource/manager.go
index 5cb56dc..0c78c71 100644
--- a/datasource/manager.go
+++ b/datasource/manager.go
@@ -20,6 +20,7 @@ package datasource
import (
"fmt"
+ "github.com/apache/servicecomb-service-center/datasource/dlock"
"github.com/apache/servicecomb-service-center/datasource/rbac"
"github.com/apache/servicecomb-service-center/datasource/schema"
"github.com/apache/servicecomb-service-center/pkg/log"
@@ -56,6 +57,14 @@ func Init(opts Options) error {
if err != nil {
return err
}
+
+ err = dlock.Init(dlock.Options{
+ Kind: opts.Kind,
+ })
+ if err != nil {
+ return err
+ }
+
return nil
}
diff --git a/go.mod b/go.mod
index 63b01a4..8825492 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ replace (
)
require (
- github.com/robfig/cron/v3 v3.0.1
github.com/NYTimes/gziphandler v1.1.1
github.com/apache/servicecomb-service-center/api v0.0.0
github.com/astaxie/beego v1.12.2
@@ -38,6 +37,7 @@ require (
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/procfs v0.6.0
+ github.com/robfig/cron/v3 v3.0.1
github.com/rs/cors v1.7.0 // v1.1
github.com/satori/go.uuid v1.1.0
github.com/spf13/cobra v1.1.3
diff --git a/go.sum b/go.sum
index 7f895cf..e1f4c40 100644
--- a/go.sum
+++ b/go.sum
@@ -184,9 +184,6 @@ github.com/go-chassis/cari v0.5.1-0.20211208092532-78a52aa9d52e/go.mod h1:av/19f
github.com/go-chassis/foundation v0.2.2-0.20201210043510-9f6d3de40234/go.mod h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
github.com/go-chassis/foundation v0.2.2/go.mod h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
github.com/go-chassis/foundation v0.3.0/go.mod h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
-github.com/go-chassis/foundation v0.3.1-0.20210806081520-3bd92d1ef787/go.mod h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
-github.com/go-chassis/foundation v0.3.1-0.20210811025651-7f4d2b2b906c h1:6ooUyysnayGgoJHV++NLEcnnnXzsw3ud4VydjISGBqI=
-github.com/go-chassis/foundation v0.3.1-0.20210811025651-7f4d2b2b906c/go.mod h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
github.com/go-chassis/foundation v0.4.0 h1:z0xETnSxF+vRXWjoIhOdzt6rywjZ4sB++utEl4YgWEY=
github.com/go-chassis/foundation v0.4.0/go.mod h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
github.com/go-chassis/go-archaius v1.5.1 h1:1FrNyzzmD6o6BIjPF8uQ4Cc+u7qYIgQTpDk8uopBqfo=
@@ -436,12 +433,6 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/little-cui/etcdadpt v0.2.1 h1:eT1A+BV1/2/dmmZA2Nl+cc7uTMuwd6T6DD+JrXr8xcA=
-github.com/little-cui/etcdadpt v0.2.1/go.mod h1:727wftF2FS4vfkgFLmIvQue1XH+9u4lK2/hd6L7OAC8=
-github.com/little-cui/etcdadpt v0.2.2-0.20211218040008-804e734f410b h1:GeNmlkSJKu1B8pNM18g+u8O/7BJNd9TP6KrfePy7Z6w=
-github.com/little-cui/etcdadpt v0.2.2-0.20211218040008-804e734f410b/go.mod h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
-github.com/little-cui/etcdadpt v0.2.2-0.20211222115540-fc5b1296d8b5 h1:SlD/2mPGjzkg2oj4ndoR9u/yadPcyE8om3njQEI65bE=
-github.com/little-cui/etcdadpt v0.2.2-0.20211222115540-fc5b1296d8b5/go.mod h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
github.com/little-cui/etcdadpt v0.3.1 h1:lAPIffcOR6jROu/mWf+zHscV8urIu1qbsJvwvziLWDY=
github.com/little-cui/etcdadpt v0.3.1/go.mod h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
diff --git a/server/service/dlock/dlock.go b/server/service/dlock/dlock.go
new file mode 100644
index 0000000..ac7c914
--- /dev/null
+++ b/server/service/dlock/dlock.go
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Package dlock provide distributed lock function
+package dlock
+
+import (
+ "github.com/apache/servicecomb-service-center/datasource/dlock"
+)
+
+func Lock(key string, ttl int64) error {
+ return dlock.Instance().Lock(key, ttl)
+}
+
+func TryLock(key string, ttl int64) error {
+ return dlock.Instance().TryLock(key, ttl)
+}
+
+func Renew(key string) error {
+ return dlock.Instance().Renew(key)
+}
+
+func IsHoldLock(key string) bool {
+ return dlock.Instance().IsHoldLock(key)
+}
+
+func Unlock(key string) error {
+ return dlock.Instance().Unlock(key)
+}
diff --git a/server/service/dlock/dlock_test.go b/server/service/dlock/dlock_test.go
new file mode 100644
index 0000000..625bb0c
--- /dev/null
+++ b/server/service/dlock/dlock_test.go
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dlock_test
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/servicecomb-service-center/server/service/dlock"
+ "github.com/apache/servicecomb-service-center/test"
+ _ "github.com/apache/servicecomb-service-center/test"
+)
+
+func TestDLock(t *testing.T) {
+ if !test.IsETCD() {
+ return
+ }
+ t.Run("test lock", func(t *testing.T) {
+ t.Run("lock the global key for 5s should pass", func(t *testing.T) {
+ err := dlock.Lock("global", 5)
+ assert.Nil(t, err)
+ isHold := dlock.IsHoldLock("global")
+ assert.Equal(t, true, isHold)
+ })
+ t.Run("two locks fight for the same lock 5s, one lock should pass, another lock should fail", func(t *testing.T) {
+ err := dlock.Lock("same-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.IsHoldLock("same-lock")
+ assert.Equal(t, true, isHold)
+ err = dlock.TryLock("same-lock", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test try lock", func(t *testing.T) {
+ t.Run("try lock the try key for 5s should pass", func(t *testing.T) {
+ err := dlock.TryLock("try-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.IsHoldLock("try-lock")
+ assert.Equal(t, true, isHold)
+ err = dlock.TryLock("try-lock", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test renew", func(t *testing.T) {
+ t.Run("renew the renew key for 5s should pass", func(t *testing.T) {
+ err := dlock.Lock("renew", 5)
+ assert.Nil(t, err)
+ isHold := dlock.IsHoldLock("renew")
+ assert.Equal(t, true, isHold)
+ time.Sleep(3 * time.Second)
+ err = dlock.Renew("renew")
+ time.Sleep(2 * time.Second)
+ err = dlock.TryLock("renew", 5)
+ assert.NotNil(t, err)
+ })
+ })
+ t.Run("test isHoldLock", func(t *testing.T) {
+ t.Run("already owns the lock should pass", func(t *testing.T) {
+ err := dlock.Lock("hold-lock", 5)
+ assert.Nil(t, err)
+ isHold := dlock.IsHoldLock("hold-lock")
+ assert.Equal(t, true, isHold)
+ })
+ t.Run("key does not exist should fail", func(t *testing.T) {
+ isHold := dlock.IsHoldLock("not-exist")
+ assert.Equal(t, false, isHold)
+ })
+ })
+ t.Run("test unlock", func(t *testing.T) {
+ t.Run("unlock the unlock key should pass", func(t *testing.T) {
+ err := dlock.Lock("unlock", 5)
+ assert.Nil(t, err)
+ err = dlock.Unlock("unlock")
+ assert.Nil(t, err)
+ })
+ })
+}