Skip to content

Commit

Permalink
[feat] add dlock service, implemented etcd's dlock mechanism (#1191)
Browse files Browse the repository at this point in the history
  • Loading branch information
robotLJW committed Dec 29, 2021
1 parent 35b4680 commit 175853b
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 10 deletions.
33 changes: 33 additions & 0 deletions datasource/dlock/dlock.go
Original file line number Diff line number Diff line change
@@ -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
}
90 changes: 90 additions & 0 deletions datasource/dlock/dlock_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
})
}
56 changes: 56 additions & 0 deletions datasource/dlock/init.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions datasource/dlock/options.go
Original file line number Diff line number Diff line change
@@ -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
}
90 changes: 90 additions & 0 deletions datasource/etcd/dlock.go
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions datasource/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
Loading

0 comments on commit 175853b

Please sign in to comment.