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)
+		})
+	})
+}