You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by as...@apache.org on 2018/07/07 15:36:36 UTC
[incubator-servicecomb-service-center] branch master updated:
SCB-680 Optimize find instance api (#387)
This is an automated email from the ASF dual-hosted git repository.
asifdxtreme pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-service-center.git
The following commit(s) were added to refs/heads/master by this push:
new d085bc4 SCB-680 Optimize find instance api (#387)
d085bc4 is described below
commit d085bc48e18c30647bdc9d7e00b556e88b309514
Author: little-cui <su...@qq.com>
AuthorDate: Sat Jul 7 23:36:34 2018 +0800
SCB-680 Optimize find instance api (#387)
* Reconstruct core package
* Update other packages
* Fix some bugs
* Optimize indexer
* Fix UT failure
* Optimize indexer and cache
* Add cache tree package
* Fix some Bugs
* Release indexer when renew one
* Optimize notification package
* Optimize websocket package
* Fix the subscriber map leak
* Restructure notification package
* Optimize notification package
* Fix cache do get updating bug.
* Add cache metrics
* Fix timer bug and cache bug
* Fix UT failure
* Add notification UT
---
pkg/async/service.go | 17 +-
.../util/schema_util_test.go => pkg/cache/cache.go | 33 +-
pkg/cache/cache_test.go | 178 +++++
.../service/util/common.go => pkg/cache/config.go | 38 +-
.../core/backend/cacher.go => pkg/cache/filter.go | 19 +-
.../backend/extend_test.go => pkg/cache/node.go | 45 +-
.../core/backend/cacher.go => pkg/cache/option.go | 31 +-
.../backend/cacher.go => pkg/cache/option_test.go | 27 +-
pkg/cache/tree.go | 160 ++++
pkg/util/concurrent_map.go | 27 +-
pkg/util/concurrent_map_test.go | 10 +-
pkg/util/goroutines.go | 8 +-
pkg/util/util.go | 16 +
pkg/util/util_test.go | 10 +
server/broker/service.go | 22 +-
server/broker/store.go | 28 +-
server/broker/util.go | 18 +-
server/core/backend/{cacher.go => cache.go} | 13 +-
server/core/backend/cache_kv.go | 534 +++----------
server/core/backend/cache_null.go | 50 +-
.../core/backend/{cacher_test.go => cache_test.go} | 76 +-
server/core/backend/{cache_kv.go => cacher_kv.go} | 362 ++++-----
server/core/backend/common.go | 123 +--
server/core/backend/config.go | 57 +-
server/core/backend/defer_instance.go | 44 +-
server/core/backend/defer_test.go | 77 +-
server/core/backend/event.go | 4 +-
server/core/backend/event_proxy.go | 4 +-
server/core/backend/extend.go | 37 +-
server/core/backend/extend_test.go | 8 +-
server/core/backend/indexer.go | 350 +--------
server/core/backend/indexer_kv.go | 150 ++++
server/core/backend/{cacher.go => kv.go} | 31 +-
server/core/backend/lease.go | 14 +-
server/core/backend/listwatch.go | 52 +-
server/core/backend/parser.go | 206 +++++
server/core/backend/parser_test.go | 80 ++
server/core/backend/store.go | 113 +--
server/core/key_generator.go | 76 +-
server/core/proto/services.go | 127 +--
server/govern/service.go | 12 +-
server/plugin/infra/quota/buildin/buildin.go | 4 +-
server/plugin/infra/registry/etcd/etcd.go | 3 +-
server/server.go | 5 -
server/service/{util => cache}/common.go | 15 +-
server/service/cache/dependency.go | 64 ++
.../filter_consumer.go} | 24 +-
server/service/cache/filter_instances.go | 72 ++
.../filter_permission.go} | 52 +-
.../tag_util_test.go => cache/filter_service.go} | 31 +-
server/service/cache/filter_tags.go | 83 ++
.../filter_version.go} | 51 +-
server/service/cache/instance.go | 73 ++
server/service/event/dependency_event_handler.go | 25 +-
server/service/event/instance_event_handler.go | 58 +-
server/service/event/rule_event_handler.go | 32 +-
server/service/event/service_event_handler.go | 37 +-
server/service/event/tag_event_handler.go | 49 +-
server/service/instance.go | 117 ++-
server/service/instance_test.go | 6 +-
server/service/microservice.go | 2 +-
server/service/notification/common.go | 32 +-
.../tag_util_test.go => notification/group.go} | 52 +-
server/service/notification/group_test.go | 69 ++
server/service/notification/listwatcher.go | 81 +-
server/service/notification/notice.go | 22 +-
.../notification/notification_healthchecker.go | 18 +-
.../service/notification/notification_service.go | 233 ++----
server/service/notification/notification_test.go | 86 ++
server/service/notification/processor.go | 106 +++
server/service/notification/processor_test.go | 74 ++
server/service/notification/publisher.go | 103 +++
server/service/notification/stream.go | 23 +-
.../stream_test.go} | 27 +-
server/service/notification/subject.go | 76 ++
server/service/notification/subject_test.go | 65 ++
server/service/notification/subscriber.go | 63 +-
server/service/notification/websocket.go | 259 +++---
server/service/notification/websocket_test.go | 120 +++
server/service/schema.go | 16 +-
server/service/tag_test.go | 10 +-
server/service/util/common.go | 4 -
server/service/util/dependency.go | 867 ---------------------
server/service/util/dependency_query.go | 351 +++++++++
server/service/util/dependency_test.go | 167 +---
server/service/util/dependency_util.go | 427 ++++++++++
server/service/util/domain_util.go | 3 +-
server/service/util/domain_util_test.go | 37 +-
server/service/util/find_cache.go | 71 --
server/service/util/find_cache_test.go | 60 --
server/service/util/instance_util.go | 112 +--
server/service/util/instance_util_test.go | 59 +-
server/service/util/microservice_util.go | 44 +-
server/service/util/rule_util.go | 54 +-
server/service/util/rule_util_test.go | 62 +-
server/service/util/schema_util_test.go | 10 +-
server/service/util/tag_util.go | 10 +-
server/service/util/tag_util_test.go | 8 +-
server/service/util/util_suite_test.go | 138 +---
server/service/util/versionrule.go | 37 +-
server/service/util/versionrule_test.go | 85 +-
server/service/watch.go | 4 +-
102 files changed, 4235 insertions(+), 4000 deletions(-)
diff --git a/pkg/async/service.go b/pkg/async/service.go
index b83b8ea..3551e56 100644
--- a/pkg/async/service.go
+++ b/pkg/async/service.go
@@ -26,10 +26,10 @@ import (
)
const (
- maxExecutorCount = 1000
+ initExecutorCount = 1000
removeExecutorInterval = 30 * time.Second
initExecutorTTL = 4
- executeInterval = 100 * time.Millisecond
+ executeInterval = 1 * time.Second
compactTimes = 2
)
@@ -107,12 +107,11 @@ func (lat *TaskService) daemon(ctx context.Context) {
util.SafeCloseChan(lat.ready)
ticker := time.NewTicker(removeExecutorInterval)
max := 0
+ timer := time.NewTimer(executeInterval)
+ defer timer.Stop()
for {
- timer := time.NewTimer(executeInterval)
select {
case <-ctx.Done():
- timer.Stop()
-
util.Logger().Debugf("daemon thread exited for TaskService is stopped")
return
case <-timer.C:
@@ -127,8 +126,10 @@ func (lat *TaskService) daemon(ctx context.Context) {
for _, s := range slice {
s.Execute() // non-blocked
}
+
+ timer.Reset(executeInterval)
case <-ticker.C:
- timer.Stop()
+ util.ResetTimer(timer, executeInterval)
lat.lock.RLock()
l := len(lat.executors)
@@ -154,7 +155,7 @@ func (lat *TaskService) daemon(ctx context.Context) {
}
l = len(lat.executors)
- if max > maxExecutorCount && max > l*compactTimes {
+ if max > initExecutorCount && max > l*compactTimes {
lat.renew()
max = l
}
@@ -200,7 +201,7 @@ func (lat *TaskService) Ready() <-chan struct{} {
}
func (lat *TaskService) renew() {
- newExecutor := make(map[string]*executorWithTTL, maxExecutorCount)
+ newExecutor := make(map[string]*executorWithTTL)
for k, e := range lat.executors {
newExecutor[k] = e
}
diff --git a/server/service/util/schema_util_test.go b/pkg/cache/cache.go
similarity index 68%
copy from server/service/util/schema_util_test.go
copy to pkg/cache/cache.go
index 7ba7146..7f53552 100644
--- a/server/service/util/schema_util_test.go
+++ b/pkg/cache/cache.go
@@ -14,22 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package cache
import (
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
"golang.org/x/net/context"
- "testing"
+ "sync"
)
-func TestCheckSchemaInfoExist(t *testing.T) {
- _, err := CheckSchemaInfoExist(util.SetContext(context.Background(), CTX_CACHEONLY, "1"), "")
- if err != nil {
- t.FailNow()
- }
+type Cache struct {
+ data context.Context
+ lock sync.RWMutex
+}
+
+func (c *Cache) Set(k string, v interface{}) {
+ c.lock.Lock()
+ c.data = context.WithValue(c.data, k, v)
+ c.lock.Unlock()
+}
+
+func (c *Cache) Get(k string) (v interface{}) {
+ c.lock.RLock()
+ v = c.data.Value(k)
+ c.lock.RUnlock()
+ return
+}
- _, err = CheckSchemaInfoExist(context.Background(), "")
- if err == nil {
- t.FailNow()
+func NewCache() *Cache {
+ return &Cache{
+ data: context.Background(),
}
}
diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go
new file mode 100644
index 0000000..331666e
--- /dev/null
+++ b/pkg/cache/cache_test.go
@@ -0,0 +1,178 @@
+/*
+ * 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 cache
+
+import (
+ "fmt"
+ "golang.org/x/net/context"
+ "testing"
+ "time"
+)
+
+type level1 struct {
+}
+
+func (l *level1) Name(ctx context.Context) string {
+ return ctx.Value("key1").(string)
+}
+
+func (l *level1) Init(ctx context.Context, parent *Node) (node *Node, err error) {
+ p := l.Name(ctx)
+ if p == "err" {
+ return nil, fmt.Errorf("wrong logic")
+ }
+
+ if p == "null" {
+ return
+ }
+
+ node = NewNode()
+ node.Cache.Set("a", "a")
+ node.Cache.Set("b", "b")
+ node.Cache.Set("c", "c")
+ return
+}
+
+type level2 struct {
+ changed string
+}
+
+func (l *level2) Name(ctx context.Context) string {
+ return ctx.Value("key2").(string)
+}
+
+func (l *level2) Init(ctx context.Context, parent *Node) (node *Node, err error) {
+ if parent == nil {
+ return
+ }
+
+ p := l.Name(ctx)
+ if p == "err" {
+ return nil, fmt.Errorf("wrong logic")
+ }
+
+ if p == "null" {
+ return
+ }
+
+ node = NewNode()
+ if len(l.changed) == 0 {
+ i := parent.Cache.Get(p)
+ node.Cache.Set("aa", i)
+ } else {
+ node.Cache.Set("aa", l.changed)
+ }
+
+ return
+}
+
+func TestTree_Get(t *testing.T) {
+ tree := NewTree(Configure().WithMaxSize(1).WithTTL(time.Second))
+ node, err := tree.Get(context.Background())
+ if node != nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ tree.Remove(context.Background())
+
+ l2 := &level2{}
+ tree.AddFilter(&level1{}, l2)
+
+ ctx := context.WithValue(context.WithValue(context.Background(), "key1", "err"), "key2", "")
+ node, err = tree.Get(ctx)
+ if node != nil || err == nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "null"), "key2", ""))
+ if node != nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "err"))
+ node, err = tree.Get(ctx)
+ if node != nil || err == nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "null"))
+ if node != nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "err"))
+ if node != nil || err == nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "b"))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("aa") != "b" {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "a"))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("aa") != "a" {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "b"))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("aa") != "b" {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ tree.Remove(context.WithValue(ctx, "key1", "1"))
+
+ l2.changed = "changed"
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "b"))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("aa") != "changed" {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ l2.changed = ""
+ node, err = tree.Get(context.WithValue(context.WithValue(ctx, "key1", "1"), "key2", "b"),
+ Options().Temporary(true))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("aa") != "b" {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ node, err = tree.Get(context.WithValue(ctx, "key1", "1"), Options().ToLevel(1))
+ if node == nil || err != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+ if node.Cache.Get("a") != "a" || node.Cache.Get("aa") != nil {
+ t.Fatalf("TestTree_Get failed")
+ }
+
+ childs := node.ChildNodes()
+ if len(childs) != 1 || childs[0].Cache.Get("aa") != "changed" {
+ t.Fatalf("TestTree_Get failed")
+ }
+}
diff --git a/server/service/util/common.go b/pkg/cache/config.go
similarity index 66%
copy from server/service/util/common.go
copy to pkg/cache/config.go
index 9e31b3f..fac805d 100644
--- a/server/service/util/common.go
+++ b/pkg/cache/config.go
@@ -14,16 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package cache
import "time"
-const (
- HEADER_REV = "X-Resource-Revision"
- CTX_NOCACHE = "noCache"
- CTX_CACHEONLY = "cacheOnly"
- CTX_REQUEST_REVISION = "requestRev"
- CTX_RESPONSE_REVISION = "responseRev"
+type Config struct {
+ ttl time.Duration
+ max int64
+}
- cacheTTL = 5 * time.Minute
-)
+func (c *Config) TTL() time.Duration {
+ return c.ttl
+}
+
+func (c *Config) WithTTL(ttl time.Duration) *Config {
+ c.ttl = ttl
+ return c
+}
+
+func (c *Config) MaxSize() int64 {
+ return c.max
+}
+
+func (c *Config) WithMaxSize(s int64) *Config {
+ c.max = s
+ return c
+}
+
+func Configure() *Config {
+ return &Config{
+ ttl: 5 * time.Minute,
+ max: 5000,
+ }
+}
diff --git a/server/core/backend/cacher.go b/pkg/cache/filter.go
similarity index 75%
copy from server/core/backend/cacher.go
copy to pkg/cache/filter.go
index 410ef50..51c6448 100644
--- a/server/core/backend/cacher.go
+++ b/pkg/cache/filter.go
@@ -14,20 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package backend
+package cache
-type Cache interface {
- Version() int64
- Data(interface{}) interface{}
- Have(interface{}) bool
- Size() int
-}
+import "golang.org/x/net/context"
-type Cacher interface {
- // Name is the cache size metric name
- Name() string
- Cache() Cache
- Run()
- Stop()
- Ready() <-chan struct{}
+type Filter interface {
+ Name(ctx context.Context) string
+ Init(ctx context.Context, parent *Node) (*Node, error)
}
diff --git a/server/core/backend/extend_test.go b/pkg/cache/node.go
similarity index 60%
copy from server/core/backend/extend_test.go
copy to pkg/cache/node.go
index 6a37ce5..0e9b439 100644
--- a/server/core/backend/extend_test.go
+++ b/pkg/cache/node.go
@@ -14,36 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package backend
+package cache
-import "testing"
+import "github.com/apache/incubator-servicecomb-service-center/pkg/util"
-type extend struct {
+type Node struct {
+ // user data
+ Cache *Cache
+ // tree will set the value below after the node added in.
+ Name string
+ Tree *Tree
+ Childs *util.ConcurrentMap
+ Level int
}
-func (e *extend) Name() string {
- return "test"
+func (n *Node) ChildNodes() (nodes []*Node) {
+ n.Childs.ForEach(func(item util.MapItem) (next bool) {
+ nodes = append(nodes, item.Value.(*Node))
+ return true
+ })
+ return
}
-func (e *extend) Prefix() string {
- return "/test"
-}
-
-func (e *extend) InitSize() int {
- return 0
-}
-
-func TestInstallType(t *testing.T) {
- id, err := InstallType(&extend{})
- if err != nil {
- t.Fatal(err)
- }
- if id == NONEXIST {
- t.Fatal(err)
- }
-
- id, err = InstallType(&extend{})
- if id != NONEXIST || err == nil {
- t.Fatal("InstallType fail", err)
+func NewNode() *Node {
+ return &Node{
+ Cache: NewCache(),
+ Childs: util.NewConcurrentMap(0),
}
}
diff --git a/server/core/backend/cacher.go b/pkg/cache/option.go
similarity index 73%
copy from server/core/backend/cacher.go
copy to pkg/cache/option.go
index 410ef50..4250ca1 100644
--- a/server/core/backend/cacher.go
+++ b/pkg/cache/option.go
@@ -14,20 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package backend
+package cache
-type Cache interface {
- Version() int64
- Data(interface{}) interface{}
- Have(interface{}) bool
- Size() int
+type Option struct {
+ Level int
+ NoCache bool
}
-type Cacher interface {
- // Name is the cache size metric name
- Name() string
- Cache() Cache
- Run()
- Stop()
- Ready() <-chan struct{}
+// get node before level l
+func (op Option) ToLevel(l int) Option {
+ op.Level = l
+ return op
+}
+
+// get node without cache
+func (op Option) Temporary(b bool) Option {
+ op.NoCache = b
+ return op
+}
+
+func Options() (op Option) {
+ return
}
diff --git a/server/core/backend/cacher.go b/pkg/cache/option_test.go
similarity index 70%
copy from server/core/backend/cacher.go
copy to pkg/cache/option_test.go
index 410ef50..f756a54 100644
--- a/server/core/backend/cacher.go
+++ b/pkg/cache/option_test.go
@@ -14,20 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package backend
+package cache
-type Cache interface {
- Version() int64
- Data(interface{}) interface{}
- Have(interface{}) bool
- Size() int
-}
+import "testing"
-type Cacher interface {
- // Name is the cache size metric name
- Name() string
- Cache() Cache
- Run()
- Stop()
- Ready() <-chan struct{}
+func BenchmarkWithLevel(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ op := Options().ToLevel(3).ToLevel(2).ToLevel(1)
+ if op.Level != 1 {
+ b.Fatalf("BenchmarkWithLevel failed")
+ }
+ }
+ })
+ b.ReportAllocs()
+ // 2000000000 0.28 ns/op 0 B/op 0 allocs/op
}
diff --git a/pkg/cache/tree.go b/pkg/cache/tree.go
new file mode 100644
index 0000000..9c89df0
--- /dev/null
+++ b/pkg/cache/tree.go
@@ -0,0 +1,160 @@
+/*
+ * 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 cache
+
+import (
+ "errors"
+ "github.com/karlseguin/ccache"
+ "golang.org/x/net/context"
+ "sync"
+)
+
+var errNilNode = errors.New("nil node")
+
+type Tree struct {
+ Config *Config
+ roots *ccache.Cache
+ filters []Filter
+ lock sync.RWMutex
+}
+
+func (t *Tree) AddFilter(fs ...Filter) *Tree {
+ t.lock.Lock()
+ for _, f := range fs {
+ t.filters = append(t.filters, f)
+ }
+ t.lock.Unlock()
+ return t
+}
+
+func (t *Tree) Get(ctx context.Context, ops ...Option) (node *Node, err error) {
+ var op Option
+ if len(ops) > 0 {
+ op = ops[0]
+ }
+
+ var (
+ parent *Node
+ i int
+ )
+
+ if !op.NoCache {
+ if parent, err = t.getOrCreateRoot(ctx); parent == nil {
+ return
+ }
+ i++
+ // parent may be a temp root in concurrent scene
+ }
+
+ for ; i < len(t.filters); i++ {
+ if op.Level > 0 && op.Level == i {
+ break
+ }
+ if parent, err = t.getOrCreateNode(ctx, i, parent); parent == nil {
+ break
+ }
+ }
+ node = parent
+ return
+}
+
+func (t *Tree) Remove(ctx context.Context) {
+ if len(t.filters) == 0 {
+ return
+ }
+
+ t.roots.Delete(t.filters[0].Name(ctx))
+}
+
+func (t *Tree) getOrCreateRoot(ctx context.Context) (node *Node, err error) {
+ if len(t.filters) == 0 {
+ return
+ }
+
+ filter := t.filters[0]
+ name := filter.Name(ctx)
+ item, err := t.roots.Fetch(name, t.Config.TTL(), func() (interface{}, error) {
+ node, err := t.getOrCreateNode(ctx, 0, nil)
+ if err != nil {
+ return nil, err
+ }
+ if node == nil {
+ return nil, errNilNode
+ }
+ return node, nil
+ })
+ switch err {
+ case nil:
+ node = item.Value().(*Node)
+ case errNilNode:
+ err = nil
+ }
+ return
+}
+
+func (t *Tree) getOrCreateNode(ctx context.Context, idx int, parent *Node) (node *Node, err error) {
+ filter := t.filters[idx]
+ name := t.nodeFullName(filter.Name(ctx), parent)
+
+ if parent == nil {
+ // new a temp node
+ return t.createNode(ctx, idx, name, parent)
+ }
+
+ item, err := parent.Childs.Fetch(name, func() (interface{}, error) {
+ node, err := t.createNode(ctx, idx, name, parent)
+ if err != nil {
+ return nil, err
+ }
+ if node == nil {
+ return nil, errNilNode
+ }
+ return node, nil
+ })
+ switch err {
+ case nil:
+ node = item.(*Node)
+ case errNilNode:
+ err = nil
+ }
+ return
+}
+
+func (t *Tree) nodeFullName(name string, parent *Node) string {
+ if parent != nil {
+ name = parent.Name + "." + name
+ }
+ return name
+}
+
+func (t *Tree) createNode(ctx context.Context, idx int, name string, parent *Node) (node *Node, err error) {
+ node, err = t.filters[idx].Init(ctx, parent)
+ if node == nil {
+ return
+ }
+ node.Name = name
+ node.Tree = t
+ node.Level = idx
+ return
+}
+
+func NewTree(cfg *Config) *Tree {
+ return &Tree{
+ Config: cfg,
+ roots: ccache.New(ccache.Configure().MaxSize(cfg.MaxSize())),
+ }
+}
diff --git a/pkg/util/concurrent_map.go b/pkg/util/concurrent_map.go
index 546ec47..fce2c3c 100644
--- a/pkg/util/concurrent_map.go
+++ b/pkg/util/concurrent_map.go
@@ -46,18 +46,37 @@ func (cm *ConcurrentMap) Put(key, val interface{}) (old interface{}) {
return
}
-func (cm *ConcurrentMap) PutIfAbsent(key, val interface{}) (old interface{}) {
- var b bool
+func (cm *ConcurrentMap) PutIfAbsent(key, val interface{}) (exist interface{}) {
cm.init()
cm.mux.Lock()
- old, b = cm.items[key]
+ var b bool
+ exist, b = cm.items[key]
if !b {
- cm.items[key] = val
+ cm.items[key], exist = val, val
}
cm.mux.Unlock()
return
}
+func (cm *ConcurrentMap) Fetch(key interface{}, f func() (interface{}, error)) (exist interface{}, err error) {
+ cm.init()
+ cm.mux.RLock()
+ var b bool
+ exist, b = cm.items[key]
+ cm.mux.RUnlock()
+ if !b {
+ cm.mux.Lock()
+ exist, b = cm.items[key]
+ if !b {
+ if exist, err = f(); err == nil {
+ cm.items[key] = exist
+ }
+ }
+ cm.mux.Unlock()
+ }
+ return
+}
+
func (cm *ConcurrentMap) Get(key interface{}) (val interface{}, b bool) {
cm.init()
cm.mux.RLock()
diff --git a/pkg/util/concurrent_map_test.go b/pkg/util/concurrent_map_test.go
index 344de5d..9259d62 100644
--- a/pkg/util/concurrent_map_test.go
+++ b/pkg/util/concurrent_map_test.go
@@ -42,14 +42,8 @@ func TestConcurrentMap(t *testing.T) {
if v.(string) != "1" {
t.Fatalf("TestConcurrentMap Put an item again failed.")
}
- v = cm.PutIfAbsent("b", "1")
- if v != nil {
- t.Fatalf("TestConcurrentMap PutIfAbsent a not exist item failed.")
- }
- v = cm.PutIfAbsent("a", "3")
- if v.(string) != "2" {
- t.Fatalf("TestConcurrentMap PutIfAbsent an item failed.")
- }
+ cm.PutIfAbsent("b", "1")
+ cm.PutIfAbsent("a", "3")
v, b = cm.Get("a")
if !b || v.(string) != "2" {
t.Fatalf("TestConcurrentMap Get an item after PutIfAbsent failed.")
diff --git a/pkg/util/goroutines.go b/pkg/util/goroutines.go
index 7ee48a9..74689f3 100644
--- a/pkg/util/goroutines.go
+++ b/pkg/util/goroutines.go
@@ -63,15 +63,12 @@ func (g *GoRoutine) loop(f func(context.Context)) {
g.wg.Add(1)
defer g.wg.Done()
defer func() { <-g.concurrent }()
+
timer := time.NewTimer(GlobalPoolConfig.IdleTimeout)
+ defer timer.Stop()
for {
g.execute(f)
- if !timer.Stop() {
- <-timer.C
- }
- timer.Reset(GlobalPoolConfig.IdleTimeout)
-
select {
case <-timer.C:
return
@@ -79,6 +76,7 @@ func (g *GoRoutine) loop(f func(context.Context)) {
if f == nil {
return
}
+ ResetTimer(timer, GlobalPoolConfig.IdleTimeout)
}
}
}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index 44b8b09..5c3b3f0 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -211,3 +211,19 @@ func SliceHave(arr []string, str string) bool {
}
return false
}
+
+// do not call after drain timer.C channel
+func ResetTimer(timer *time.Timer, d time.Duration) {
+ if !timer.Stop() {
+ // timer is expired: can not find the timer in timer stack
+ // select {
+ // case <-timer.C:
+ // // here block when drain channel call before timer.Stop()
+ // default:
+ // // here will cause a BUG When sendTime() after drain channel
+ // // BUG: timer.C still trigger even after timer.Reset()
+ // }
+ <-timer.C
+ }
+ timer.Reset(d)
+}
diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go
index 932aa11..596e931 100644
--- a/pkg/util/util_test.go
+++ b/pkg/util/util_test.go
@@ -18,6 +18,7 @@ package util
import (
"testing"
+ "time"
)
func TestInt16ToInt64(t *testing.T) {
@@ -72,3 +73,12 @@ func TestFileLastName(t *testing.T) {
t.Fatal("TestFileLastName 'b' failed", n)
}
}
+
+func TestResetTimer(t *testing.T) {
+ timer := time.NewTimer(time.Microsecond)
+ <-timer.C
+ ResetTimer(timer, time.Microsecond)
+ <-timer.C
+ ResetTimer(timer, time.Second)
+ <-timer.C
+}
diff --git a/server/broker/service.go b/server/broker/service.go
index f4ea526..c36cb3e 100644
--- a/server/broker/service.go
+++ b/server/broker/service.go
@@ -151,7 +151,7 @@ func (*BrokerService) RetrieveProviderPacts(ctx context.Context,
versionObjects := make(map[int32]Version)
for i := 0; i < len(versions.Kvs); i++ {
version := &Version{}
- err = json.Unmarshal(versions.Kvs[i].Value, version)
+ err = json.Unmarshal(versions.Kvs[i].Value.([]byte), version)
if err != nil {
return nil, err
}
@@ -174,7 +174,7 @@ func (*BrokerService) RetrieveProviderPacts(ctx context.Context,
participantToVersionObj := make(map[int32]Version)
for i := 0; i < len(pactVersions.Kvs); i++ {
pactVersion := &PactVersion{}
- err = json.Unmarshal(pactVersions.Kvs[i].Value, pactVersion)
+ err = json.Unmarshal(pactVersions.Kvs[i].Value.([]byte), pactVersion)
if err != nil {
return nil, err
}
@@ -206,7 +206,7 @@ func (*BrokerService) RetrieveProviderPacts(ctx context.Context,
consumerInfoArr := make([]*ConsumerInfo, 0)
for i := 0; i < len(participants.Kvs); i++ {
participant := &Participant{}
- err = json.Unmarshal(participants.Kvs[i].Value, participant)
+ err = json.Unmarshal(participants.Kvs[i].Value.([]byte), participant)
if err != nil {
return nil, err
}
@@ -304,7 +304,7 @@ func (*BrokerService) GetAllProviderPacts(ctx context.Context,
versionObjects := make(map[int32]Version)
for i := 0; i < len(versions.Kvs); i++ {
version := &Version{}
- err = json.Unmarshal(versions.Kvs[i].Value, version)
+ err = json.Unmarshal(versions.Kvs[i].Value.([]byte), version)
if err != nil {
return nil, err
}
@@ -327,7 +327,7 @@ func (*BrokerService) GetAllProviderPacts(ctx context.Context,
participantToVersionObj := make(map[int32]Version)
for i := 0; i < len(pactVersions.Kvs); i++ {
pactVersion := &PactVersion{}
- err = json.Unmarshal(pactVersions.Kvs[i].Value, pactVersion)
+ err = json.Unmarshal(pactVersions.Kvs[i].Value.([]byte), pactVersion)
if err != nil {
return nil, err
}
@@ -359,7 +359,7 @@ func (*BrokerService) GetAllProviderPacts(ctx context.Context,
consumerInfoArr := make([]*ConsumerInfo, 0)
for i := 0; i < len(participants.Kvs); i++ {
participant := &Participant{}
- err = json.Unmarshal(participants.Kvs[i].Value, participant)
+ err = json.Unmarshal(participants.Kvs[i].Value.([]byte), participant)
if err != nil {
return nil, err
}
@@ -466,7 +466,7 @@ func (*BrokerService) RetrieveVerificationResults(ctx context.Context, in *Retri
verificationDetailsArr := make([]*VerificationDetail, 0)
for j := 0; j < len(pactVersions.Kvs); j++ {
pactVersion := &PactVersion{}
- err = json.Unmarshal(pactVersions.Kvs[j].Value, &pactVersion)
+ err = json.Unmarshal(pactVersions.Kvs[j].Value.([]byte), &pactVersion)
if err != nil {
PactLogger.Errorf(nil, "verification result retrieve request failed, pact version cannot be searched.")
return &RetrieveVerificationResponse{
@@ -488,7 +488,7 @@ func (*BrokerService) RetrieveVerificationResults(ctx context.Context, in *Retri
var lastVerificationResult *Verification
for i := 0; i < len(verifications.Kvs); i++ {
verification := &Verification{}
- err = json.Unmarshal(verifications.Kvs[i].Value, &verification)
+ err = json.Unmarshal(verifications.Kvs[i].Value.([]byte), &verification)
if err != nil {
PactLogger.Errorf(nil, "verification result retrieve request failed, verification result unmarshall error.")
return &RetrieveVerificationResponse{
@@ -525,7 +525,7 @@ func (*BrokerService) RetrieveVerificationResults(ctx context.Context, in *Retri
var providerParticipant *Participant
for i := 0; i < len(participants.Kvs); i++ {
participant := &Participant{}
- err = json.Unmarshal(participants.Kvs[i].Value, &participant)
+ err = json.Unmarshal(participants.Kvs[i].Value.([]byte), &participant)
if err != nil {
PactLogger.Errorf(nil, "verification result retrieve request failed, verification result unmarshall error.")
return &RetrieveVerificationResponse{
@@ -635,7 +635,7 @@ func (*BrokerService) PublishVerificationResults(ctx context.Context, in *Publis
pactExists := false
for i := 0; i < len(pacts.Kvs); i++ {
pact := &Pact{}
- err = json.Unmarshal(pacts.Kvs[i].Value, &pact)
+ err = json.Unmarshal(pacts.Kvs[i].Value.([]byte), &pact)
if err != nil {
PactLogger.Errorf(nil, "verification result publish request failed, pact cannot be searched.")
return &PublishVerificationResponse{
@@ -675,7 +675,7 @@ func (*BrokerService) PublishVerificationResults(ctx context.Context, in *Publis
if len(verifications.Kvs) != 0 {
for i := 0; i < len(verifications.Kvs); i++ {
verification := &Verification{}
- err = json.Unmarshal(verifications.Kvs[i].Value, &verification)
+ err = json.Unmarshal(verifications.Kvs[i].Value.([]byte), &verification)
if err != nil {
PactLogger.Errorf(nil, "verification result publish request failed, verification result unmarshall error.")
return &PublishVerificationResponse{
diff --git a/server/broker/store.go b/server/broker/store.go
index 51acaca..cb73923 100644
--- a/server/broker/store.go
+++ b/server/broker/store.go
@@ -33,44 +33,44 @@ var (
var brokerKvStore = &BKvStore{}
func init() {
- PARTICIPANT = backend.Store().MustInstall(backend.NewEntity("PARTICIPANT", GetBrokerParticipantKey("")))
- VERSION = backend.Store().MustInstall(backend.NewEntity("VERSION", GetBrokerVersionKey("")))
- PACT = backend.Store().MustInstall(backend.NewEntity("PACT", GetBrokerPactKey("")))
- PACT_VERSION = backend.Store().MustInstall(backend.NewEntity("PACT_VERSION", GetBrokerPactVersionKey("")))
- PACT_TAG = backend.Store().MustInstall(backend.NewEntity("PACT_TAG", GetBrokerTagKey("")))
- VERIFICATION = backend.Store().MustInstall(backend.NewEntity("VERIFICATION", GetBrokerVerificationKey("")))
- PACT_LATEST = backend.Store().MustInstall(backend.NewEntity("PACT_LATEST", GetBrokerLatestKey("")))
+ PARTICIPANT = backend.Store().MustInstall(backend.NewEntity("PARTICIPANT", backend.DefaultConfig().WithPrefix(GetBrokerParticipantKey(""))))
+ VERSION = backend.Store().MustInstall(backend.NewEntity("VERSION", backend.DefaultConfig().WithPrefix(GetBrokerVersionKey(""))))
+ PACT = backend.Store().MustInstall(backend.NewEntity("PACT", backend.DefaultConfig().WithPrefix(GetBrokerPactKey(""))))
+ PACT_VERSION = backend.Store().MustInstall(backend.NewEntity("PACT_VERSION", backend.DefaultConfig().WithPrefix(GetBrokerPactVersionKey(""))))
+ PACT_TAG = backend.Store().MustInstall(backend.NewEntity("PACT_TAG", backend.DefaultConfig().WithPrefix(GetBrokerTagKey(""))))
+ VERIFICATION = backend.Store().MustInstall(backend.NewEntity("VERIFICATION", backend.DefaultConfig().WithPrefix(GetBrokerVerificationKey(""))))
+ PACT_LATEST = backend.Store().MustInstall(backend.NewEntity("PACT_LATEST", backend.DefaultConfig().WithPrefix(GetBrokerLatestKey(""))))
}
type BKvStore struct {
}
-func (s *BKvStore) Participant() *backend.Indexer {
+func (s *BKvStore) Participant() backend.Indexer {
return backend.Store().Entity(PARTICIPANT)
}
-func (s *BKvStore) Version() *backend.Indexer {
+func (s *BKvStore) Version() backend.Indexer {
return backend.Store().Entity(VERSION)
}
-func (s *BKvStore) Pact() *backend.Indexer {
+func (s *BKvStore) Pact() backend.Indexer {
return backend.Store().Entity(PACT)
}
-func (s *BKvStore) PactVersion() *backend.Indexer {
+func (s *BKvStore) PactVersion() backend.Indexer {
return backend.Store().Entity(PACT_VERSION)
}
-func (s *BKvStore) PactTag() *backend.Indexer {
+func (s *BKvStore) PactTag() backend.Indexer {
return backend.Store().Entity(PACT_TAG)
}
-func (s *BKvStore) Verification() *backend.Indexer {
+func (s *BKvStore) Verification() backend.Indexer {
return backend.Store().Entity(VERIFICATION)
}
-func (s *BKvStore) PactLatest() *backend.Indexer {
+func (s *BKvStore) PactLatest() backend.Indexer {
return backend.Store().Entity(PACT_LATEST)
}
diff --git a/server/broker/util.go b/server/broker/util.go
index 895ee8c..3d398b5 100644
--- a/server/broker/util.go
+++ b/server/broker/util.go
@@ -187,7 +187,7 @@ func GetBrokerParticipantUtils(ctx context.Context, tenant string, appId string,
}
participant := &Participant{}
- err = json.Unmarshal(participants.Kvs[0].Value, participant)
+ err = json.Unmarshal(participants.Kvs[0].Value.([]byte), participant)
if err != nil {
return nil, err
}
@@ -259,7 +259,7 @@ func GetParticipant(ctx context.Context, domain string, appId string,
return nil, nil
}
participant := &Participant{}
- err = json.Unmarshal(participants.Kvs[0].Value, participant)
+ err = json.Unmarshal(participants.Kvs[0].Value.([]byte), participant)
if err != nil {
return nil, err
}
@@ -278,7 +278,7 @@ func GetVersion(ctx context.Context, domain string, number string,
return nil, nil
}
version := &Version{}
- err = json.Unmarshal(versions.Kvs[0].Value, version)
+ err = json.Unmarshal(versions.Kvs[0].Value.([]byte), version)
if err != nil {
return nil, err
}
@@ -296,7 +296,7 @@ func GetPact(ctx context.Context, domain string, consumerParticipantId int32, pr
return nil, nil
}
pact := &Pact{}
- err = json.Unmarshal(versions.Kvs[0].Value, pact)
+ err = json.Unmarshal(versions.Kvs[0].Value.([]byte), pact)
if err != nil {
return nil, err
}
@@ -315,7 +315,7 @@ func GetPactVersion(ctx context.Context, domain string, versionId int32,
return nil, nil
}
pactVersion := &PactVersion{}
- err = json.Unmarshal(versions.Kvs[0].Value, pactVersion)
+ err = json.Unmarshal(versions.Kvs[0].Value.([]byte), pactVersion)
if err != nil {
return nil, err
}
@@ -331,7 +331,7 @@ func GetData(ctx context.Context, key string) (int, error) {
if len(values.Kvs) == 0 {
return -1, nil
}
- id, err := strconv.Atoi(string(values.Kvs[0].Value))
+ id, err := strconv.Atoi(string(values.Kvs[0].Value.([]byte)))
if err != nil {
return -1, err
}
@@ -525,7 +525,7 @@ func GetLastestVersionNumberForParticipant(ctx context.Context,
order := int32(math.MinInt32)
for i := 0; i < len(versions.Kvs); i++ {
version := &Version{}
- err = json.Unmarshal(versions.Kvs[i].Value, &version)
+ err = json.Unmarshal(versions.Kvs[i].Value.([]byte), &version)
if err != nil {
return -1
}
@@ -622,7 +622,7 @@ func RetrieveProviderConsumerPact(ctx context.Context,
pactIds := make(map[int32]int32)
for i := 0; i < len(pactVersions.Kvs); i++ {
pactVersion := &PactVersion{}
- err = json.Unmarshal(pactVersions.Kvs[i].Value, pactVersion)
+ err = json.Unmarshal(pactVersions.Kvs[i].Value.([]byte), pactVersion)
if err != nil {
return nil, -1, err
}
@@ -656,7 +656,7 @@ func RetrieveProviderConsumerPact(ctx context.Context,
}
for i := 0; i < len(pacts.Kvs); i++ {
pactObj := &Pact{}
- err = json.Unmarshal(pacts.Kvs[i].Value, pactObj)
+ err = json.Unmarshal(pacts.Kvs[i].Value.([]byte), pactObj)
if err != nil {
return nil, -1, err
}
diff --git a/server/core/backend/cacher.go b/server/core/backend/cache.go
similarity index 84%
copy from server/core/backend/cacher.go
copy to server/core/backend/cache.go
index 410ef50..2f9107e 100644
--- a/server/core/backend/cacher.go
+++ b/server/core/backend/cache.go
@@ -17,15 +17,18 @@
package backend
type Cache interface {
- Version() int64
- Data(interface{}) interface{}
- Have(interface{}) bool
+ Name() string
Size() int
+
+ Get(k string) *KeyValue
+ GetAll(prefix string, arr *[]*KeyValue) int
+ ForEach(iter func(k string, v *KeyValue) (next bool))
+
+ Put(k string, v *KeyValue)
+ Remove(k string)
}
type Cacher interface {
- // Name is the cache size metric name
- Name() string
Cache() Cache
Run()
Stop()
diff --git a/server/core/backend/cache_kv.go b/server/core/backend/cache_kv.go
index cedb9dc..b001544 100644
--- a/server/core/backend/cache_kv.go
+++ b/server/core/backend/cache_kv.go
@@ -17,511 +17,155 @@
package backend
import (
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
- "github.com/coreos/etcd/mvcc/mvccpb"
- "golang.org/x/net/context"
+ "strings"
"sync"
"time"
)
type KvCache struct {
- owner *KvCacher
- size int
- store map[string]*mvccpb.KeyValue
+ Cfg *Config
+ name string
+ store map[string]map[string]*KeyValue
rwMux sync.RWMutex
lastRefresh time.Time
lastMaxSize int
}
-func (c *KvCache) Version() int64 {
- return c.owner.lw.Revision()
+func (c *KvCache) Name() string {
+ return c.name
}
-func (c *KvCache) Data(k interface{}) interface{} {
- c.rwMux.RLock()
- kv, ok := c.store[k.(string)]
- c.rwMux.RUnlock()
- if !ok {
- return nil
- }
- return kv
+func (c *KvCache) Size() (l int) {
+ l = c.GetAll(c.Cfg.Prefix, nil)
+ return
}
-func (c *KvCache) Have(k interface{}) (ok bool) {
+func (c *KvCache) Get(key string) (v *KeyValue) {
c.rwMux.RLock()
- _, ok = c.store[k.(string)]
+ prefix := c.prefix(key)
+ if p, ok := c.store[prefix]; ok {
+ v, _ = p[key]
+ }
c.rwMux.RUnlock()
return
}
-func (c *KvCache) RLock() map[string]*mvccpb.KeyValue {
+func (c *KvCache) GetAll(prefix string, arr *[]*KeyValue) (count int) {
c.rwMux.RLock()
- return c.store
-}
-
-func (c *KvCache) RUnlock() {
+ count = c.getPrefixKey(arr, prefix)
c.rwMux.RUnlock()
+ return
}
-func (c *KvCache) Lock() map[string]*mvccpb.KeyValue {
+func (c *KvCache) Put(key string, v *KeyValue) {
c.rwMux.Lock()
- return c.store
-}
-
-func (c *KvCache) Unlock() {
- l := len(c.store)
- if l > c.lastMaxSize {
- c.lastMaxSize = l
- }
- if c.size >= l &&
- c.lastMaxSize > c.size*DEFAULT_COMPACT_TIMES &&
- time.Now().Sub(c.lastRefresh) >= DEFAULT_COMPACT_TIMEOUT {
- c.compact()
- c.lastMaxSize = l
- c.lastRefresh = time.Now()
- }
-
+ c.addPrefixKey(key, v)
c.rwMux.Unlock()
}
-func (c *KvCache) compact() {
- // gc
- newCache := make(map[string]*mvccpb.KeyValue, c.size)
- for k, v := range c.store {
- newCache[k] = v
- }
- c.store = newCache
-
- util.Logger().Infof("cache %s is not in use over %s, compact capacity to size %d->%d",
- c.owner.Cfg.Prefix, DEFAULT_COMPACT_TIMEOUT, c.lastMaxSize, c.size)
+func (c *KvCache) Remove(key string) {
+ c.rwMux.Lock()
+ c.deletePrefixKey(key)
+ c.rwMux.Unlock()
}
-func (c *KvCache) Size() (l int) {
+func (c *KvCache) ForEach(iter func(k string, v *KeyValue) (next bool)) {
c.rwMux.RLock()
- l = len(c.store)
- c.rwMux.RUnlock()
- return
-}
-
-type KvCacher struct {
- Cfg Config
-
- name string
- lastRev int64
- noEventCount int
-
- ready chan struct{}
- lw ListWatcher
- mux sync.Mutex
- once sync.Once
- cache *KvCache
- goroutine *util.GoRoutine
-}
-
-func (c *KvCacher) Name() string {
- return c.name
-}
-
-func (c *KvCacher) needList() bool {
- rev := c.lw.Revision()
- defer func() { c.lastRev = rev }()
-
- if rev == 0 {
- c.noEventCount = 0
- return true
- }
- if c.lastRev != rev {
- c.noEventCount = 0
- return false
- }
- c.noEventCount++
- if c.noEventCount < c.Cfg.NoEventMaxInterval {
- return false
- }
-
- util.Logger().Debugf("no events come in more then %s, need to list key %s, rev: %d",
- time.Duration(c.noEventCount)*c.Cfg.Timeout, c.Cfg.Prefix, rev)
- c.noEventCount = 0
- return true
-}
-
-func (c *KvCacher) doList(cfg ListWatchConfig) error {
- kvs, err := c.lw.List(cfg)
- if err != nil {
- return err
- }
-
- start := time.Now()
- evts := c.filter(c.lw.Revision(), kvs)
- if ec, kc := len(evts), len(kvs); c.Cfg.DeferHandler != nil && ec == 0 && kc != 0 &&
- c.Cfg.DeferHandler.Reset() {
- util.Logger().Warnf(nil, "most of the protected data(%d/%d) are recovered", kc, c.cache.Size())
- }
- c.sync(evts)
- util.LogDebugOrWarnf(start, "finish to cache key %s, %d items, rev: %d",
- c.Cfg.Prefix, len(kvs), c.lw.Revision())
-
- return nil
-}
-
-func (c *KvCacher) doWatch(cfg ListWatchConfig) error {
- watcher := c.lw.Watch(cfg)
- return c.handleWatcher(watcher)
-}
-
-func (c *KvCacher) ListAndWatch(ctx context.Context) error {
- c.mux.Lock()
- defer c.mux.Unlock()
-
- cfg := ListWatchConfig{
- Timeout: c.Cfg.Timeout,
- Context: ctx,
- }
- if c.needList() {
- c.doList(cfg)
- }
-
- util.SafeCloseChan(c.ready)
-
- return c.doWatch(cfg)
-}
-
-func (c *KvCacher) handleWatcher(watcher *Watcher) error {
- defer watcher.Stop()
- for evts := range watcher.EventBus() {
- if evts[0].Type == proto.EVT_ERROR {
- err := evts[0].Object.(error)
- return err
- }
- c.sync(evts)
- }
- return nil
-}
-
-func (c *KvCacher) needDeferHandle(evts []KvEvent) bool {
- if c.Cfg.DeferHandler == nil || !c.IsReady() {
- return false
- }
-
- return c.Cfg.DeferHandler.OnCondition(c.Cache(), evts)
-}
-
-func (c *KvCacher) refresh(ctx context.Context) {
- util.Logger().Debugf("start to list and watch %s", c.Cfg)
- retries := 0
- ticker := time.NewTicker(DEFAULT_METRICS_INTERVAL)
- for {
- nextPeriod := minWaitInterval
- if err := c.ListAndWatch(ctx); err != nil {
- nextPeriod = util.GetBackoff().Delay(retries)
- retries++
- } else {
- retries = 0
- }
-
- timer := time.NewTimer(nextPeriod)
- select {
- case <-ctx.Done():
- timer.Stop()
-
- util.Logger().Debugf("stop to list and watch %s", c.Cfg)
- return
- case <-ticker.C:
- timer.Stop()
-
- ReportCacheMetrics(c.Name(), "raw", c.cache.RLock())
- c.cache.RUnlock()
- case <-timer.C:
- }
- }
-}
-
-func (c *KvCacher) deferHandle(ctx context.Context) {
- if c.Cfg.DeferHandler == nil {
- return
- }
-
- var (
- evts = make([]KvEvent, eventBlockSize)
- i int
- )
- for {
- timer := time.NewTimer(300 * time.Millisecond)
- select {
- case <-ctx.Done():
- timer.Stop()
- return
- case evt, ok := <-c.Cfg.DeferHandler.HandleChan():
- timer.Stop()
-
- if !ok {
- <-time.After(time.Second)
- continue
+loopParent:
+ for _, p := range c.store {
+ for k, v := range p {
+ if v == nil {
+ continue loopParent
}
-
- if i >= eventBlockSize {
- c.onEvents(evts[:i])
- evts = make([]KvEvent, eventBlockSize)
- i = 0
- }
-
- evts[i] = evt
- i++
- case <-timer.C:
- if i == 0 {
- continue
+ if !iter(k, v) {
+ break loopParent
}
-
- c.onEvents(evts[:i])
- evts = make([]KvEvent, eventBlockSize)
- i = 0
}
}
+ c.rwMux.RUnlock()
}
-func (c *KvCacher) sync(evts []KvEvent) {
- if len(evts) == 0 {
- return
+func (c *KvCache) prefix(key string) string {
+ if len(key) == 0 {
+ return ""
}
-
- if c.needDeferHandle(evts) {
- return
- }
-
- c.onEvents(evts)
+ return key[:strings.LastIndex(key[:len(key)-1], "/")+1]
}
-func (c *KvCacher) filter(rev int64, items []*mvccpb.KeyValue) []KvEvent {
- store := c.cache.RLock()
- defer c.cache.RUnlock()
-
- oc, nc := len(store), len(items)
- tc := oc + nc
- if tc == 0 {
- return nil
- }
- max := oc
- if nc > oc {
- max = nc
- }
-
- newStore := make(map[string]*mvccpb.KeyValue, nc)
- for _, kv := range items {
- newStore[util.BytesToStringWithNoCopy(kv.Key)] = kv
- }
- filterStopCh := make(chan struct{})
- eventsCh := make(chan [eventBlockSize]KvEvent, 2)
-
- go c.filterDelete(store, newStore, rev, eventsCh, filterStopCh)
-
- go c.filterCreateOrUpdate(store, newStore, rev, eventsCh, filterStopCh)
-
- evts := make([]KvEvent, 0, max)
- for block := range eventsCh {
- for _, e := range block {
- if e.Object == nil {
- break
+func (c *KvCache) getPrefixKey(arr *[]*KeyValue, prefix string) (count int) {
+ keysRef, ok := c.store[prefix]
+ if !ok {
+ return 0
+ }
+
+ for key := range keysRef {
+ n := c.getPrefixKey(arr, key)
+ if n == 0 {
+ count += len(keysRef)
+ if arr != nil {
+ // TODO support sort option
+ for _, v := range keysRef {
+ *arr = append(*arr, v)
+ }
}
- evts = append(evts, e)
+ break
}
+ count += n
}
- return evts
+ return count
}
-func (c *KvCacher) filterDelete(store map[string]*mvccpb.KeyValue, newStore map[string]*mvccpb.KeyValue,
- rev int64, eventsCh chan [eventBlockSize]KvEvent, filterStopCh chan struct{}) {
- var block [eventBlockSize]KvEvent
- i := 0
- for k, v := range store {
- _, ok := newStore[k]
- if ok {
- continue
- }
-
- if i >= eventBlockSize {
- eventsCh <- block
- block = [eventBlockSize]KvEvent{}
- i = 0
- }
-
- block[i] = KvEvent{
- Revision: rev,
- Type: proto.EVT_DELETE,
- Prefix: c.Cfg.Prefix,
- Object: v,
- }
- i++
- }
-
- if i > 0 {
- eventsCh <- block
- }
-
- close(filterStopCh)
-}
-
-func (c *KvCacher) filterCreateOrUpdate(store map[string]*mvccpb.KeyValue, newStore map[string]*mvccpb.KeyValue,
- rev int64, eventsCh chan [eventBlockSize]KvEvent, filterStopCh chan struct{}) {
- var block [eventBlockSize]KvEvent
- i := 0
- for k, v := range newStore {
- ov, ok := store[k]
- if !ok {
- if i >= eventBlockSize {
- eventsCh <- block
- block = [eventBlockSize]KvEvent{}
- i = 0
- }
-
- block[i] = KvEvent{
- Revision: rev,
- Type: proto.EVT_CREATE,
- Prefix: c.Cfg.Prefix,
- Object: v,
- }
- i++
- continue
- }
-
- if ov.CreateRevision == v.CreateRevision && ov.ModRevision == v.ModRevision {
- continue
- }
-
- if i >= eventBlockSize {
- eventsCh <- block
- block = [eventBlockSize]KvEvent{}
- i = 0
- }
-
- block[i] = KvEvent{
- Revision: rev,
- Type: proto.EVT_UPDATE,
- Prefix: c.Cfg.Prefix,
- Object: v,
- }
- i++
+func (c *KvCache) addPrefixKey(key string, val *KeyValue) {
+ if len(c.Cfg.Prefix) >= len(key) {
+ return
}
-
- if i > 0 {
- eventsCh <- block
+ prefix := c.prefix(key)
+ if len(prefix) == 0 {
+ return
}
-
- <-filterStopCh
-
- close(eventsCh)
-}
-
-func (c *KvCacher) onEvents(evts []KvEvent) {
- init := !c.IsReady()
- store := c.cache.Lock()
- for i, evt := range evts {
- kv := evt.Object.(*mvccpb.KeyValue)
- key := util.BytesToStringWithNoCopy(kv.Key)
- prevKv, ok := store[key]
-
- switch evt.Type {
- case proto.EVT_CREATE, proto.EVT_UPDATE:
- switch {
- case init:
- evt.Type = proto.EVT_INIT
- case !ok && evt.Type != proto.EVT_CREATE:
- util.Logger().Warnf(nil, "unexpected %s event! it should be %s key %s",
- evt.Type, proto.EVT_CREATE, key)
- evt.Type = proto.EVT_CREATE
- case ok && evt.Type != proto.EVT_UPDATE:
- util.Logger().Warnf(nil, "unexpected %s event! it should be %s key %s",
- evt.Type, proto.EVT_UPDATE, key)
- evt.Type = proto.EVT_UPDATE
- }
-
- store[key] = kv
- evts[i] = evt
- case proto.EVT_DELETE:
- if !ok {
- util.Logger().Warnf(nil, "unexpected %s event! key %s does not cache", evt.Type, key)
- } else {
- delete(store, key)
- }
- evt.Object = prevKv // maybe nil
- evts[i] = evt
+ keys, ok := c.store[prefix]
+ if !ok {
+ // build parent index key and new child nodes
+ keys = make(map[string]*KeyValue)
+ c.store[prefix] = keys
+ } else if _, ok := keys[key]; ok {
+ if val != nil {
+ // override the value
+ keys[key] = val
}
+ return
}
- c.cache.Unlock()
-
- c.onKvEvents(evts)
+ keys[key], key = val, prefix
+ c.addPrefixKey(key, nil)
}
-func (c *KvCacher) onKvEvents(evts []KvEvent) {
- if c.Cfg.OnEvent == nil {
+func (c *KvCache) deletePrefixKey(key string) {
+ prefix := c.prefix(key)
+ m, ok := c.store[prefix]
+ if !ok {
return
}
- defer util.RecoverAndReport()
+ delete(m, key)
- for _, evt := range evts {
- if evt.Object == nil {
- continue
- }
- c.Cfg.OnEvent(evt)
+ // remove parent which has no child
+ if len(m) == 0 {
+ delete(c.store, prefix)
+ c.deletePrefixKey(prefix)
}
}
-func (c *KvCacher) Cache() Cache {
- return c.cache
-}
-
-func (c *KvCacher) Run() {
- c.once.Do(func() {
- c.goroutine.Do(c.refresh)
- c.goroutine.Do(c.deferHandle)
- })
-}
-
-func (c *KvCacher) Stop() {
- c.goroutine.Close(true)
-
- util.SafeCloseChan(c.ready)
-}
-
-func (c *KvCacher) Ready() <-chan struct{} {
- return c.ready
-}
-
-func (c *KvCacher) IsReady() bool {
- select {
- case <-c.ready:
- return true
- default:
- return false
- }
+func (c *KvCache) ReportMetrics() {
+ c.rwMux.RLock()
+ ReportCacheMetrics(c.Name(), "raw", c.store)
+ c.rwMux.RUnlock()
}
-func NewKvCache(c *KvCacher, size int) *KvCache {
+func NewKvCache(name string, cfg *Config) *KvCache {
return &KvCache{
- owner: c,
- size: size,
- store: make(map[string]*mvccpb.KeyValue, size),
+ Cfg: cfg,
+ name: name,
+ store: make(map[string]map[string]*KeyValue),
lastRefresh: time.Now(),
}
}
-
-func NewKvCacher(name string, opts ...ConfigOption) *KvCacher {
- cfg := DefaultConfig()
- for _, opt := range opts {
- opt(&cfg)
- }
-
- cacher := &KvCacher{
- name: name,
- Cfg: cfg,
- ready: make(chan struct{}),
- lw: ListWatcher{
- Client: Registry(),
- Prefix: cfg.Prefix,
- },
- goroutine: util.NewGo(context.Background()),
- }
- cacher.cache = NewKvCache(cacher, cfg.InitSize)
- return cacher
-}
diff --git a/server/core/backend/cache_null.go b/server/core/backend/cache_null.go
index d6681aa..cce930e 100644
--- a/server/core/backend/cache_null.go
+++ b/server/core/backend/cache_null.go
@@ -19,47 +19,27 @@ package backend
var (
NullCache = &nullCache{}
NullCacher = &nullCacher{}
- closedCh = make(chan struct{})
)
-func init() {
- close(closedCh)
-}
-
type nullCache struct {
}
-func (n *nullCache) Version() int64 {
- return 0
-}
-
-func (n *nullCache) Data(interface{}) interface{} {
- return nil
-}
-
-func (n *nullCache) Have(interface{}) bool {
- return false
-}
-
-func (n *nullCache) Size() int {
- return 0
-}
+func (n *nullCache) Name() string { return "null" }
+func (n *nullCache) Size() int { return 0 }
+func (n *nullCache) RLock() {}
+func (n *nullCache) RUnlock() {}
+func (n *nullCache) Get(k string) *KeyValue { return nil }
+func (n *nullCache) GetAll(prefix string, arr *[]*KeyValue) int { return 0 }
+func (n *nullCache) ForEach(iter func(k string, v *KeyValue) (next bool)) {}
+func (n *nullCache) Lock() {}
+func (n *nullCache) Unlock() {}
+func (n *nullCache) Put(k string, v *KeyValue) {}
+func (n *nullCache) Remove(k string) {}
type nullCacher struct {
}
-func (n *nullCacher) Name() string {
- return ""
-}
-
-func (n *nullCacher) Cache() Cache {
- return NullCache
-}
-
-func (n *nullCacher) Run() {}
-
-func (n *nullCacher) Stop() {}
-
-func (n *nullCacher) Ready() <-chan struct{} {
- return closedCh
-}
+func (n *nullCacher) Cache() Cache { return NullCache }
+func (n *nullCacher) Run() {}
+func (n *nullCacher) Stop() {}
+func (n *nullCacher) Ready() <-chan struct{} { return closedCh }
diff --git a/server/core/backend/cacher_test.go b/server/core/backend/cache_test.go
similarity index 56%
rename from server/core/backend/cacher_test.go
rename to server/core/backend/cache_test.go
index 9e06f6d..e15ba9c 100644
--- a/server/core/backend/cacher_test.go
+++ b/server/core/backend/cache_test.go
@@ -35,10 +35,10 @@ func BenchmarkFilter(b *testing.B) {
}
v, _ := json.Marshal(inst)
- cacher := &KvCacher{}
+ cfg := DefaultConfig().WithParser(InstanceParser)
n := 300 * 1000 // 30w
- cache := NewKvCache(cacher, n)
+ cache := NewKvCache("test", cfg)
items := make([]*mvccpb.KeyValue, 0, n)
for ; n > 0; n-- {
k := fmt.Sprintf("/%d", n)
@@ -51,11 +51,11 @@ func BenchmarkFilter(b *testing.B) {
})
} else if n > 100*1000 && n <= 20*1000 {
// update
- cache.store[k] = &mvccpb.KeyValue{
+ cache.Put(k, &KeyValue{
Key: util.StringToBytesWithNoCopy(k),
- Value: v,
+ Value: inst,
ModRevision: 1,
- }
+ })
items = append(items, &mvccpb.KeyValue{
Key: util.StringToBytesWithNoCopy(k),
Value: v,
@@ -63,13 +63,14 @@ func BenchmarkFilter(b *testing.B) {
})
} else {
// delete
- cache.store[k] = &mvccpb.KeyValue{
+ cache.Put(k, &KeyValue{
Key: util.StringToBytesWithNoCopy(k),
- Value: v,
+ Value: inst,
ModRevision: 1,
- }
+ })
}
}
+ cacher := &KvCacher{Cfg: cfg}
cacher.cache = cache
b.ResetTimer()
@@ -79,6 +80,61 @@ func BenchmarkFilter(b *testing.B) {
b.ReportAllocs()
// TODO bad performance!!!
- //10 120612060 ns/op 37128035 B/op 134 allocs/op
- //
+ //20 82367261 ns/op 37964987 B/op 80132 allocs/op
+}
+
+func TestKvCache_Get(t *testing.T) {
+ c := NewKvCache("test", DefaultConfig())
+ c.Put("", &KeyValue{Version: 1})
+ c.Put("/", &KeyValue{Version: 1})
+ c.Put("/a/b/c/d/e/1", &KeyValue{Version: 1})
+ c.Put("/a/b/c/d/e/2", &KeyValue{Version: 2})
+ c.Put("/a/b/d/d/f/3", &KeyValue{Version: 3})
+ c.Put("/a/b/e/d/g/4", &KeyValue{Version: 4})
+
+ if l := c.Size(); l != 4 {
+ t.Fatalf("TestKvCache Size() failed, %d", l)
+ }
+
+ if kv := c.Get("/a/b/c/d/e/2"); kv == nil || kv.Version != 2 {
+ t.Fatalf("TestKvCache Get() failed, %v", kv)
+ }
+
+ if l := c.GetAll("/", nil); l != 4 {
+ t.Fatalf("TestKvCache GetAll() failed, %d", l)
+ }
+
+ var arr []*KeyValue
+ if l := c.GetAll("/a/b/c/", &arr); l != 2 || (arr[0].Version != 1 && arr[1].Version != 1) {
+ t.Fatalf("TestKvCache GetAll() failed, %d, %v", l, arr)
+ }
+
+ l, b := -1, false
+ c.ForEach(func(k string, v *KeyValue) (next bool) {
+ next = false
+ l++
+ return
+ })
+ if l != 0 {
+ t.Fatalf("TestKvCache ForEach() failed, %d", l)
+ }
+ c.ForEach(func(k string, v *KeyValue) (next bool) {
+ next = true
+ l++
+ if v.Version == 4 {
+ b = true
+ }
+ return
+ })
+ if l != 4 || !b {
+ t.Fatalf("TestKvCache ForEach() failed, %d, %v", l, b)
+ }
+
+ c.Remove("")
+ c.Remove("/")
+ c.Remove("/a/b/c/d/e/2")
+ c.Remove("/a/b/d/d/f/3")
+ if l := c.Size(); l != 2 {
+ t.Fatalf("TestKvCache Size() failed, %d", l)
+ }
}
diff --git a/server/core/backend/cache_kv.go b/server/core/backend/cacher_kv.go
similarity index 66%
copy from server/core/backend/cache_kv.go
copy to server/core/backend/cacher_kv.go
index cedb9dc..6c6aa99 100644
--- a/server/core/backend/cache_kv.go
+++ b/server/core/backend/cacher_kv.go
@@ -17,97 +17,20 @@
package backend
import (
+ "errors"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "github.com/apache/incubator-servicecomb-service-center/server/core"
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
"github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"sync"
"time"
)
-type KvCache struct {
- owner *KvCacher
- size int
- store map[string]*mvccpb.KeyValue
- rwMux sync.RWMutex
- lastRefresh time.Time
- lastMaxSize int
-}
-
-func (c *KvCache) Version() int64 {
- return c.owner.lw.Revision()
-}
-
-func (c *KvCache) Data(k interface{}) interface{} {
- c.rwMux.RLock()
- kv, ok := c.store[k.(string)]
- c.rwMux.RUnlock()
- if !ok {
- return nil
- }
- return kv
-}
-
-func (c *KvCache) Have(k interface{}) (ok bool) {
- c.rwMux.RLock()
- _, ok = c.store[k.(string)]
- c.rwMux.RUnlock()
- return
-}
-
-func (c *KvCache) RLock() map[string]*mvccpb.KeyValue {
- c.rwMux.RLock()
- return c.store
-}
-
-func (c *KvCache) RUnlock() {
- c.rwMux.RUnlock()
-}
-
-func (c *KvCache) Lock() map[string]*mvccpb.KeyValue {
- c.rwMux.Lock()
- return c.store
-}
-
-func (c *KvCache) Unlock() {
- l := len(c.store)
- if l > c.lastMaxSize {
- c.lastMaxSize = l
- }
- if c.size >= l &&
- c.lastMaxSize > c.size*DEFAULT_COMPACT_TIMES &&
- time.Now().Sub(c.lastRefresh) >= DEFAULT_COMPACT_TIMEOUT {
- c.compact()
- c.lastMaxSize = l
- c.lastRefresh = time.Now()
- }
-
- c.rwMux.Unlock()
-}
-
-func (c *KvCache) compact() {
- // gc
- newCache := make(map[string]*mvccpb.KeyValue, c.size)
- for k, v := range c.store {
- newCache[k] = v
- }
- c.store = newCache
-
- util.Logger().Infof("cache %s is not in use over %s, compact capacity to size %d->%d",
- c.owner.Cfg.Prefix, DEFAULT_COMPACT_TIMEOUT, c.lastMaxSize, c.size)
-}
-
-func (c *KvCache) Size() (l int) {
- c.rwMux.RLock()
- l = len(c.store)
- c.rwMux.RUnlock()
- return
-}
-
type KvCacher struct {
- Cfg Config
+ Cfg *Config
- name string
lastRev int64
noEventCount int
@@ -119,10 +42,6 @@ type KvCacher struct {
goroutine *util.GoRoutine
}
-func (c *KvCacher) Name() string {
- return c.name
-}
-
func (c *KvCacher) needList() bool {
rev := c.lw.Revision()
defer func() { c.lastRev = rev }()
@@ -147,11 +66,12 @@ func (c *KvCacher) needList() bool {
}
func (c *KvCacher) doList(cfg ListWatchConfig) error {
- kvs, err := c.lw.List(cfg)
+ resp, err := c.lw.List(cfg)
if err != nil {
return err
}
+ kvs := resp.Kvs
start := time.Now()
evts := c.filter(c.lw.Revision(), kvs)
if ec, kc := len(evts), len(kvs); c.Cfg.DeferHandler != nil && ec == 0 && kc != 0 &&
@@ -189,12 +109,40 @@ func (c *KvCacher) ListAndWatch(ctx context.Context) error {
func (c *KvCacher) handleWatcher(watcher *Watcher) error {
defer watcher.Stop()
- for evts := range watcher.EventBus() {
- if evts[0].Type == proto.EVT_ERROR {
- err := evts[0].Object.(error)
- return err
+ for resp := range watcher.EventBus() {
+ if resp == nil {
+ return errors.New("handle watcher error")
+ }
+
+ start := time.Now()
+ evts := make([]KvEvent, 0, len(resp.Kvs))
+ for _, kv := range resp.Kvs {
+ evt := KvEvent{Prefix: c.lw.Prefix, Revision: kv.ModRevision}
+ switch {
+ case resp.Action == registry.Put && kv.Version == 1:
+ evt.Type, evt.KV = proto.EVT_CREATE, c.doParse(kv)
+ case resp.Action == registry.Put:
+ evt.Type, evt.KV = proto.EVT_UPDATE, c.doParse(kv)
+ case resp.Action == registry.Delete:
+ evt.Type = proto.EVT_DELETE
+ if kv.Value == nil {
+ // it will happen in embed mode, and then need to get the cache value to unmarshal
+ evt.KV = c.cache.Get(util.BytesToStringWithNoCopy(kv.Key))
+ } else {
+ evt.KV = c.doParse(kv)
+ }
+ default:
+ util.Logger().Errorf(nil, "unknown KeyValue %v", kv)
+ continue
+ }
+ if evt.KV == nil {
+ continue
+ }
+ evts = append(evts, evt)
}
c.sync(evts)
+ util.LogDebugOrWarnf(start, "finish to handle %d events, prefix: %s, rev: %d",
+ len(evts), c.Cfg.Prefix, c.lw.Revision())
}
return nil
}
@@ -210,7 +158,9 @@ func (c *KvCacher) needDeferHandle(evts []KvEvent) bool {
func (c *KvCacher) refresh(ctx context.Context) {
util.Logger().Debugf("start to list and watch %s", c.Cfg)
retries := 0
- ticker := time.NewTicker(DEFAULT_METRICS_INTERVAL)
+
+ timer := time.NewTimer(minWaitInterval)
+ defer timer.Stop()
for {
nextPeriod := minWaitInterval
if err := c.ListAndWatch(ctx); err != nil {
@@ -220,66 +170,17 @@ func (c *KvCacher) refresh(ctx context.Context) {
retries = 0
}
- timer := time.NewTimer(nextPeriod)
select {
case <-ctx.Done():
- timer.Stop()
-
util.Logger().Debugf("stop to list and watch %s", c.Cfg)
return
- case <-ticker.C:
- timer.Stop()
-
- ReportCacheMetrics(c.Name(), "raw", c.cache.RLock())
- c.cache.RUnlock()
- case <-timer.C:
- }
- }
-}
-
-func (c *KvCacher) deferHandle(ctx context.Context) {
- if c.Cfg.DeferHandler == nil {
- return
- }
-
- var (
- evts = make([]KvEvent, eventBlockSize)
- i int
- )
- for {
- timer := time.NewTimer(300 * time.Millisecond)
- select {
- case <-ctx.Done():
- timer.Stop()
- return
- case evt, ok := <-c.Cfg.DeferHandler.HandleChan():
- timer.Stop()
-
- if !ok {
- <-time.After(time.Second)
- continue
- }
-
- if i >= eventBlockSize {
- c.onEvents(evts[:i])
- evts = make([]KvEvent, eventBlockSize)
- i = 0
- }
-
- evts[i] = evt
- i++
case <-timer.C:
- if i == 0 {
- continue
- }
-
- c.onEvents(evts[:i])
- evts = make([]KvEvent, eventBlockSize)
- i = 0
+ timer.Reset(nextPeriod)
}
}
}
+// keep the evts valid when call sync
func (c *KvCacher) sync(evts []KvEvent) {
if len(evts) == 0 {
return
@@ -293,19 +194,7 @@ func (c *KvCacher) sync(evts []KvEvent) {
}
func (c *KvCacher) filter(rev int64, items []*mvccpb.KeyValue) []KvEvent {
- store := c.cache.RLock()
- defer c.cache.RUnlock()
-
- oc, nc := len(store), len(items)
- tc := oc + nc
- if tc == 0 {
- return nil
- }
- max := oc
- if nc > oc {
- max = nc
- }
-
+ nc := len(items)
newStore := make(map[string]*mvccpb.KeyValue, nc)
for _, kv := range items {
newStore[util.BytesToStringWithNoCopy(kv.Key)] = kv
@@ -313,14 +202,14 @@ func (c *KvCacher) filter(rev int64, items []*mvccpb.KeyValue) []KvEvent {
filterStopCh := make(chan struct{})
eventsCh := make(chan [eventBlockSize]KvEvent, 2)
- go c.filterDelete(store, newStore, rev, eventsCh, filterStopCh)
+ go c.filterDelete(newStore, rev, eventsCh, filterStopCh)
- go c.filterCreateOrUpdate(store, newStore, rev, eventsCh, filterStopCh)
+ go c.filterCreateOrUpdate(newStore, rev, eventsCh, filterStopCh)
- evts := make([]KvEvent, 0, max)
+ evts := make([]KvEvent, 0, nc)
for block := range eventsCh {
for _, e := range block {
- if e.Object == nil {
+ if e.KV == nil {
break
}
evts = append(evts, e)
@@ -329,14 +218,17 @@ func (c *KvCacher) filter(rev int64, items []*mvccpb.KeyValue) []KvEvent {
return evts
}
-func (c *KvCacher) filterDelete(store map[string]*mvccpb.KeyValue, newStore map[string]*mvccpb.KeyValue,
+func (c *KvCacher) filterDelete(newStore map[string]*mvccpb.KeyValue,
rev int64, eventsCh chan [eventBlockSize]KvEvent, filterStopCh chan struct{}) {
var block [eventBlockSize]KvEvent
i := 0
- for k, v := range store {
+
+ c.cache.ForEach(func(k string, v *KeyValue) (next bool) {
+ next = true
+
_, ok := newStore[k]
if ok {
- continue
+ return
}
if i >= eventBlockSize {
@@ -349,10 +241,11 @@ func (c *KvCacher) filterDelete(store map[string]*mvccpb.KeyValue, newStore map[
Revision: rev,
Type: proto.EVT_DELETE,
Prefix: c.Cfg.Prefix,
- Object: v,
+ KV: v,
}
i++
- }
+ return
+ })
if i > 0 {
eventsCh <- block
@@ -361,26 +254,29 @@ func (c *KvCacher) filterDelete(store map[string]*mvccpb.KeyValue, newStore map[
close(filterStopCh)
}
-func (c *KvCacher) filterCreateOrUpdate(store map[string]*mvccpb.KeyValue, newStore map[string]*mvccpb.KeyValue,
+func (c *KvCacher) filterCreateOrUpdate(newStore map[string]*mvccpb.KeyValue,
rev int64, eventsCh chan [eventBlockSize]KvEvent, filterStopCh chan struct{}) {
var block [eventBlockSize]KvEvent
i := 0
+
for k, v := range newStore {
- ov, ok := store[k]
- if !ok {
+ ov := c.cache.Get(k)
+ if ov == nil {
if i >= eventBlockSize {
eventsCh <- block
block = [eventBlockSize]KvEvent{}
i = 0
}
- block[i] = KvEvent{
- Revision: rev,
- Type: proto.EVT_CREATE,
- Prefix: c.Cfg.Prefix,
- Object: v,
+ if kv := c.doParse(v); kv != nil {
+ block[i] = KvEvent{
+ Revision: rev,
+ Type: proto.EVT_CREATE,
+ Prefix: c.Cfg.Prefix,
+ KV: kv,
+ }
+ i++
}
- i++
continue
}
@@ -394,13 +290,15 @@ func (c *KvCacher) filterCreateOrUpdate(store map[string]*mvccpb.KeyValue, newSt
i = 0
}
- block[i] = KvEvent{
- Revision: rev,
- Type: proto.EVT_UPDATE,
- Prefix: c.Cfg.Prefix,
- Object: v,
+ if kv := c.doParse(v); kv != nil {
+ block[i] = KvEvent{
+ Revision: rev,
+ Type: proto.EVT_UPDATE,
+ Prefix: c.Cfg.Prefix,
+ KV: kv,
+ }
+ i++
}
- i++
}
if i > 0 {
@@ -412,13 +310,57 @@ func (c *KvCacher) filterCreateOrUpdate(store map[string]*mvccpb.KeyValue, newSt
close(eventsCh)
}
+func (c *KvCacher) deferHandle(ctx context.Context) {
+ if c.Cfg.DeferHandler == nil {
+ return
+ }
+
+ var (
+ evts = make([]KvEvent, eventBlockSize)
+ i int
+ )
+ interval := 300 * time.Millisecond
+ timer := time.NewTimer(interval)
+ defer timer.Stop()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case evt, ok := <-c.Cfg.DeferHandler.HandleChan():
+ if !ok {
+ return
+ }
+
+ if i >= eventBlockSize {
+ c.onEvents(evts[:i])
+ evts = make([]KvEvent, eventBlockSize)
+ i = 0
+ }
+
+ evts[i] = evt
+ i++
+
+ util.ResetTimer(timer, interval)
+ case <-timer.C:
+ timer.Reset(interval)
+
+ if i == 0 {
+ continue
+ }
+
+ c.onEvents(evts[:i])
+ evts = make([]KvEvent, eventBlockSize)
+ i = 0
+ }
+ }
+}
+
func (c *KvCacher) onEvents(evts []KvEvent) {
init := !c.IsReady()
- store := c.cache.Lock()
for i, evt := range evts {
- kv := evt.Object.(*mvccpb.KeyValue)
- key := util.BytesToStringWithNoCopy(kv.Key)
- prevKv, ok := store[key]
+ key := util.BytesToStringWithNoCopy(evt.KV.Key)
+ prevKv := c.cache.Get(key)
+ ok := prevKv != nil
switch evt.Type {
case proto.EVT_CREATE, proto.EVT_UPDATE:
@@ -435,21 +377,20 @@ func (c *KvCacher) onEvents(evts []KvEvent) {
evt.Type = proto.EVT_UPDATE
}
- store[key] = kv
+ c.cache.Put(key, evt.KV)
evts[i] = evt
case proto.EVT_DELETE:
if !ok {
- util.Logger().Warnf(nil, "unexpected %s event! key %s does not cache", evt.Type, key)
+ util.Logger().Warnf(nil, "unexpected %s event! key %s does not cache",
+ evt.Type, key)
} else {
- delete(store, key)
+ evt.KV = prevKv
+ c.cache.Remove(key)
}
- evt.Object = prevKv // maybe nil
evts[i] = evt
}
}
- c.cache.Unlock()
-
c.onKvEvents(evts)
}
@@ -457,16 +398,22 @@ func (c *KvCacher) onKvEvents(evts []KvEvent) {
if c.Cfg.OnEvent == nil {
return
}
+
defer util.RecoverAndReport()
for _, evt := range evts {
- if evt.Object == nil {
- continue
- }
c.Cfg.OnEvent(evt)
}
}
+func (c *KvCacher) doParse(src *mvccpb.KeyValue) (kv *KeyValue) {
+ kv = new(KeyValue)
+ if err := kv.From(c.Cfg.Parser, src); err != nil {
+ util.Logger().Errorf(err, "parse %s value failed", util.BytesToStringWithNoCopy(src.Key))
+ }
+ return kv
+}
+
func (c *KvCacher) Cache() Cache {
return c.cache
}
@@ -475,6 +422,7 @@ func (c *KvCacher) Run() {
c.once.Do(func() {
c.goroutine.Do(c.refresh)
c.goroutine.Do(c.deferHandle)
+ c.goroutine.Do(c.reportMetrics)
})
}
@@ -497,23 +445,27 @@ func (c *KvCacher) IsReady() bool {
}
}
-func NewKvCache(c *KvCacher, size int) *KvCache {
- return &KvCache{
- owner: c,
- size: size,
- store: make(map[string]*mvccpb.KeyValue, size),
- lastRefresh: time.Now(),
+func (c *KvCacher) reportMetrics(ctx context.Context) {
+ if !core.ServerInfo.Config.EnablePProf ||
+ !core.ServerInfo.Config.EnableCache {
+ return
}
-}
+ timer := time.NewTimer(DEFAULT_METRICS_INTERVAL)
+ defer timer.Stop()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-timer.C:
+ c.cache.ReportMetrics()
-func NewKvCacher(name string, opts ...ConfigOption) *KvCacher {
- cfg := DefaultConfig()
- for _, opt := range opts {
- opt(&cfg)
+ timer.Reset(DEFAULT_METRICS_INTERVAL)
+ }
}
+}
+func NewKvCacher(name string, cfg *Config) *KvCacher {
cacher := &KvCacher{
- name: name,
Cfg: cfg,
ready: make(chan struct{}),
lw: ListWatcher{
@@ -522,6 +474,6 @@ func NewKvCacher(name string, opts ...ConfigOption) *KvCacher {
},
goroutine: util.NewGo(context.Background()),
}
- cacher.cache = NewKvCache(cacher, cfg.InitSize)
+ cacher.cache = NewKvCache(name, cfg)
return cacher
}
diff --git a/server/core/backend/common.go b/server/core/backend/common.go
index 3895f57..1ae79d3 100644
--- a/server/core/backend/common.go
+++ b/server/core/backend/common.go
@@ -22,6 +22,36 @@ import (
"time"
)
+const (
+ // re-list etcd when there is no event coming in more than 1h(=120*30s)
+ DEFAULT_MAX_NO_EVENT_INTERVAL = 120
+ DEFAULT_LISTWATCH_TIMEOUT = 30 * time.Second
+
+ DEFAULT_SELF_PRESERVATION_PERCENT = 0.8
+ DEFAULT_CACHE_INIT_SIZE = 100
+)
+
+const (
+ DEFAULT_METRICS_INTERVAL = 30 * time.Second
+ DEFAULT_COMPACT_TIMES = 2
+ DEFAULT_COMPACT_TIMEOUT = 5 * time.Minute
+
+ minWaitInterval = 1 * time.Second
+ eventBlockSize = 1000
+)
+
+const DEFAULT_CHECK_WINDOW = 2 * time.Second // instance DELETE event will be delay.
+
+const TIME_FORMAT = "15:04:05.000"
+
+const EVENT_BUS_MAX_SIZE = 1000
+
+var closedCh = make(chan struct{})
+
+func init() {
+ close(closedCh)
+}
+
type StoreType int
func (st StoreType) String() string {
@@ -74,67 +104,50 @@ var TypeNames = []string{
typeEnd: "TYPEEND",
}
-var TypeRoots = map[StoreType]string{
- SERVICE: apt.GetServiceRootKey(""),
- INSTANCE: apt.GetInstanceRootKey(""),
- DOMAIN: apt.GetDomainRootKey() + "/",
- SCHEMA: apt.GetServiceSchemaRootKey(""),
- SCHEMA_SUMMARY: apt.GetServiceSchemaSummaryRootKey(""),
- RULE: apt.GetServiceRuleRootKey(""),
- LEASE: apt.GetInstanceLeaseRootKey(""),
- SERVICE_INDEX: apt.GetServiceIndexRootKey(""),
- SERVICE_ALIAS: apt.GetServiceAliasRootKey(""),
- SERVICE_TAG: apt.GetServiceTagRootKey(""),
- RULE_INDEX: apt.GetServiceRuleIndexRootKey(""),
- DEPENDENCY: apt.GetServiceDependencyRootKey(""),
- DEPENDENCY_RULE: apt.GetServiceDependencyRuleRootKey(""),
- DEPENDENCY_QUEUE: apt.GetServiceDependencyQueueRootKey(""),
- PROJECT: apt.GetProjectRootKey(""),
-}
+var TypeConfig = map[StoreType]*Config{
+ SERVICE: DefaultConfig().WithPrefix(apt.GetServiceRootKey("")).
+ WithInitSize(500).WithParser(ServiceParser),
-var TypeInitSize = map[StoreType]int{
- SERVICE: 500,
- INSTANCE: 1000,
- DOMAIN: 100,
- SCHEMA: 0,
- SCHEMA_SUMMARY: 100,
- RULE: 100,
- LEASE: 1000,
- SERVICE_INDEX: 500,
- SERVICE_ALIAS: 100,
- SERVICE_TAG: 100,
- RULE_INDEX: 100,
- DEPENDENCY: 100,
- DEPENDENCY_RULE: 100,
- DEPENDENCY_QUEUE: 100,
- PROJECT: 100,
-}
+ INSTANCE: DefaultConfig().WithPrefix(apt.GetInstanceRootKey("")).
+ WithInitSize(1000).WithParser(InstanceParser).
+ WithDeferHandler(NewInstanceEventDeferHandler()),
-const (
- // re-list etcd when there is no event coming in more than 2h(=240*30s)
- DEFAULT_MAX_NO_EVENT_INTERVAL = 240
- DEFAULT_LISTWATCH_TIMEOUT = 30 * time.Second
+ DOMAIN: DefaultConfig().WithPrefix(apt.GetDomainRootKey() + "/").
+ WithInitSize(100).WithParser(StringParser),
- DEFAULT_SELF_PRESERVATION_PERCENT = 0.8
- DEFAULT_CACHE_INIT_SIZE = 100
-)
+ SCHEMA: DefaultConfig().WithPrefix(apt.GetServiceSchemaRootKey("")).
+ WithInitSize(0),
-const (
- DEFAULT_METRICS_INTERVAL = 30 * time.Second
+ SCHEMA_SUMMARY: DefaultConfig().WithPrefix(apt.GetServiceSchemaSummaryRootKey("")).
+ WithInitSize(100).WithParser(StringParser),
- DEFAULT_COMPACT_TIMES = 2
- DEFAULT_COMPACT_TIMEOUT = 5 * time.Minute
- minWaitInterval = 1 * time.Second
- eventBlockSize = 1000
-)
+ RULE: DefaultConfig().WithPrefix(apt.GetServiceRuleRootKey("")).
+ WithInitSize(100).WithParser(RuleParser),
-const (
- DEFAULT_MAX_EVENT_COUNT = 1000
- DEFAULT_ADD_QUEUE_TIMEOUT = 5 * time.Second
-)
+ LEASE: DefaultConfig().WithPrefix(apt.GetInstanceLeaseRootKey("")).
+ WithInitSize(1000).WithParser(StringParser),
-const DEFAULT_CHECK_WINDOW = 2 * time.Second // instance DELETE event will be delay.
+ SERVICE_INDEX: DefaultConfig().WithPrefix(apt.GetServiceIndexRootKey("")).
+ WithInitSize(500).WithParser(StringParser),
-const TIME_FORMAT = "15:04:05.000"
+ SERVICE_ALIAS: DefaultConfig().WithPrefix(apt.GetServiceAliasRootKey("")).
+ WithInitSize(100).WithParser(StringParser),
-const EVENT_BUS_MAX_SIZE = 1000
+ SERVICE_TAG: DefaultConfig().WithPrefix(apt.GetServiceTagRootKey("")).
+ WithInitSize(100).WithParser(MapParser),
+
+ RULE_INDEX: DefaultConfig().WithPrefix(apt.GetServiceRuleIndexRootKey("")).
+ WithInitSize(100).WithParser(StringParser),
+
+ DEPENDENCY: DefaultConfig().WithPrefix(apt.GetServiceDependencyRootKey("")).
+ WithInitSize(100),
+
+ DEPENDENCY_RULE: DefaultConfig().WithPrefix(apt.GetServiceDependencyRuleRootKey("")).
+ WithInitSize(100).WithParser(DependencyRuleParser),
+
+ DEPENDENCY_QUEUE: DefaultConfig().WithPrefix(apt.GetServiceDependencyQueueRootKey("")).
+ WithInitSize(100).WithParser(DependencyQueueParser),
+
+ PROJECT: DefaultConfig().WithPrefix(apt.GetProjectRootKey("")).
+ WithInitSize(100).WithParser(StringParser),
+}
diff --git a/server/core/backend/config.go b/server/core/backend/config.go
index 13e162d..32c2e41 100644
--- a/server/core/backend/config.go
+++ b/server/core/backend/config.go
@@ -28,48 +28,71 @@ type Config struct {
NoEventMaxInterval int
Timeout time.Duration
Period time.Duration
- OnEvent KvEventFunc
DeferHandler DeferHandler
+ OnEvent KvEventFunc
+ Parser *Parser
}
-func (cfg Config) String() string {
+func (cfg *Config) String() string {
return fmt.Sprintf("{prefix: %s, timeout: %s, period: %s}",
cfg.Prefix, cfg.Timeout, cfg.Period)
}
-type ConfigOption func(*Config)
+func (cfg *Config) WithPrefix(key string) *Config {
+ cfg.Prefix = key
+ return cfg
+}
+
+func (cfg *Config) WithInitSize(size int) *Config {
+ cfg.InitSize = size
+ return cfg
+}
-func WithPrefix(key string) ConfigOption {
- return func(cfg *Config) { cfg.Prefix = key }
+func (cfg *Config) WithTimeout(ot time.Duration) *Config {
+ cfg.Timeout = ot
+ return cfg
}
-func WithInitSize(size int) ConfigOption {
- return func(cfg *Config) { cfg.InitSize = size }
+func (cfg *Config) WithPeriod(ot time.Duration) *Config {
+ cfg.Period = ot
+ return cfg
}
-func WithTimeout(ot time.Duration) ConfigOption {
- return func(cfg *Config) { cfg.Timeout = ot }
+func (cfg *Config) WithDeferHandler(h DeferHandler) *Config {
+ cfg.DeferHandler = h
+ return cfg
}
-func WithPeriod(ot time.Duration) ConfigOption {
- return func(cfg *Config) { cfg.Period = ot }
+func (cfg *Config) WithEventFunc(f KvEventFunc) *Config {
+ cfg.OnEvent = f
+ return cfg
}
-func WithEventFunc(f KvEventFunc) ConfigOption {
- return func(cfg *Config) { cfg.OnEvent = f }
+func (cfg *Config) AppendEventFunc(f KvEventFunc) *Config {
+ if prev := cfg.OnEvent; prev != nil {
+ next := f
+ f = func(evt KvEvent) {
+ prev(evt)
+ next(evt)
+ }
+ }
+ cfg.OnEvent = f
+ return cfg
}
-func WithDeferHandler(h DeferHandler) ConfigOption {
- return func(cfg *Config) { cfg.DeferHandler = h }
+func (cfg *Config) WithParser(parser *Parser) *Config {
+ cfg.Parser = parser
+ return cfg
}
-func DefaultConfig() Config {
- return Config{
+func DefaultConfig() *Config {
+ return &Config{
Prefix: "/",
Timeout: DEFAULT_LISTWATCH_TIMEOUT,
Period: time.Second,
NoEventMaxInterval: DEFAULT_MAX_NO_EVENT_INTERVAL,
InitSize: DEFAULT_CACHE_INIT_SIZE,
+ Parser: BytesParser,
}
}
diff --git a/server/core/backend/defer_instance.go b/server/core/backend/defer_instance.go
index aab844b..fea9485 100644
--- a/server/core/backend/defer_instance.go
+++ b/server/core/backend/defer_instance.go
@@ -17,10 +17,8 @@
package backend
import (
- "encoding/json"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"sync"
"time"
@@ -50,7 +48,7 @@ func (iedh *InstanceEventDeferHandler) OnCondition(cache Cache, evts []KvEvent)
iedh.once.Do(func() {
iedh.cache = cache
- iedh.items = make(map[string]deferItem, eventBlockSize)
+ iedh.items = make(map[string]deferItem)
iedh.pendingCh = make(chan []KvEvent, eventBlockSize)
iedh.deferCh = make(chan KvEvent, eventBlockSize)
iedh.resetCh = make(chan struct{})
@@ -62,7 +60,7 @@ func (iedh *InstanceEventDeferHandler) OnCondition(cache Cache, evts []KvEvent)
}
func (iedh *InstanceEventDeferHandler) recoverOrDefer(evt KvEvent) error {
- kv := evt.Object.(*mvccpb.KeyValue)
+ kv := evt.KV
key := util.BytesToStringWithNoCopy(kv.Key)
_, ok := iedh.items[key]
switch evt.Type {
@@ -77,20 +75,7 @@ func (iedh *InstanceEventDeferHandler) recoverOrDefer(evt KvEvent) error {
return nil
}
- var instance pb.MicroServiceInstance
-
- // it will happen in embed mode, and then need to get the cache value to unmarshal
- if kv.Value == nil {
- if c, ok := iedh.cache.Data(key).(*mvccpb.KeyValue); ok {
- kv.Value = c.Value
- }
- }
- err := json.Unmarshal(kv.Value, &instance)
- if err != nil {
- util.Logger().Errorf(err, "unmarshal instance file failed, key is %s, value is %s", key,
- util.BytesToStringWithNoCopy(kv.Value))
- return err
- }
+ instance := kv.Value.(*pb.MicroServiceInstance)
iedh.items[key] = deferItem{
ttl: time.NewTimer(
time.Duration(instance.HealthCheck.Interval*(instance.HealthCheck.Times+1)) * time.Second),
@@ -106,7 +91,9 @@ func (iedh *InstanceEventDeferHandler) HandleChan() <-chan KvEvent {
func (iedh *InstanceEventDeferHandler) check(ctx context.Context) {
defer util.RecoverAndReport()
+
t, n := time.NewTimer(DEFAULT_CHECK_WINDOW), false
+ defer t.Stop()
for {
select {
case <-ctx.Done():
@@ -118,10 +105,7 @@ func (iedh *InstanceEventDeferHandler) check(ctx context.Context) {
del := len(iedh.items)
if del > 0 && !n {
- if !t.Stop() {
- <-t.C
- }
- t.Reset(DEFAULT_CHECK_WINDOW)
+ util.ResetTimer(t, DEFAULT_CHECK_WINDOW)
n = true
}
@@ -132,7 +116,6 @@ func (iedh *InstanceEventDeferHandler) check(ctx context.Context) {
del, total, iedh.Percent*100)
}
case <-t.C:
- t.Reset(DEFAULT_CHECK_WINDOW)
n = false
for key, item := range iedh.items {
@@ -151,22 +134,29 @@ func (iedh *InstanceEventDeferHandler) check(ctx context.Context) {
iedh.renew()
util.Logger().Warnf(nil, "self preservation is stopped")
}
+
+ t.Reset(DEFAULT_CHECK_WINDOW)
case <-iedh.resetCh:
iedh.renew()
util.Logger().Warnf(nil, "self preservation is reset")
+
+ util.ResetTimer(t, DEFAULT_CHECK_WINDOW)
}
}
}
func (iedh *InstanceEventDeferHandler) recover(evt KvEvent) {
- key := util.BytesToStringWithNoCopy(evt.Object.(*mvccpb.KeyValue).Key)
+ key := util.BytesToStringWithNoCopy(evt.KV.Key)
delete(iedh.items, key)
iedh.deferCh <- evt
}
func (iedh *InstanceEventDeferHandler) renew() {
iedh.enabled = false
- iedh.items = make(map[string]deferItem, eventBlockSize)
+ for _, item := range iedh.items {
+ item.ttl.Stop()
+ }
+ iedh.items = make(map[string]deferItem)
}
func (iedh *InstanceEventDeferHandler) Reset() bool {
@@ -176,3 +166,7 @@ func (iedh *InstanceEventDeferHandler) Reset() bool {
}
return false
}
+
+func NewInstanceEventDeferHandler() *InstanceEventDeferHandler {
+ return &InstanceEventDeferHandler{Percent: DEFAULT_SELF_PRESERVATION_PERCENT}
+}
diff --git a/server/core/backend/defer_test.go b/server/core/backend/defer_test.go
index 93acd97..42a43e8 100644
--- a/server/core/backend/defer_test.go
+++ b/server/core/backend/defer_test.go
@@ -17,11 +17,9 @@
package backend
import (
- "encoding/json"
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
- "github.com/coreos/etcd/mvcc/mvccpb"
"testing"
"time"
)
@@ -42,94 +40,93 @@ func TestInstanceEventDeferHandler_OnCondition(t *testing.T) {
}
func TestInstanceEventDeferHandler_HandleChan(t *testing.T) {
- inst := &pb.MicroServiceInstance{
+ b := &pb.MicroServiceInstance{
HealthCheck: &pb.HealthCheck{
Interval: 4,
Times: 0,
},
}
- b, _ := json.Marshal(inst)
- kv1 := &mvccpb.KeyValue{
+ kv1 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/1"),
Value: b,
}
- kv2 := &mvccpb.KeyValue{
+ kv2 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/2"),
Value: b,
}
- kv3 := &mvccpb.KeyValue{
+ kv3 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/3"),
Value: b,
}
- kv4 := &mvccpb.KeyValue{
+ kv4 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/4"),
Value: b,
}
- kv5 := &mvccpb.KeyValue{
+ kv5 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/5"),
Value: b,
}
- kv6 := &mvccpb.KeyValue{
+ kv6 := &KeyValue{
Key: util.StringToBytesWithNoCopy("/6"),
Value: b,
}
- cache := NewKvCache(nil, 1)
- cache.store["/1"] = kv1
- cache.store["/2"] = kv2
- cache.store["/3"] = kv3
- cache.store["/4"] = kv4
- cache.store["/5"] = kv5
- cache.store["/6"] = kv6
+ cache := NewKvCache("test", DefaultConfig())
+ cache.Put("/1", kv1)
+ cache.Put("/2", kv2)
+ cache.Put("/3", kv3)
+ cache.Put("/4", kv4)
+ cache.Put("/5", kv5)
+ cache.Put("/6", kv6)
evts1 := []KvEvent{
{
- Type: pb.EVT_CREATE,
- Object: kv1,
+ Type: pb.EVT_CREATE,
+ KV: kv1,
},
{
- Type: pb.EVT_UPDATE,
- Object: kv1,
+ Type: pb.EVT_UPDATE,
+ KV: kv1,
},
}
evts2 := []KvEvent{
{
- Type: pb.EVT_DELETE,
- Object: kv2,
+ Type: pb.EVT_DELETE,
+ KV: kv2,
},
{
- Type: pb.EVT_DELETE,
- Object: kv3,
+ Type: pb.EVT_DELETE,
+ KV: kv3,
},
{
- Type: pb.EVT_DELETE,
- Object: kv4,
+ Type: pb.EVT_DELETE,
+ KV: kv4,
},
{
- Type: pb.EVT_DELETE,
- Object: kv5,
+ Type: pb.EVT_DELETE,
+ KV: kv5,
},
{
- Type: pb.EVT_DELETE,
- Object: kv6,
+ Type: pb.EVT_DELETE,
+ KV: kv6,
},
}
evts3 := []KvEvent{
{
- Type: pb.EVT_CREATE,
- Object: kv2,
+ Type: pb.EVT_CREATE,
+ KV: kv2,
},
{
- Type: pb.EVT_UPDATE,
- Object: kv4,
+ Type: pb.EVT_UPDATE,
+ KV: kv4,
},
{
- Type: pb.EVT_UPDATE,
- Object: kv5,
+ Type: pb.EVT_UPDATE,
+ KV: kv5,
},
{
- Type: pb.EVT_CREATE,
- Object: kv6,
+ Type: pb.EVT_CREATE,
+ KV: kv6,
},
}
@@ -159,7 +156,7 @@ func getEvents(t *testing.T, iedh *InstanceEventDeferHandler) {
select {
case evt := <-iedh.HandleChan():
fmt.Println(time.Now(), evt)
- if string(evt.Object.(*mvccpb.KeyValue).Key) == "/3" {
+ if string(evt.KV.Key) == "/3" {
evt3 = &evt
if iedh.Percent == 0.01 && evt.Type == pb.EVT_DELETE {
t.Fatalf(`TestInstanceEventDeferHandler_HandleChan with 1%% failed`)
diff --git a/server/core/backend/event.go b/server/core/backend/event.go
index 839dc7a..720ef6f 100644
--- a/server/core/backend/event.go
+++ b/server/core/backend/event.go
@@ -20,13 +20,11 @@ import (
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
)
-type KvEventFunc func(evt KvEvent)
-
type KvEvent struct {
Revision int64
Type proto.EventType
Prefix string
- Object interface{}
+ KV *KeyValue
}
type KvEventHandler interface {
diff --git a/server/core/backend/event_proxy.go b/server/core/backend/event_proxy.go
index 592c70a..3b02287 100644
--- a/server/core/backend/event_proxy.go
+++ b/server/core/backend/event_proxy.go
@@ -49,9 +49,7 @@ func (h *KvEventProxy) OnEvent(evt KvEvent) {
}
func NewEventProxy() *KvEventProxy {
- return &KvEventProxy{
- evtHandleFuncs: make([]KvEventFunc, 0, 5),
- }
+ return &KvEventProxy{}
}
func EventProxy(t StoreType) *KvEventProxy {
diff --git a/server/core/backend/extend.go b/server/core/backend/extend.go
index 200d45e..dbc0cd1 100644
--- a/server/core/backend/extend.go
+++ b/server/core/backend/extend.go
@@ -23,26 +23,20 @@ import (
type Entity interface {
Name() string
- Prefix() string
- InitSize() int
+ Config() *Config
}
type entity struct {
- name string
- prefix string
- initSize int
+ name string
+ cfg *Config
}
func (e *entity) Name() string {
return e.name
}
-func (e *entity) Prefix() string {
- return e.prefix
-}
-
-func (e *entity) InitSize() int {
- return e.initSize
+func (e *entity) Config() *Config {
+ return e.cfg
}
func InstallType(e Entity) (id StoreType, err error) {
@@ -54,31 +48,22 @@ func InstallType(e Entity) (id StoreType, err error) {
return NONEXIST, fmt.Errorf("redeclare store type '%s'", n)
}
}
- for _, r := range TypeRoots {
- if r == e.Prefix() {
+ for _, r := range TypeConfig {
+ if r.Prefix == e.Config().Prefix {
return NONEXIST, fmt.Errorf("redeclare store root '%s'", r)
}
}
id = StoreType(len(TypeNames))
TypeNames = append(TypeNames, e.Name())
-
- TypeRoots[id] = e.Prefix()
- TypeInitSize[id] = e.InitSize()
-
+ TypeConfig[id] = e.Config()
EventProxies[id] = NewEventProxy()
return
}
-func NewEntity(name, prefix string, opts ...ConfigOption) Entity {
- cfg := DefaultConfig()
- for _, opt := range opts {
- opt(&cfg)
- }
- cfg.Prefix = prefix
+func NewEntity(name string, cfg *Config) Entity {
return &entity{
- name: name,
- prefix: cfg.Prefix,
- initSize: cfg.InitSize,
+ name: name,
+ cfg: cfg,
}
}
diff --git a/server/core/backend/extend_test.go b/server/core/backend/extend_test.go
index 6a37ce5..3f1e1af 100644
--- a/server/core/backend/extend_test.go
+++ b/server/core/backend/extend_test.go
@@ -25,12 +25,8 @@ func (e *extend) Name() string {
return "test"
}
-func (e *extend) Prefix() string {
- return "/test"
-}
-
-func (e *extend) InitSize() int {
- return 0
+func (e *extend) Config() *Config {
+ return DefaultConfig().WithPrefix("/test")
}
func TestInstallType(t *testing.T) {
diff --git a/server/core/backend/indexer.go b/server/core/backend/indexer.go
index a88fec7..4f47a72 100644
--- a/server/core/backend/indexer.go
+++ b/server/core/backend/indexer.go
@@ -17,344 +17,78 @@
package backend
import (
+ "fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "github.com/apache/incubator-servicecomb-service-center/server/core"
- pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
"github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"strings"
- "sync"
- "time"
)
-type Indexer struct {
- BuildTimeout time.Duration
- Root string
-
- cacher Cacher
- goroutine *util.GoRoutine
- ready chan struct{}
- prefixIndex map[string]map[string]struct{}
- prefixBuildQueue chan KvEvent
- prefixLock sync.RWMutex
- isClose bool
+type Response struct {
+ Kvs []*KeyValue
+ Count int64
}
-func (i *Indexer) Search(ctx context.Context, opts ...registry.PluginOpOption) (*registry.PluginResponse, error) {
- op := registry.OpGet(opts...)
-
- key := util.BytesToStringWithNoCopy(op.Key)
-
- if !core.ServerInfo.Config.EnableCache ||
- op.Mode == registry.MODE_NO_CACHE ||
- op.Revision > 0 ||
- (op.Offset >= 0 && op.Limit > 0) {
- util.Logger().Debugf("search %s match special options, request etcd server, opts: %s",
- i.cacher.Name(), op)
- return Registry().Do(ctx, opts...)
- }
-
- if op.Prefix {
- resp, err := i.searchPrefixKeyWithCache(ctx, op)
- if err != nil {
- return nil, err
- }
-
- if len(resp.Kvs) > 0 || op.Mode == registry.MODE_CACHE {
- return resp, nil
+func (pr *Response) MaxModRevision() (max int64) {
+ for _, kv := range pr.Kvs {
+ if max < kv.ModRevision {
+ max = kv.ModRevision
}
-
- util.Logger().Debugf("can not find any key from %s cache with prefix, request etcd server, key: %s",
- i.cacher.Name(), key)
- return Registry().Do(ctx, opts...)
- }
-
- resp := ®istry.PluginResponse{
- Action: op.Action,
- Count: 0,
- Revision: i.Cache().Version(),
- Succeeded: true,
- }
-
- if op.CountOnly {
- if i.Cache().Have(key) {
- resp.Count = 1
- return resp, nil
- }
- if op.Mode == registry.MODE_CACHE {
- return resp, nil
- }
-
- util.Logger().Debugf("%s cache does not store this key, request etcd server, key: %s",
- i.cacher.Name(), key)
- return Registry().Do(ctx, opts...)
- }
-
- cacheData := i.Cache().Data(key)
- if cacheData == nil {
- if op.Mode == registry.MODE_CACHE {
- return resp, nil
- }
-
- util.Logger().Debugf("do not match any key in %s cache store, request etcd server, key: %s",
- i.cacher.Name(), key)
- return Registry().Do(ctx, opts...)
}
-
- resp.Count = 1
- resp.Kvs = []*mvccpb.KeyValue{cacheData.(*mvccpb.KeyValue)}
- return resp, nil
+ return
}
-func (i *Indexer) Cache() Cache {
- return i.cacher.Cache()
+type Indexer interface {
+ Search(ctx context.Context, opts ...registry.PluginOpOption) (*Response, error)
+ Run()
+ Stop()
+ Ready() <-chan struct{}
}
-func (i *Indexer) searchPrefixKeyWithCache(ctx context.Context, op registry.PluginOp) (*registry.PluginResponse, error) {
- resp := ®istry.PluginResponse{
- Action: op.Action,
- Kvs: []*mvccpb.KeyValue{},
- Count: 0,
- Revision: i.Cache().Version(),
- Succeeded: true,
- }
-
- prefix := util.BytesToStringWithNoCopy(op.Key)
-
- i.prefixLock.RLock()
- resp.Count = int64(i.getPrefixKey(nil, prefix))
- if resp.Count == 0 || op.CountOnly {
- i.prefixLock.RUnlock()
- return resp, nil
- }
-
- t := time.Now()
- keys := make([]string, 0, resp.Count)
- i.getPrefixKey(&keys, prefix)
- i.prefixLock.RUnlock()
-
- kvs := make([]*mvccpb.KeyValue, resp.Count)
- idx := 0
- for _, key := range keys {
- c := i.Cache().Data(key) // TODO too slow when big data is requested
- if c == nil {
- // it means resp.Count is not equal to len(keys)
- util.Logger().Warnf(nil, "unexpected nil cache, maybe it is removed, key is %s", key)
- continue
- }
- kvs[idx] = c.(*mvccpb.KeyValue)
- idx++
- }
- util.LogNilOrWarnf(t, "too long to copy data[%d] from cache[%d] with prefix %s", idx, len(i.prefixIndex), prefix)
-
- resp.Kvs = kvs[:idx]
- return resp, nil
+type baseIndexer struct {
+ Cfg *Config
}
-func (i *Indexer) OnCacheEvent(evt KvEvent) {
- switch evt.Type {
- case pb.EVT_INIT, pb.EVT_CREATE, pb.EVT_DELETE:
- default:
- return
- }
-
- if i.isClose {
- return
- }
- defer util.RecoverAndReport()
-
- ctx, _ := context.WithTimeout(context.Background(), i.BuildTimeout)
- select {
- case <-ctx.Done():
- key := util.BytesToStringWithNoCopy(evt.Object.(*mvccpb.KeyValue).Key)
- util.Logger().Warnf(nil, "add event to build index queue timed out(%s), key is %s [%s] event",
- i.BuildTimeout, key, evt.Type)
- case i.prefixBuildQueue <- evt:
- }
-}
-
-func (i *Indexer) buildIndex() {
- i.goroutine.Do(func(ctx context.Context) {
- util.SafeCloseChan(i.ready)
- lastCompactTime := time.Now()
- ticker := time.NewTicker(DEFAULT_METRICS_INTERVAL)
- max := 0
- for {
- select {
- case <-ctx.Done():
- return
- case <-ticker.C:
- i.prefixLock.RLock()
- // report metrics
- ReportCacheMetrics(i.cacher.Name(), "index", i.prefixIndex)
- i.prefixLock.RUnlock()
- case evt, ok := <-i.prefixBuildQueue:
- if !ok {
- return
- }
- t := time.Now()
- key := util.BytesToStringWithNoCopy(evt.Object.(*mvccpb.KeyValue).Key)
- prefix := key[:strings.LastIndex(key[:len(key)-1], "/")+1]
-
- i.prefixLock.Lock()
- switch evt.Type {
- case pb.EVT_DELETE:
- i.deletePrefixKey(prefix, key)
- default:
- i.addPrefixKey(prefix, key)
- }
-
- // compact
- initSize, l := DEFAULT_CACHE_INIT_SIZE, len(i.prefixIndex)
- if max < l {
- max = l
- }
- if initSize >= l &&
- max >= initSize*DEFAULT_COMPACT_TIMES &&
- time.Now().Sub(lastCompactTime) >= DEFAULT_COMPACT_TIMEOUT {
- i.compact()
- max = l
- lastCompactTime = time.Now()
- }
-
- i.prefixLock.Unlock()
-
- util.LogNilOrWarnf(t, "too long to rebuild(action: %s) index[%d], key is %s",
- evt.Type, len(i.prefixIndex), key)
- }
- }
- util.Logger().Debugf("the goroutine building index %s is stopped", i.cacher.Name())
- })
- <-i.ready
-}
-
-func (i *Indexer) compact() {
- n := make(map[string]map[string]struct{}, DEFAULT_CACHE_INIT_SIZE)
- for k, v := range i.prefixIndex {
- c, ok := n[k]
- if !ok {
- c = make(map[string]struct{}, len(v))
- n[k] = c
- }
- for ck, cv := range v {
- c[ck] = cv
- }
- }
- i.prefixIndex = n
-
- util.Logger().Infof("index %s: compact root capacity to size %d",
- i.cacher.Name(), DEFAULT_CACHE_INIT_SIZE)
-}
-
-func (i *Indexer) getPrefixKey(arr *[]string, prefix string) (count int) {
- keysRef, ok := i.prefixIndex[prefix]
- if !ok {
- return 0
- }
-
- for key := range keysRef {
- n := i.getPrefixKey(arr, key)
- if n == 0 {
- count += len(keysRef)
- if arr != nil {
- // TODO support sort option
- for k := range keysRef {
- *arr = append(*arr, k)
- }
- }
- break
- }
- count += n
+func (i *baseIndexer) Search(ctx context.Context, opts ...registry.PluginOpOption) (r *Response, err error) {
+ op := registry.OpGet(opts...)
+ key := util.BytesToStringWithNoCopy(op.Key)
+ if strings.Index(key, i.Cfg.Prefix) != 0 {
+ return nil, fmt.Errorf("search %s mismatch pattern %s", key, i.Cfg.Prefix)
}
- return count
-}
-func (i *Indexer) addPrefixKey(prefix, key string) {
- if i.Root == key {
- return
+ resp, err := Registry().Do(ctx, opts...)
+ if err != nil {
+ return nil, err
}
- keys, ok := i.prefixIndex[prefix]
- if !ok {
- // build parent index key and new child nodes
- keys = make(map[string]struct{})
- i.prefixIndex[prefix] = keys
- } else if _, ok := keys[key]; ok {
+ r = new(Response)
+ r.Count = resp.Count
+ if len(resp.Kvs) == 0 {
return
}
- keys[key], key = struct{}{}, prefix
- prefix = key[:strings.LastIndex(key[:len(key)-1], "/")+1]
-
- i.addPrefixKey(prefix, key)
-}
-
-func (i *Indexer) deletePrefixKey(prefix, key string) {
- m, ok := i.prefixIndex[prefix]
- if !ok {
- return
- }
- delete(m, key)
-
- // remove parent which has no child
- if len(m) == 0 {
- delete(i.prefixIndex, prefix)
- i.deletePrefixKey(prefix[:strings.LastIndex(prefix[:len(prefix)-1], "/")+1], prefix)
+ kvs := make([]*KeyValue, 0, len(resp.Kvs))
+ for _, src := range resp.Kvs {
+ kv := new(KeyValue)
+ if err = kv.From(i.Cfg.Parser, src); err != nil {
+ continue
+ }
+ kvs = append(kvs, kv)
}
+ r.Kvs = kvs
+ return
}
-func (i *Indexer) Run() {
- i.prefixLock.Lock()
- if !i.isClose {
- i.prefixLock.Unlock()
- return
- }
- i.isClose = false
- i.prefixLock.Unlock()
-
- if !core.ServerInfo.Config.EnableCache {
- util.SafeCloseChan(i.ready)
- return
- }
-
- i.buildIndex()
-
- i.cacher.Run()
+func (i *baseIndexer) Run() {
}
-func (i *Indexer) Stop() {
- i.prefixLock.Lock()
- if i.isClose {
- i.prefixLock.Unlock()
- return
- }
- i.isClose = true
- i.prefixLock.Unlock()
-
- i.cacher.Stop()
-
- i.goroutine.Close(true)
-
- close(i.prefixBuildQueue)
-
- util.SafeCloseChan(i.ready)
+func (i *baseIndexer) Stop() {
}
-func (i *Indexer) Ready() <-chan struct{} {
- <-i.cacher.Ready()
- return i.ready
+func (i *baseIndexer) Ready() <-chan struct{} {
+ return closedCh
}
-func NewCacheIndexer(root string, cr Cacher) *Indexer {
- return &Indexer{
- BuildTimeout: DEFAULT_ADD_QUEUE_TIMEOUT,
- Root: root,
- cacher: cr,
- prefixIndex: make(map[string]map[string]struct{}, DEFAULT_CACHE_INIT_SIZE),
- prefixBuildQueue: make(chan KvEvent, DEFAULT_MAX_EVENT_COUNT),
- goroutine: util.NewGo(context.Background()),
- ready: make(chan struct{}),
- isClose: true,
- }
+func NewBaseIndexer(cfg *Config) (indexer *baseIndexer) {
+ return &baseIndexer{Cfg: cfg}
}
diff --git a/server/core/backend/indexer_kv.go b/server/core/backend/indexer_kv.go
new file mode 100644
index 0000000..7cf2443
--- /dev/null
+++ b/server/core/backend/indexer_kv.go
@@ -0,0 +1,150 @@
+/*
+ * 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 backend
+
+import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "github.com/apache/incubator-servicecomb-service-center/server/core"
+ "github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
+ "golang.org/x/net/context"
+ "strings"
+ "sync"
+ "time"
+)
+
+type CacheIndexer struct {
+ *baseIndexer
+
+ cacher Cacher
+ lock sync.Mutex
+ isClose bool
+}
+
+func (i *CacheIndexer) Search(ctx context.Context, opts ...registry.PluginOpOption) (*Response, error) {
+ op := registry.OpGet(opts...)
+ key := util.BytesToStringWithNoCopy(op.Key)
+ if strings.Index(key, i.baseIndexer.Cfg.Prefix) != 0 {
+ return nil, fmt.Errorf("search %s mismatch %s pattern %s",
+ key, i.cacher.Cache().Name(), i.baseIndexer.Cfg.Prefix)
+ }
+
+ if !core.ServerInfo.Config.EnableCache ||
+ op.Mode == registry.MODE_NO_CACHE ||
+ op.Revision > 0 ||
+ (op.Offset >= 0 && op.Limit > 0) {
+ util.Logger().Debugf("search %s match special options, request etcd server, opts: %s",
+ i.cacher.Cache().Name(), op)
+ return i.baseIndexer.Search(ctx, opts...)
+ }
+
+ var resp *Response
+ if op.Prefix {
+ resp = i.searchByPrefix(op)
+ } else {
+ resp = i.search(op)
+ }
+
+ if resp.Count > 0 || op.Mode == registry.MODE_CACHE {
+ return resp, nil
+ }
+
+ util.Logger().Debugf("can not find any key from %s cache with prefix, request etcd server, key: %s",
+ i.cacher.Cache().Name(), key)
+ return i.baseIndexer.Search(ctx, opts...)
+}
+
+func (i *CacheIndexer) search(op registry.PluginOp) *Response {
+ resp := new(Response)
+
+ key := util.BytesToStringWithNoCopy(op.Key)
+
+ kv := i.cacher.Cache().Get(key)
+ if kv != nil {
+ resp.Count = 1
+ }
+ if kv == nil || op.CountOnly {
+ return resp
+ }
+
+ resp.Kvs = []*KeyValue{kv}
+ return resp
+}
+
+func (i *CacheIndexer) searchByPrefix(op registry.PluginOp) *Response {
+ resp := new(Response)
+
+ prefix := util.BytesToStringWithNoCopy(op.Key)
+
+ resp.Count = int64(i.cacher.Cache().GetAll(prefix, nil))
+ if resp.Count == 0 || op.CountOnly {
+ return resp
+ }
+
+ t := time.Now()
+ kvs := make([]*KeyValue, 0, resp.Count)
+ i.cacher.Cache().GetAll(prefix, &kvs)
+
+ util.LogNilOrWarnf(t, "too long to index data[%d] from cache '%s'", len(kvs), i.cacher.Cache().Name())
+
+ resp.Kvs = kvs
+ return resp
+}
+
+func (i *CacheIndexer) Run() {
+ i.lock.Lock()
+ if !i.isClose {
+ i.lock.Unlock()
+ return
+ }
+ i.isClose = false
+ i.lock.Unlock()
+
+ i.cacher.Run()
+}
+
+func (i *CacheIndexer) Stop() {
+ i.lock.Lock()
+ if i.isClose {
+ i.lock.Unlock()
+ return
+ }
+ i.isClose = true
+ i.lock.Unlock()
+
+ i.cacher.Stop()
+}
+
+func (i *CacheIndexer) Ready() <-chan struct{} {
+ return i.cacher.Ready()
+}
+
+func NewCacheIndexer(name string, cfg *Config) (indexer *CacheIndexer) {
+ indexer = &CacheIndexer{
+ baseIndexer: NewBaseIndexer(cfg),
+ cacher: NullCacher,
+ isClose: true,
+ }
+
+ switch {
+ case core.ServerInfo.Config.EnableCache && cfg.InitSize > 0:
+ indexer.cacher = NewKvCacher(name, cfg)
+ default:
+ util.Logger().Infof("service center will not cache '%s'", name)
+ }
+ return
+}
diff --git a/server/core/backend/cacher.go b/server/core/backend/kv.go
similarity index 66%
rename from server/core/backend/cacher.go
rename to server/core/backend/kv.go
index 410ef50..73f0393 100644
--- a/server/core/backend/cacher.go
+++ b/server/core/backend/kv.go
@@ -16,18 +16,25 @@
*/
package backend
-type Cache interface {
- Version() int64
- Data(interface{}) interface{}
- Have(interface{}) bool
- Size() int
+import (
+ "github.com/coreos/etcd/mvcc/mvccpb"
+)
+
+type KeyValue struct {
+ Key []byte
+ Value interface{}
+ Version int64
+ CreateRevision int64
+ ModRevision int64
}
-type Cacher interface {
- // Name is the cache size metric name
- Name() string
- Cache() Cache
- Run()
- Stop()
- Ready() <-chan struct{}
+func (kv *KeyValue) From(p *Parser, s *mvccpb.KeyValue) error {
+ kv.Key = s.Key
+ kv.Version = s.Version
+ kv.CreateRevision = s.CreateRevision
+ kv.ModRevision = s.ModRevision
+
+ v, err := p.Unmarshal(s.Value)
+ kv.Value = v
+ return err
}
diff --git a/server/core/backend/lease.go b/server/core/backend/lease.go
index e37cafc..89fcb2e 100644
--- a/server/core/backend/lease.go
+++ b/server/core/backend/lease.go
@@ -56,11 +56,15 @@ func (lat *LeaseTask) Do(ctx context.Context) (err error) {
}
lat.err, err = nil, nil
- util.LogNilOrWarnf(recv, "renew lease %d(recv: %s, send: %s), key %s",
- lat.LeaseID,
- recv.Format(TIME_FORMAT),
- start.Format(TIME_FORMAT),
- lat.Key())
+
+ cost := time.Now().Sub(recv)
+ if cost >= 2*time.Second {
+ util.Logger().Warnf(nil, "[%s]renew lease %d(recv: %s, send: %s), key %s", cost,
+ lat.LeaseID,
+ recv.Format(TIME_FORMAT),
+ start.Format(TIME_FORMAT),
+ lat.Key())
+ }
return
}
diff --git a/server/core/backend/listwatch.go b/server/core/backend/listwatch.go
index 0aa46ab..469a8d1 100644
--- a/server/core/backend/listwatch.go
+++ b/server/core/backend/listwatch.go
@@ -19,9 +19,7 @@ package backend
import (
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
"github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"sync"
)
@@ -33,7 +31,7 @@ type ListWatcher struct {
rev int64
}
-func (lw *ListWatcher) List(op ListWatchConfig) ([]*mvccpb.KeyValue, error) {
+func (lw *ListWatcher) List(op ListWatchConfig) (*registry.PluginResponse, error) {
otCtx, _ := context.WithTimeout(op.Context, op.Timeout)
resp, err := lw.Client.Do(otCtx, registry.WatchPrefixOpOptions(lw.Prefix)...)
if err != nil {
@@ -41,10 +39,7 @@ func (lw *ListWatcher) List(op ListWatchConfig) ([]*mvccpb.KeyValue, error) {
return nil, err
}
lw.setRevision(resp.Revision)
- if len(resp.Kvs) == 0 {
- return nil, nil
- }
- return resp.Kvs, nil
+ return resp, nil
}
func (lw *ListWatcher) Revision() int64 {
@@ -59,7 +54,7 @@ func (lw *ListWatcher) Watch(op ListWatchConfig) *Watcher {
return newWatcher(lw, op)
}
-func (lw *ListWatcher) doWatch(ctx context.Context, f func(evt []KvEvent)) error {
+func (lw *ListWatcher) doWatch(ctx context.Context, f func(*registry.PluginResponse)) error {
rev := lw.Revision()
opts := append(
registry.WatchPrefixOpOptions(lw.Prefix),
@@ -67,29 +62,12 @@ func (lw *ListWatcher) doWatch(ctx context.Context, f func(evt []KvEvent)) error
registry.WithWatchCallback(
func(message string, resp *registry.PluginResponse) error {
if resp == nil || len(resp.Kvs) == 0 {
- return fmt.Errorf("unknown event %s", resp)
+ return fmt.Errorf("unknown event %s, watch prefix %s", resp, lw.Prefix)
}
- util.Logger().Infof("caught event %s, watch prefix %s, start rev %d+1,", resp, lw.Prefix, rev)
-
lw.setRevision(resp.Revision)
- evts := make([]KvEvent, len(resp.Kvs))
- for i, kv := range resp.Kvs {
- evt := KvEvent{Prefix: lw.Prefix, Revision: kv.ModRevision}
- switch {
- case resp.Action == registry.Put && kv.Version == 1:
- evt.Type, evt.Object = proto.EVT_CREATE, kv
- case resp.Action == registry.Put:
- evt.Type, evt.Object = proto.EVT_UPDATE, kv
- case resp.Action == registry.Delete:
- evt.Type, evt.Object = proto.EVT_DELETE, kv
- default:
- return fmt.Errorf("unknown KeyValue %v", kv)
- }
- evts[i] = evt
- }
- f(evts)
+ f(resp)
return nil
}))
@@ -98,7 +76,7 @@ func (lw *ListWatcher) doWatch(ctx context.Context, f func(evt []KvEvent)) error
util.Logger().Errorf(err, "watch prefix %s failed, start rev: %d+1->%d->0", lw.Prefix, rev, lw.Revision())
lw.setRevision(0)
- f([]KvEvent{errEvent(lw.Prefix, err)})
+ f(nil)
}
return err
}
@@ -106,13 +84,13 @@ func (lw *ListWatcher) doWatch(ctx context.Context, f func(evt []KvEvent)) error
type Watcher struct {
ListOps ListWatchConfig
lw *ListWatcher
- bus chan []KvEvent
+ bus chan *registry.PluginResponse
stopCh chan struct{}
stop bool
mux sync.Mutex
}
-func (w *Watcher) EventBus() <-chan []KvEvent {
+func (w *Watcher) EventBus() <-chan *registry.PluginResponse {
return w.bus
}
@@ -133,9 +111,9 @@ func (w *Watcher) process(_ context.Context) {
}
}
-func (w *Watcher) sendEvent(evts []KvEvent) {
+func (w *Watcher) sendEvent(resp *registry.PluginResponse) {
defer util.RecoverAndReport()
- w.bus <- evts
+ w.bus <- resp
}
func (w *Watcher) Stop() {
@@ -150,19 +128,11 @@ func (w *Watcher) Stop() {
w.mux.Unlock()
}
-func errEvent(key string, err error) KvEvent {
- return KvEvent{
- Type: proto.EVT_ERROR,
- Prefix: key,
- Object: err,
- }
-}
-
func newWatcher(lw *ListWatcher, listOps ListWatchConfig) *Watcher {
w := &Watcher{
ListOps: listOps,
lw: lw,
- bus: make(chan []KvEvent, EVENT_BUS_MAX_SIZE),
+ bus: make(chan *registry.PluginResponse, EVENT_BUS_MAX_SIZE),
stopCh: make(chan struct{}),
}
util.Go(w.process)
diff --git a/server/core/backend/parser.go b/server/core/backend/parser.go
new file mode 100644
index 0000000..8873988
--- /dev/null
+++ b/server/core/backend/parser.go
@@ -0,0 +1,206 @@
+/*
+ * 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 backend
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "strings"
+)
+
+// event
+type KvEventFunc func(evt KvEvent)
+
+// new
+type CreateValueFunc func() interface{}
+
+var (
+ newBytes CreateValueFunc = func() interface{} { return []byte(nil) }
+ newString CreateValueFunc = func() interface{} { return "" }
+ newMap CreateValueFunc = func() interface{} { return make(map[string]string) }
+ newService CreateValueFunc = func() interface{} { return new(pb.MicroService) }
+ newInstance CreateValueFunc = func() interface{} { return new(pb.MicroServiceInstance) }
+ newRule CreateValueFunc = func() interface{} { return new(pb.ServiceRule) }
+ newDependencyRule CreateValueFunc = func() interface{} { return new(pb.MicroServiceDependency) }
+ newDependencyQueue CreateValueFunc = func() interface{} { return new(pb.ConsumerDependency) }
+)
+
+// parse
+type ParseValueFunc func(src []byte, dist interface{}) error
+
+var (
+ unParse ParseValueFunc = func(src []byte, dist interface{}) error {
+ d := dist.(*interface{})
+ *d = src
+ return nil
+ }
+ textUnmarshal ParseValueFunc = func(src []byte, dist interface{}) error {
+ d := dist.(*interface{})
+ *d = util.BytesToStringWithNoCopy(src)
+ return nil
+ }
+ mapUnmarshal ParseValueFunc = func(src []byte, dist interface{}) error {
+ d := dist.(*interface{})
+ m := (*d).(map[string]string)
+ return json.Unmarshal(src, &m)
+ }
+ jsonUnmarshal ParseValueFunc = func(src []byte, dist interface{}) error {
+ d := dist.(*interface{})
+ return json.Unmarshal(src, *d)
+ }
+)
+
+type Parser struct {
+ c CreateValueFunc
+ p ParseValueFunc
+}
+
+func (p *Parser) Unmarshal(src []byte) (interface{}, error) {
+ v := p.c()
+ err := p.p(src, &v)
+ return v, err
+}
+
+var (
+ BytesParser = &Parser{newBytes, unParse}
+ StringParser = &Parser{newString, textUnmarshal}
+ MapParser = &Parser{newMap, mapUnmarshal}
+ ServiceParser = &Parser{newService, jsonUnmarshal}
+ InstanceParser = &Parser{newInstance, jsonUnmarshal}
+ RuleParser = &Parser{newRule, jsonUnmarshal}
+ DependencyRuleParser = &Parser{newDependencyRule, jsonUnmarshal}
+ DependencyQueueParser = &Parser{newDependencyQueue, jsonUnmarshal}
+)
+
+func KvToResponse(kv *KeyValue) (keys []string) {
+ return strings.Split(util.BytesToStringWithNoCopy(kv.Key), "/")
+}
+
+func GetInfoFromSvcKV(kv *KeyValue) (serviceId, domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 4 {
+ return
+ }
+ serviceId = keys[l-1]
+ domainProject = fmt.Sprintf("%s/%s", keys[l-3], keys[l-2])
+ return
+}
+
+func GetInfoFromInstKV(kv *KeyValue) (serviceId, instanceId, domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 4 {
+ return
+ }
+ serviceId = keys[l-2]
+ instanceId = keys[l-1]
+ domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
+ return
+}
+
+func GetInfoFromDomainKV(kv *KeyValue) (domain string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 1 {
+ return
+ }
+ domain = keys[l-1]
+ return
+}
+
+func GetInfoFromProjectKV(kv *KeyValue) (domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 2 {
+ return
+ }
+ domainProject = fmt.Sprintf("%s/%s", keys[l-2], keys[l-1])
+ return
+}
+
+func GetInfoFromRuleKV(kv *KeyValue) (serviceId, ruleId, domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 4 {
+ return
+ }
+ serviceId = keys[l-2]
+ ruleId = keys[l-1]
+ domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
+ return
+}
+
+func GetInfoFromTagKV(kv *KeyValue) (serviceId, domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 3 {
+ return
+ }
+ serviceId = keys[l-1]
+ domainProject = fmt.Sprintf("%s/%s", keys[l-3], keys[l-2])
+ return
+}
+
+func GetInfoFromSvcIndexKV(kv *KeyValue) (key *pb.MicroServiceKey) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 6 {
+ return
+ }
+ domainProject := fmt.Sprintf("%s/%s", keys[l-6], keys[l-5])
+ return &pb.MicroServiceKey{
+ Tenant: domainProject,
+ Environment: keys[l-4],
+ AppId: keys[l-3],
+ ServiceName: keys[l-2],
+ Version: keys[l-1],
+ }
+}
+
+func GetInfoFromSchemaSummaryKV(kv *KeyValue) (schemaId string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 1 {
+ return
+ }
+
+ return keys[l-1]
+}
+
+func GetInfoFromSchemaKV(kv *KeyValue) (schemaId string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 1 {
+ return
+ }
+
+ return keys[l-1]
+}
+
+func GetInfoFromDependencyQueueKV(kv *KeyValue) (consumerId, domainProject string) {
+ keys := KvToResponse(kv)
+ l := len(keys)
+ if l < 4 {
+ return
+ }
+ consumerId = keys[l-2]
+ domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
+ return
+}
diff --git a/server/core/backend/parser_test.go b/server/core/backend/parser_test.go
new file mode 100644
index 0000000..dc20af9
--- /dev/null
+++ b/server/core/backend/parser_test.go
@@ -0,0 +1,80 @@
+/*
+ * 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 backend
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "testing"
+)
+
+func TestParseValueFunc(t *testing.T) {
+ r, err := BytesParser.Unmarshal([]byte("a"))
+ if err != nil {
+ t.Fatalf("BytesParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.([]byte); !ok || v[0] != 'a' {
+ t.Fatalf("BytesParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = StringParser.Unmarshal([]byte("abc"))
+ if err != nil {
+ t.Fatalf("StringParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(string); !ok || v != "abc" {
+ t.Fatalf("StringParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = ServiceParser.Unmarshal([]byte(`{"serviceName": "abc"}`))
+ if err != nil {
+ t.Fatalf("ServiceParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(*proto.MicroService); !ok || v.ServiceName != "abc" {
+ t.Fatalf("ServiceParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = InstanceParser.Unmarshal([]byte(`{"hostName": "abc"}`))
+ if err != nil {
+ t.Fatalf("InstanceParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(*proto.MicroServiceInstance); !ok || v.HostName != "abc" {
+ t.Fatalf("InstanceParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = RuleParser.Unmarshal([]byte(`{"ruleId": "abc"}`))
+ if err != nil {
+ t.Fatalf("RuleParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(*proto.ServiceRule); !ok || v.RuleId != "abc" {
+ t.Fatalf("RuleParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = DependencyRuleParser.Unmarshal([]byte(`{"Dependency":[{"tenant":"opsadm/southchina","appId":"csezhq","serviceName":"zhqClient","version":"1.0.0"}]}`))
+ if err != nil {
+ t.Fatalf("DependencyRuleParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(*proto.MicroServiceDependency); !ok || v.Dependency[0].ServiceName != "zhqClient" {
+ t.Fatalf("DependencyRuleParser.Unmarshal failed, %s", v)
+ }
+
+ r, err = MapParser.Unmarshal([]byte(`{"a": "abc"}`))
+ if err != nil {
+ t.Fatalf("MapParser.Unmarshal failed, %s", err.Error())
+ }
+ if v, ok := r.(map[string]string); !ok || v["a"] != "abc" {
+ t.Fatalf("MapParser.Unmarshal failed, %s", v)
+ }
+}
diff --git a/server/core/backend/store.go b/server/core/backend/store.go
index 7a09469..608b2d5 100644
--- a/server/core/backend/store.go
+++ b/server/core/backend/store.go
@@ -32,35 +32,43 @@ func init() {
}
type KvStore struct {
- indexers map[StoreType]*Indexer
+ indexers map[StoreType]Indexer
taskService *async.TaskService
lock sync.RWMutex
ready chan struct{}
goroutine *util.GoRoutine
isClose bool
+ rev int64
}
func (s *KvStore) Initialize() {
- s.indexers = make(map[StoreType]*Indexer)
+ s.indexers = make(map[StoreType]Indexer)
s.taskService = async.NewTaskService()
s.ready = make(chan struct{})
s.goroutine = util.NewGo(context.Background())
-
- for i := StoreType(0); i != typeEnd; i++ {
- s.newIndexBuilder(i, NullCacher)
+ for t := StoreType(0); t != typeEnd; t++ {
+ s.setupIndexer(t, NewBaseIndexer(s.injectConfig(t)))
}
- for i := StoreType(0); i != typeEnd; i++ {
- s.indexers[i].Run()
+}
+
+func (s *KvStore) OnCacheEvent(evt KvEvent) {
+ if s.rev < evt.Revision {
+ s.rev = evt.Revision
}
}
-func (s *KvStore) dispatchEvent(t StoreType, evt KvEvent) {
- s.indexers[t].OnCacheEvent(evt)
- EventProxy(t).OnEvent(evt)
+func (s *KvStore) injectConfig(t StoreType) *Config {
+ return TypeConfig[t].AppendEventFunc(s.OnCacheEvent).
+ AppendEventFunc(EventProxies[t].OnEvent)
}
-func (s *KvStore) newIndexBuilder(t StoreType, cacher Cacher) {
- s.indexers[t] = NewCacheIndexer(TypeRoots[t], cacher)
+func (s *KvStore) setupIndexer(t StoreType, indexer Indexer) {
+ old := s.indexers[t]
+ s.indexers[t] = indexer
+ indexer.Run()
+ if old != nil {
+ old.Stop()
+ }
}
func (s *KvStore) Run() {
@@ -68,25 +76,6 @@ func (s *KvStore) Run() {
s.taskService.Run()
}
-func (s *KvStore) getKvCacherCfgOptions(t StoreType) (opts []ConfigOption) {
- switch t {
- case INSTANCE:
- opts = append(opts, WithDeferHandler(s.SelfPreservationHandler()))
- }
- sz := TypeInitSize[t]
- if sz > 0 {
- opts = append(opts, WithInitSize(sz))
- }
- opts = append(opts,
- WithPrefix(TypeRoots[t]),
- WithEventFunc(func(evt KvEvent) { s.dispatchEvent(t, evt) }))
- return
-}
-
-func (s *KvStore) SelfPreservationHandler() DeferHandler {
- return &InstanceEventDeferHandler{Percent: DEFAULT_SELF_PRESERVATION_PERCENT}
-}
-
func (s *KvStore) store(ctx context.Context) {
defer s.wait(ctx)
@@ -96,17 +85,7 @@ func (s *KvStore) store(ctx context.Context) {
}
for t := StoreType(0); t != typeEnd; t++ {
- switch t {
- case SCHEMA:
- util.Logger().Infof("service center will not cache '%s'", SCHEMA)
- continue
- default:
- s.indexers[t].Stop() // release the exist indexer
- s.newIndexBuilder(t, NewKvCacher(t.String(), s.getKvCacherCfgOptions(t)...))
- }
- }
- for t := StoreType(0); t != typeEnd; t++ {
- s.indexers[t].Run()
+ s.setupIndexer(t, NewCacheIndexer(t.String(), TypeConfig[t]))
}
}
@@ -151,63 +130,63 @@ func (s *KvStore) Ready() <-chan struct{} {
return s.ready
}
-func (s *KvStore) Service() *Indexer {
+func (s *KvStore) Service() Indexer {
return s.indexers[SERVICE]
}
-func (s *KvStore) SchemaSummary() *Indexer {
+func (s *KvStore) SchemaSummary() Indexer {
return s.indexers[SCHEMA_SUMMARY]
}
-func (s *KvStore) Instance() *Indexer {
+func (s *KvStore) Instance() Indexer {
return s.indexers[INSTANCE]
}
-func (s *KvStore) Lease() *Indexer {
+func (s *KvStore) Lease() Indexer {
return s.indexers[LEASE]
}
-func (s *KvStore) ServiceIndex() *Indexer {
+func (s *KvStore) ServiceIndex() Indexer {
return s.indexers[SERVICE_INDEX]
}
-func (s *KvStore) ServiceAlias() *Indexer {
+func (s *KvStore) ServiceAlias() Indexer {
return s.indexers[SERVICE_ALIAS]
}
-func (s *KvStore) ServiceTag() *Indexer {
+func (s *KvStore) ServiceTag() Indexer {
return s.indexers[SERVICE_TAG]
}
-func (s *KvStore) Rule() *Indexer {
+func (s *KvStore) Rule() Indexer {
return s.indexers[RULE]
}
-func (s *KvStore) RuleIndex() *Indexer {
+func (s *KvStore) RuleIndex() Indexer {
return s.indexers[RULE_INDEX]
}
-func (s *KvStore) Schema() *Indexer {
+func (s *KvStore) Schema() Indexer {
return s.indexers[SCHEMA]
}
-func (s *KvStore) Dependency() *Indexer {
+func (s *KvStore) Dependency() Indexer {
return s.indexers[DEPENDENCY]
}
-func (s *KvStore) DependencyRule() *Indexer {
+func (s *KvStore) DependencyRule() Indexer {
return s.indexers[DEPENDENCY_RULE]
}
-func (s *KvStore) DependencyQueue() *Indexer {
+func (s *KvStore) DependencyQueue() Indexer {
return s.indexers[DEPENDENCY_QUEUE]
}
-func (s *KvStore) Domain() *Indexer {
+func (s *KvStore) Domain() Indexer {
return s.indexers[DOMAIN]
}
-func (s *KvStore) Project() *Indexer {
+func (s *KvStore) Project() Indexer {
return s.indexers[PROJECT]
}
@@ -234,7 +213,7 @@ func (s *KvStore) KeepAlive(ctx context.Context, opts ...registry.PluginOpOption
return pt.TTL, pt.Err()
}
-func (s *KvStore) Entity(id StoreType) *Indexer {
+func (s *KvStore) Entity(id StoreType) Indexer {
return s.indexers[id]
}
@@ -243,14 +222,9 @@ func (s *KvStore) Install(e Entity) (id StoreType, err error) {
return
}
- util.Logger().Infof("install new store entity %d:%s->%s", id, e.Name(), e.Prefix())
+ util.Logger().Infof("install new store entity %d:%s->%s", id, e.Name(), e.Config().Prefix)
- var cacher Cacher = NullCacher
- if core.ServerInfo.Config.EnableCache {
- cacher = NewKvCacher(id.String(), s.getKvCacherCfgOptions(id)...)
- }
- s.newIndexBuilder(id, cacher)
- s.indexers[id].Run()
+ s.setupIndexer(id, NewCacheIndexer(id.String(), e.Config()))
return
}
@@ -266,11 +240,6 @@ func Store() *KvStore {
return store
}
-func Revision() (rev int64) {
- for _, i := range Store().indexers {
- if rev < i.Cache().Version() {
- rev = i.Cache().Version()
- }
- }
- return
+func Revision() int64 {
+ return store.rev
}
diff --git a/server/core/key_generator.go b/server/core/key_generator.go
index 634ad68..884e001 100644
--- a/server/core/key_generator.go
+++ b/server/core/key_generator.go
@@ -22,6 +22,7 @@ import (
)
const (
+ SPLIT = "/"
REGISTRY_ROOT_KEY = "cse-sr"
REGISTRY_SYS_KEY = "sys"
REGISTRY_SERVICE_KEY = "ms"
@@ -44,10 +45,7 @@ const (
)
func GetRootKey() string {
- return util.StringJoin([]string{
- "",
- REGISTRY_ROOT_KEY,
- }, "/")
+ return SPLIT + REGISTRY_ROOT_KEY
}
func GetServiceRootKey(domainProject string) string {
@@ -56,7 +54,7 @@ func GetServiceRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_FILE,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceIndexRootKey(domainProject string) string {
@@ -65,7 +63,7 @@ func GetServiceIndexRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_INDEX,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceAliasRootKey(domainProject string) string {
@@ -74,7 +72,7 @@ func GetServiceAliasRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_ALIAS_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceAppKey(domainProject, env, appId string) string {
@@ -82,7 +80,7 @@ func GetServiceAppKey(domainProject, env, appId string) string {
GetServiceIndexRootKey(domainProject),
env,
appId,
- }, "/")
+ }, SPLIT)
}
func GetServiceRuleRootKey(domainProject string) string {
@@ -91,7 +89,7 @@ func GetServiceRuleRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_RULE_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceRuleIndexRootKey(domainProject string) string {
@@ -100,7 +98,7 @@ func GetServiceRuleIndexRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_RULE_INDEX_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceTagRootKey(domainProject string) string {
@@ -109,7 +107,7 @@ func GetServiceTagRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_TAG_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceSchemaRootKey(domainProject string) string {
@@ -118,7 +116,7 @@ func GetServiceSchemaRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_SCHEMA_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetInstanceRootKey(domainProject string) string {
@@ -127,7 +125,7 @@ func GetInstanceRootKey(domainProject string) string {
REGISTRY_INSTANCE_KEY,
REGISTRY_FILE,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetInstanceLeaseRootKey(domainProject string) string {
@@ -136,14 +134,14 @@ func GetInstanceLeaseRootKey(domainProject string) string {
REGISTRY_INSTANCE_KEY,
REGISTRY_LEASE_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceKey(domainProject string, serviceId string) string {
return util.StringJoin([]string{
GetServiceRootKey(domainProject),
serviceId,
- }, "/")
+ }, SPLIT)
}
func GenerateRuleIndexKey(domainProject string, serviceId string, attr string, pattern string) string {
@@ -152,7 +150,7 @@ func GenerateRuleIndexKey(domainProject string, serviceId string, attr string, p
serviceId,
attr,
pattern,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceIndexKey(key *pb.MicroServiceKey) string {
@@ -162,7 +160,7 @@ func GenerateServiceIndexKey(key *pb.MicroServiceKey) string {
key.AppId,
key.ServiceName,
key.Version,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceAliasKey(key *pb.MicroServiceKey) string {
@@ -172,7 +170,7 @@ func GenerateServiceAliasKey(key *pb.MicroServiceKey) string {
key.AppId,
key.Alias,
key.Version,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceRuleKey(domainProject string, serviceId string, ruleId string) string {
@@ -180,14 +178,14 @@ func GenerateServiceRuleKey(domainProject string, serviceId string, ruleId strin
GetServiceRuleRootKey(domainProject),
serviceId,
ruleId,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceTagKey(domainProject string, serviceId string) string {
return util.StringJoin([]string{
GetServiceTagRootKey(domainProject),
serviceId,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceSchemaKey(domainProject string, serviceId string, schemaId string) string {
@@ -195,7 +193,7 @@ func GenerateServiceSchemaKey(domainProject string, serviceId string, schemaId s
GetServiceSchemaRootKey(domainProject),
serviceId,
schemaId,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceSchemaSummaryKey(domainProject string, serviceId string, schemaId string) string {
@@ -203,7 +201,7 @@ func GenerateServiceSchemaSummaryKey(domainProject string, serviceId string, sch
GetServiceSchemaSummaryRootKey(domainProject),
serviceId,
schemaId,
- }, "/")
+ }, SPLIT)
}
func GetServiceSchemaSummaryRootKey(domainProject string) string {
@@ -212,7 +210,7 @@ func GetServiceSchemaSummaryRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_SCHEMA_SUMMARY_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GenerateInstanceKey(domainProject string, serviceId string, instanceId string) string {
@@ -220,7 +218,7 @@ func GenerateInstanceKey(domainProject string, serviceId string, instanceId stri
GetInstanceRootKey(domainProject),
serviceId,
instanceId,
- }, "/")
+ }, SPLIT)
}
func GenerateInstanceLeaseKey(domainProject string, serviceId string, instanceId string) string {
@@ -228,7 +226,7 @@ func GenerateInstanceLeaseKey(domainProject string, serviceId string, instanceId
GetInstanceLeaseRootKey(domainProject),
serviceId,
instanceId,
- }, "/")
+ }, SPLIT)
}
func GenerateServiceDependencyRuleKey(serviceType string, domainProject string, in *pb.MicroServiceKey) string {
@@ -238,7 +236,7 @@ func GenerateServiceDependencyRuleKey(serviceType string, domainProject string,
serviceType,
in.Environment,
in.ServiceName,
- }, "/")
+ }, SPLIT)
}
return util.StringJoin([]string{
GetServiceDependencyRuleRootKey(domainProject),
@@ -247,7 +245,7 @@ func GenerateServiceDependencyRuleKey(serviceType string, domainProject string,
in.AppId,
in.ServiceName,
in.Version,
- }, "/")
+ }, SPLIT)
}
func GenerateConsumerDependencyRuleKey(domainProject string, in *pb.MicroServiceKey) string {
@@ -264,7 +262,7 @@ func GetServiceDependencyRuleRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_DEPS_RULE_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetServiceDependencyQueueRootKey(domainProject string) string {
@@ -273,7 +271,7 @@ func GetServiceDependencyQueueRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_DEPS_QUEUE_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GenerateConsumerDependencyKey(domainProject string, consumerId string, providerId string) string {
@@ -286,7 +284,7 @@ func GenerateServiceDependencyKey(serviceType string, domainProject string, serv
serviceType,
serviceId1,
serviceId2,
- }, "/")
+ }, SPLIT)
}
func GenerateProviderDependencyKey(domainProject string, providerId string, consumerId string) string {
@@ -298,7 +296,7 @@ func GenerateConsumerDependencyQueueKey(domainProject, consumerId, uuid string)
GetServiceDependencyQueueRootKey(domainProject),
consumerId,
uuid,
- }, "/")
+ }, SPLIT)
}
func GetServiceDependencyRootKey(domainProject string) string {
@@ -307,35 +305,35 @@ func GetServiceDependencyRootKey(domainProject string) string {
REGISTRY_SERVICE_KEY,
REGISTRY_DEPENDENCY_KEY,
domainProject,
- }, "/")
+ }, SPLIT)
}
func GetDomainRootKey() string {
return util.StringJoin([]string{
GetRootKey(),
REGISTRY_DOMAIN_KEY,
- }, "/")
+ }, SPLIT)
}
func GenerateDomainKey(domain string) string {
return util.StringJoin([]string{
GetDomainRootKey(),
domain,
- }, "/")
+ }, SPLIT)
}
func GetServerInfoKey() string {
return util.StringJoin([]string{
GetRootKey(),
REGISTRY_SYS_KEY,
- }, "/")
+ }, SPLIT)
}
func GetMetricsRootKey() string {
return util.StringJoin([]string{
GetRootKey(),
REGISTRY_METRICS_KEY,
- }, "/")
+ }, SPLIT)
}
func GenerateMetricsKey(name, utc, domain string) string {
@@ -344,7 +342,7 @@ func GenerateMetricsKey(name, utc, domain string) string {
name,
utc,
domain,
- }, "/")
+ }, SPLIT)
}
func GetProjectRootKey(domain string) string {
@@ -352,12 +350,12 @@ func GetProjectRootKey(domain string) string {
GetRootKey(),
REGISTRY_PROJECT_KEY,
domain,
- }, "/")
+ }, SPLIT)
}
func GenerateProjectKey(domain, project string) string {
return util.StringJoin([]string{
GetProjectRootKey(domain),
project,
- }, "/")
+ }, SPLIT)
}
diff --git a/server/core/proto/services.go b/server/core/proto/services.go
index 133eb09..50f61e3 100644
--- a/server/core/proto/services.go
+++ b/server/core/proto/services.go
@@ -17,13 +17,9 @@
package proto
import (
- "fmt"
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
scerr "github.com/apache/incubator-servicecomb-service-center/server/error"
- "github.com/coreos/etcd/mvcc/mvccpb"
"github.com/gorilla/websocket"
"golang.org/x/net/context"
- "strings"
)
type EventType string
@@ -58,8 +54,8 @@ const (
ENV_ACCEPT string = "acceptance"
ENV_PROD string = "production"
- REGISTERBY_SDK string = "SDK"
- REGISTERBY_SIDECAR string = "SIDECAR"
+ REGISTERBY_SDK string = "SDK"
+ REGISTERBY_SIDECAR string = "SIDECAR"
REGISTERBY_PLATFORM string = "PLATFORM"
APP_ID = "default"
@@ -142,125 +138,6 @@ func CreateResponseWithSCErr(err *scerr.Error) *Response {
}
}
-func KvToResponse(kv *mvccpb.KeyValue) (keys []string, data []byte) {
- keys = strings.Split(util.BytesToStringWithNoCopy(kv.Key), "/")
- data = kv.Value
- return
-}
-
-func GetInfoFromSvcKV(kv *mvccpb.KeyValue) (serviceId, domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 4 {
- return
- }
- serviceId = keys[l-1]
- domainProject = fmt.Sprintf("%s/%s", keys[l-3], keys[l-2])
- return
-}
-
-func GetInfoFromInstKV(kv *mvccpb.KeyValue) (serviceId, instanceId, domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 4 {
- return
- }
- serviceId = keys[l-2]
- instanceId = keys[l-1]
- domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
- return
-}
-
-func GetInfoFromDomainKV(kv *mvccpb.KeyValue) (domain string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 1 {
- return
- }
- domain = keys[l-1]
- return
-}
-
-func GetInfoFromProjectKV(kv *mvccpb.KeyValue) (domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 2 {
- return
- }
- domainProject = fmt.Sprintf("%s/%s", keys[l-2], keys[l-1])
- return
-}
-
-func GetInfoFromRuleKV(kv *mvccpb.KeyValue) (serviceId, ruleId, domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 4 {
- return
- }
- serviceId = keys[l-2]
- ruleId = keys[l-1]
- domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
- return
-}
-
-func GetInfoFromTagKV(kv *mvccpb.KeyValue) (serviceId, domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 3 {
- return
- }
- serviceId = keys[l-1]
- domainProject = fmt.Sprintf("%s/%s", keys[l-3], keys[l-2])
- return
-}
-
-func GetInfoFromSvcIndexKV(kv *mvccpb.KeyValue) (key *MicroServiceKey, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 6 {
- return
- }
- domainProject := fmt.Sprintf("%s/%s", keys[l-6], keys[l-5])
- return &MicroServiceKey{
- Tenant: domainProject,
- Environment: keys[l-4],
- AppId: keys[l-3],
- ServiceName: keys[l-2],
- Version: keys[l-1],
- }, data
-}
-
-func GetInfoFromSchemaSummaryKV(kv *mvccpb.KeyValue) (schemaId string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 1 {
- return
- }
-
- return keys[l-1], data
-}
-
-func GetInfoFromSchemaKV(kv *mvccpb.KeyValue) (schemaId string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 1 {
- return
- }
-
- return keys[l-1], data
-}
-
-func GetInfoFromDependencyQueueKV(kv *mvccpb.KeyValue) (consumerId, domainProject string, data []byte) {
- keys, data := KvToResponse(kv)
- l := len(keys)
- if l < 4 {
- return
- }
- consumerId = keys[l-2]
- domainProject = fmt.Sprintf("%s/%s", keys[l-4], keys[l-3])
- return
-}
-
func DependenciesToKeys(in []*MicroServiceKey, domainProject string) []*MicroServiceKey {
for _, value := range in {
if len(value.Tenant) == 0 {
diff --git a/server/govern/service.go b/server/govern/service.go
index e0c695f..139fe7b 100644
--- a/server/govern/service.go
+++ b/server/govern/service.go
@@ -206,7 +206,7 @@ func (governService *GovernService) GetApplications(ctx context.Context, in *pb.
apps := make([]string, 0, l)
appMap := make(map[string]struct{}, l)
for _, kv := range resp.Kvs {
- key, _ := pb.GetInfoFromSvcIndexKV(kv)
+ key := backend.GetInfoFromSvcIndexKV(kv)
if _, ok := appMap[key.AppId]; ok {
continue
}
@@ -236,7 +236,7 @@ func getServiceAllVersions(ctx context.Context, serviceKey *pb.MicroServiceKey)
return versions, nil
}
for _, kv := range resp.Kvs {
- key, _ := pb.GetInfoFromSvcIndexKV(kv)
+ key := backend.GetInfoFromSvcIndexKV(kv)
versions = append(versions, key.Version)
}
return versions, nil
@@ -255,7 +255,7 @@ func getSchemaInfoUtil(ctx context.Context, domainProject string, serviceId stri
schemas := make([]*pb.Schema, 0, len(resp.Kvs))
for _, kv := range resp.Kvs {
schemaInfo := &pb.Schema{}
- schemaInfo.Schema = util.BytesToStringWithNoCopy(kv.Value)
+ schemaInfo.Schema = util.BytesToStringWithNoCopy(kv.Value.([]byte))
schemaInfo.SchemaId = util.BytesToStringWithNoCopy(kv.Key[len(key):])
schemas = append(schemas, schemaInfo)
}
@@ -367,7 +367,7 @@ func statistics(ctx context.Context) (*pb.Statistics, error) {
svcWithNonVersion := make(map[string]struct{}, respSvc.Count)
svcIdToNonVerKey := make(map[string]string, respSvc.Count)
for _, kv := range respSvc.Kvs {
- key, val := pb.GetInfoFromSvcIndexKV(kv)
+ key := backend.GetInfoFromSvcIndexKV(kv)
if _, ok := app[key.AppId]; !ok {
app[key.AppId] = struct{}{}
}
@@ -377,7 +377,7 @@ func statistics(ctx context.Context) (*pb.Statistics, error) {
if _, ok := svcWithNonVersion[svcWithNonVersionKey]; !ok {
svcWithNonVersion[svcWithNonVersionKey] = struct{}{}
}
- svcIdToNonVerKey[util.BytesToStringWithNoCopy(val)] = svcWithNonVersionKey
+ svcIdToNonVerKey[kv.Value.(string)] = svcWithNonVersionKey
}
result.Services.Count = int64(len(svcWithNonVersion))
@@ -401,7 +401,7 @@ func statistics(ctx context.Context) (*pb.Statistics, error) {
onlineServices := make(map[string]struct{}, respSvc.Count)
for _, kv := range respIns.Kvs {
- serviceId, _, _, _ := pb.GetInfoFromInstKV(kv)
+ serviceId, _, _ := backend.GetInfoFromInstKV(kv)
key, ok := svcIdToNonVerKey[serviceId]
if !ok {
continue
diff --git a/server/plugin/infra/quota/buildin/buildin.go b/server/plugin/infra/quota/buildin/buildin.go
index 6dffeac..6c90e75 100644
--- a/server/plugin/infra/quota/buildin/buildin.go
+++ b/server/plugin/infra/quota/buildin/buildin.go
@@ -31,7 +31,7 @@ import (
)
const (
- SERVICE_NUM_MAX_LIMIT = 12000
+ SERVICE_NUM_MAX_LIMIT = 50000
INSTANCE_NUM_MAX_LIMIT = 150000
RULE_NUM_MAX_LIMIT_PER_SERVICE = 100
SCHEMA_NUM_MAX_LIMIT_PER_SERVICE = 100
@@ -86,7 +86,7 @@ func (q *BuildInQuota) RemandQuotas(ctx context.Context, quotaType quota.Resourc
func ResourceLimitHandler(ctx context.Context, res *quota.ApplyQuotaResource) *quota.ApplyQuotaResult {
var key string
var max int64 = 0
- var indexer *backend.Indexer
+ var indexer backend.Indexer
domainProject := res.DomainProject
serviceId := res.ServiceId
diff --git a/server/plugin/infra/registry/etcd/etcd.go b/server/plugin/infra/registry/etcd/etcd.go
index 6a4d5db..db9bc89 100644
--- a/server/plugin/infra/registry/etcd/etcd.go
+++ b/server/plugin/infra/registry/etcd/etcd.go
@@ -462,7 +462,8 @@ func (c *EtcdClient) TxnWithCmp(ctx context.Context, success []registry.PluginOp
if err != nil {
return nil, err
}
- util.LogNilOrWarnf(start, "registry client txn {if: %s, then: %d, else: %d}", cmps, len(success), len(fail))
+ util.LogNilOrWarnf(start, "registry client txn {if(%v): %s, then: %d, else: %d}, rev: %d",
+ resp.Succeeded, cmps, len(success), len(fail), resp.Header.Revision)
return ®istry.PluginResponse{
Succeeded: resp.Succeeded,
Revision: resp.Header.Revision,
diff --git a/server/server.go b/server/server.go
index 7e98b6a..66de109 100644
--- a/server/server.go
+++ b/server/server.go
@@ -180,11 +180,6 @@ func (s *ServiceCenterServer) autoCompactBackend() {
}
func (s *ServiceCenterServer) startNotifyService() {
- s.notifyService.Config = nf.NotifyServiceConfig{
- AddTimeout: 30 * time.Second,
- NotifyTimeout: 30 * time.Second,
- MaxQueue: 100,
- }
s.notifyService.Start()
}
diff --git a/server/service/util/common.go b/server/service/cache/common.go
similarity index 76%
copy from server/service/util/common.go
copy to server/service/cache/common.go
index 9e31b3f..b17da58 100644
--- a/server/service/util/common.go
+++ b/server/service/cache/common.go
@@ -14,16 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
-
-import "time"
+package cache
const (
- HEADER_REV = "X-Resource-Revision"
- CTX_NOCACHE = "noCache"
- CTX_CACHEONLY = "cacheOnly"
- CTX_REQUEST_REVISION = "requestRev"
- CTX_RESPONSE_REVISION = "responseRev"
+ CTX_FIND_CONSUMER = "consumer"
+ CTX_FIND_PROVIDER = "provider"
+ CTX_FIND_TAGS = "tags"
- cacheTTL = 5 * time.Minute
+ CACHE_FIND = "find"
+ CACHE_DEP = "dep"
)
diff --git a/server/service/cache/dependency.go b/server/service/cache/dependency.go
new file mode 100644
index 0000000..23dd262
--- /dev/null
+++ b/server/service/cache/dependency.go
@@ -0,0 +1,64 @@
+/*
+ * 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 cache
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
+ "golang.org/x/net/context"
+ "math"
+)
+
+var DependencyRule = &DependencyRuleCache{
+ Tree: cache.NewTree(cache.Configure().
+ WithMaxSize(math.MaxInt64))}
+
+func init() {
+ DependencyRule.AddFilter(
+ &ServiceFilter{},
+ &ConsumerFilter{})
+}
+
+type DependencyRuleItem struct {
+ VersionRule string
+}
+
+type DependencyRuleCache struct {
+ *cache.Tree
+}
+
+func (f *DependencyRuleCache) ExistVersionRule(ctx context.Context, consumerId string, provider *pb.MicroServiceKey) bool {
+ cloneCtx := context.WithValue(context.WithValue(ctx,
+ CTX_FIND_CONSUMER, consumerId),
+ CTX_FIND_PROVIDER, provider)
+
+ node, _ := f.Tree.Get(cloneCtx, cache.Options().Temporary(ctx.Value(serviceUtil.CTX_NOCACHE) == "1"))
+ if node == nil {
+ return false
+ }
+ v := node.Cache.Get(CACHE_DEP).(*DependencyRuleItem)
+ if v.VersionRule != provider.Version {
+ v.VersionRule = provider.Version
+ return false
+ }
+ return true
+}
+
+func (f *DependencyRuleCache) Remove(provider *pb.MicroServiceKey) {
+ f.Tree.Remove(context.WithValue(context.Background(), CTX_FIND_PROVIDER, provider))
+}
diff --git a/server/service/util/schema_util_test.go b/server/service/cache/filter_consumer.go
similarity index 67%
copy from server/service/util/schema_util_test.go
copy to server/service/cache/filter_consumer.go
index 7ba7146..25f29ce 100644
--- a/server/service/util/schema_util_test.go
+++ b/server/service/cache/filter_consumer.go
@@ -14,22 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package cache
import (
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
"golang.org/x/net/context"
- "testing"
)
-func TestCheckSchemaInfoExist(t *testing.T) {
- _, err := CheckSchemaInfoExist(util.SetContext(context.Background(), CTX_CACHEONLY, "1"), "")
- if err != nil {
- t.FailNow()
- }
+type ConsumerFilter struct {
+}
+
+func (f *ConsumerFilter) Name(ctx context.Context) string {
+ return ctx.Value(CTX_FIND_CONSUMER).(string)
+}
- _, err = CheckSchemaInfoExist(context.Background(), "")
- if err == nil {
- t.FailNow()
- }
+func (f *ConsumerFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ node = cache.NewNode()
+ node.Cache.Set(CACHE_DEP, &DependencyRuleItem{})
+ return
}
diff --git a/server/service/cache/filter_instances.go b/server/service/cache/filter_instances.go
new file mode 100644
index 0000000..1c38676
--- /dev/null
+++ b/server/service/cache/filter_instances.go
@@ -0,0 +1,72 @@
+/*
+ * 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 cache
+
+import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ apt "github.com/apache/incubator-servicecomb-service-center/server/core"
+ "github.com/apache/incubator-servicecomb-service-center/server/core/backend"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
+ serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
+ "golang.org/x/net/context"
+)
+
+type InstancesFilter struct {
+}
+
+func (f *InstancesFilter) Name(ctx context.Context) string {
+ return ""
+}
+
+func (f *InstancesFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ pCopy := *parent.Cache.Get(CACHE_FIND).(*VersionRuleCacheItem)
+
+ var (
+ instances []*pb.MicroServiceInstance
+ rev int64
+ )
+ for _, providerServiceId := range pCopy.ServiceIds {
+ key := apt.GenerateInstanceKey(provider.Tenant, providerServiceId, "")
+ opts := append(serviceUtil.FromContext(ctx), registry.WithStrKey(key), registry.WithPrefix())
+ resp, err := backend.Store().Instance().Search(ctx, opts...)
+ if err != nil {
+ consumer := ctx.Value(CTX_FIND_CONSUMER).(*pb.MicroService)
+ findFlag := fmt.Sprintf("consumer %s find provider %s/%s/%s", consumer.ServiceId,
+ provider.AppId, provider.ServiceName, provider.Version)
+ util.Logger().Errorf(err, "Instance().Search failed, %s", findFlag)
+ return nil, err
+ }
+
+ for _, kv := range resp.Kvs {
+ if kv.ModRevision > rev {
+ rev = kv.ModRevision
+ }
+ instances = append(instances, kv.Value.(*pb.MicroServiceInstance))
+ }
+ }
+
+ pCopy.Instances = instances
+ pCopy.Rev = serviceUtil.FormatRevision(rev, int64(len(instances)))
+
+ node = cache.NewNode()
+ node.Cache.Set(CACHE_FIND, &pCopy)
+ return
+}
diff --git a/server/service/event/service_event_handler.go b/server/service/cache/filter_permission.go
similarity index 50%
copy from server/service/event/service_event_handler.go
copy to server/service/cache/filter_permission.go
index 45392f1..69e4dd8 100644
--- a/server/service/event/service_event_handler.go
+++ b/server/service/cache/filter_permission.go
@@ -14,49 +14,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package event
+package cache
import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
- "strings"
)
-type ServiceEventHandler struct {
+type AccessibleFilter struct {
}
-func (h *ServiceEventHandler) Type() backend.StoreType {
- return backend.SERVICE
+func (f *AccessibleFilter) Name(ctx context.Context) string {
+ consumer := ctx.Value(CTX_FIND_CONSUMER).(*pb.MicroService)
+ return consumer.ServiceId
}
-func (h *ServiceEventHandler) OnEvent(evt backend.KvEvent) {
- action := evt.Type
- if action != pb.EVT_CREATE && action != pb.EVT_INIT {
- return
+func (f *AccessibleFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ var ids []string
+ consumerId := f.Name(ctx)
+ pCopy := *parent.Cache.Get(CACHE_FIND).(*VersionRuleCacheItem)
+ for _, providerServiceId := range pCopy.ServiceIds {
+ if err := serviceUtil.Accessible(ctx, consumerId, providerServiceId); err != nil {
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ findFlag := fmt.Sprintf("consumer %s find provider %s/%s/%s", consumerId,
+ provider.AppId, provider.ServiceName, provider.Version)
+ util.Logger().Errorf(err, "AccessibleFilter failed, %s", findFlag)
+ continue
+ }
+ ids = append(ids, providerServiceId)
}
- kv := evt.Object.(*mvccpb.KeyValue)
- serviceId, domainProject, data := pb.GetInfoFromSvcKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal service file failed, service %s [%s] event, data is nil",
- serviceId, action)
+ if len(ids) == 0 {
return
}
- newDomain := domainProject[:strings.Index(domainProject, "/")]
- newProject := domainProject[strings.Index(domainProject, "/")+1:]
- err := serviceUtil.NewDomainProject(context.Background(), newDomain, newProject)
- if err != nil {
- util.Logger().Errorf(err, "new domain(%s) or project(%s) failed", newDomain, newProject)
- return
- }
-}
+ pCopy.ServiceIds = ids
-func NewServiceEventHandler() *ServiceEventHandler {
- return &ServiceEventHandler{}
+ node = cache.NewNode()
+ node.Cache.Set(CACHE_FIND, &pCopy)
+ return
}
diff --git a/server/service/util/tag_util_test.go b/server/service/cache/filter_service.go
similarity index 61%
copy from server/service/util/tag_util_test.go
copy to server/service/cache/filter_service.go
index 20fa7d5..37bee6f 100644
--- a/server/service/util/tag_util_test.go
+++ b/server/service/cache/filter_service.go
@@ -14,29 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package cache
import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
"golang.org/x/net/context"
- "testing"
)
-func TestAddTagIntoETCD(t *testing.T) {
- err := AddTagIntoETCD(context.Background(), "", "", map[string]string{"a": "1"})
- if err == nil {
- t.Fatalf(`AddTagIntoETCD with {"a": "1"} tags failed`)
- }
+type ServiceFilter struct {
}
-func TestGetTagsUtils(t *testing.T) {
- _, err := GetTagsUtils(util.SetContext(context.Background(), CTX_CACHEONLY, "1"), "", "")
- if err != nil {
- t.Fatalf(`GetTagsUtils WithCacheOnly failed`)
- }
+func (f *ServiceFilter) Name(ctx context.Context) string {
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ return util.StringJoin([]string{
+ provider.Tenant,
+ provider.Environment,
+ provider.AppId,
+ provider.ServiceName}, "/")
+}
- _, err = GetTagsUtils(context.Background(), "", "")
- if err == nil {
- t.Fatalf(`GetTagsUtils failed`)
- }
+func (f *ServiceFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ node = cache.NewNode()
+ return
}
diff --git a/server/service/cache/filter_tags.go b/server/service/cache/filter_tags.go
new file mode 100644
index 0000000..1964f15
--- /dev/null
+++ b/server/service/cache/filter_tags.go
@@ -0,0 +1,83 @@
+/*
+ * 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 cache
+
+import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
+ "golang.org/x/net/context"
+ "sort"
+ "strings"
+)
+
+type TagsFilter struct {
+}
+
+func (f *TagsFilter) Name(ctx context.Context) string {
+ tags, _ := ctx.Value(CTX_FIND_TAGS).([]string)
+ sort.Strings(tags)
+ return strings.Join(tags, ",")
+}
+
+func (f *TagsFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ tags, _ := ctx.Value(CTX_FIND_TAGS).([]string)
+ if len(tags) == 0 {
+ node = cache.NewNode()
+ node.Cache = parent.Cache
+ return
+ }
+
+ var ids []string
+
+ targetDomainProject := util.ParseTargetDomainProject(ctx)
+ pCopy := *parent.Cache.Get(CACHE_FIND).(*VersionRuleCacheItem)
+
+loopProviderIds:
+ for _, providerServiceId := range pCopy.ServiceIds {
+ tagsFromETCD, err := serviceUtil.GetTagsUtils(ctx, targetDomainProject, providerServiceId)
+ if err != nil {
+ consumer := ctx.Value(CTX_FIND_CONSUMER).(*pb.MicroService)
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ findFlag := fmt.Sprintf("consumer %s find provider %s/%s/%s", consumer.ServiceId,
+ provider.AppId, provider.ServiceName, provider.Version)
+ util.Logger().Errorf(err, "TagsFilter failed, %s", findFlag)
+ return nil, err
+ }
+ if len(tagsFromETCD) == 0 {
+ continue
+ }
+ for _, tag := range tags {
+ if _, ok := tagsFromETCD[tag]; !ok {
+ continue loopProviderIds
+ }
+ }
+ ids = append(ids, providerServiceId)
+ }
+
+ if len(ids) == 0 {
+ return
+ }
+
+ pCopy.ServiceIds = ids
+
+ node = cache.NewNode()
+ node.Cache.Set(CACHE_FIND, &pCopy)
+ return
+}
diff --git a/server/service/event/service_event_handler.go b/server/service/cache/filter_version.go
similarity index 52%
copy from server/service/event/service_event_handler.go
copy to server/service/cache/filter_version.go
index 45392f1..f5bb708 100644
--- a/server/service/event/service_event_handler.go
+++ b/server/service/cache/filter_version.go
@@ -14,49 +14,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package event
+package cache
import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
- "strings"
)
-type ServiceEventHandler struct {
+type VersionRuleFilter struct {
}
-func (h *ServiceEventHandler) Type() backend.StoreType {
- return backend.SERVICE
+func (f *VersionRuleFilter) Name(ctx context.Context) string {
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ return provider.Version
}
-func (h *ServiceEventHandler) OnEvent(evt backend.KvEvent) {
- action := evt.Type
- if action != pb.EVT_CREATE && action != pb.EVT_INIT {
+func (f *VersionRuleFilter) Init(ctx context.Context, parent *cache.Node) (node *cache.Node, err error) {
+ provider := ctx.Value(CTX_FIND_PROVIDER).(*pb.MicroServiceKey)
+ // 版本规则
+ ids, err := serviceUtil.FindServiceIds(ctx, provider.Version, provider)
+ if err != nil {
+ consumer := ctx.Value(CTX_FIND_CONSUMER).(*pb.MicroService)
+ findFlag := fmt.Sprintf("consumer %s find provider %s/%s/%s", consumer.ServiceId,
+ provider.AppId, provider.ServiceName, provider.Version)
+ util.Logger().Errorf(err, "VersionRuleFilter failed, %s", findFlag)
return
}
-
- kv := evt.Object.(*mvccpb.KeyValue)
- serviceId, domainProject, data := pb.GetInfoFromSvcKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal service file failed, service %s [%s] event, data is nil",
- serviceId, action)
+ if len(ids) == 0 {
return
}
- newDomain := domainProject[:strings.Index(domainProject, "/")]
- newProject := domainProject[strings.Index(domainProject, "/")+1:]
- err := serviceUtil.NewDomainProject(context.Background(), newDomain, newProject)
- if err != nil {
- util.Logger().Errorf(err, "new domain(%s) or project(%s) failed", newDomain, newProject)
- return
- }
-}
-
-func NewServiceEventHandler() *ServiceEventHandler {
- return &ServiceEventHandler{}
+ node = cache.NewNode()
+ node.Cache.Set(CACHE_FIND, &VersionRuleCacheItem{
+ VersionRule: provider.Version,
+ ServiceIds: ids,
+ })
+ return
}
diff --git a/server/service/cache/instance.go b/server/service/cache/instance.go
new file mode 100644
index 0000000..52967f7
--- /dev/null
+++ b/server/service/cache/instance.go
@@ -0,0 +1,73 @@
+/*
+ * 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 cache
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/cache"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
+ "golang.org/x/net/context"
+ "math"
+ "time"
+)
+
+var FindInstances = &FindInstancesCache{
+ Tree: cache.NewTree(cache.Configure().
+ WithTTL(2 * time.Minute).
+ WithMaxSize(math.MaxInt64))}
+
+func init() {
+ FindInstances.AddFilter(
+ &ServiceFilter{},
+ &VersionRuleFilter{},
+ &TagsFilter{},
+ &AccessibleFilter{},
+ &InstancesFilter{})
+}
+
+type VersionRuleCacheItem struct {
+ VersionRule string
+ ServiceIds []string
+ Instances []*pb.MicroServiceInstance
+ Rev string
+}
+
+type FindInstancesCache struct {
+ *cache.Tree
+}
+
+func (f *FindInstancesCache) Get(ctx context.Context, consumer *pb.MicroService, provider *pb.MicroServiceKey, tags []string) (*VersionRuleCacheItem, error) {
+ cloneCtx := context.WithValue(context.WithValue(context.WithValue(ctx,
+ CTX_FIND_CONSUMER, consumer),
+ CTX_FIND_PROVIDER, provider),
+ CTX_FIND_TAGS, tags)
+
+ node, err := f.Tree.Get(cloneCtx, cache.Options().Temporary(ctx.Value(serviceUtil.CTX_NOCACHE) == "1"))
+ if node == nil {
+ return nil, err
+ }
+ return node.Cache.Get(CACHE_FIND).(*VersionRuleCacheItem), nil
+}
+
+func (f *FindInstancesCache) Remove(provider *pb.MicroServiceKey) {
+ f.Tree.Remove(context.WithValue(context.Background(), CTX_FIND_PROVIDER, provider))
+ if len(provider.Alias) > 0 {
+ copy := *provider
+ copy.ServiceName = copy.Alias
+ f.Tree.Remove(context.WithValue(context.Background(), CTX_FIND_PROVIDER, ©))
+ }
+}
diff --git a/server/service/event/dependency_event_handler.go b/server/service/event/dependency_event_handler.go
index 2bcbf1d..235fa02 100644
--- a/server/service/event/dependency_event_handler.go
+++ b/server/service/event/dependency_event_handler.go
@@ -17,7 +17,6 @@
package event
import (
- "encoding/json"
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
"github.com/apache/incubator-servicecomb-service-center/server/core"
@@ -26,7 +25,6 @@ import (
"github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
"github.com/apache/incubator-servicecomb-service-center/server/mux"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"time"
)
@@ -90,11 +88,11 @@ func (h *DependencyEventHandler) loop() {
type DependencyEventHandlerResource struct {
dep *pb.ConsumerDependency
- kv *mvccpb.KeyValue
+ kv *backend.KeyValue
domainProject string
}
-func NewDependencyEventHandlerResource(dep *pb.ConsumerDependency, kv *mvccpb.KeyValue, domainProject string) *DependencyEventHandlerResource {
+func NewDependencyEventHandlerResource(dep *pb.ConsumerDependency, kv *backend.KeyValue, domainProject string) *DependencyEventHandlerResource {
return &DependencyEventHandlerResource{
dep,
kv,
@@ -126,25 +124,12 @@ func (h *DependencyEventHandler) Handle() error {
return nil
}
- ctx := context.Background()
-
dependencyTree := util.NewTree(isAddToLeft)
for _, kv := range resp.Kvs {
- r := &pb.ConsumerDependency{}
- consumerId, domainProject, data := pb.GetInfoFromDependencyQueueKV(kv)
-
- err := json.Unmarshal(data, r)
- if err != nil {
- util.Logger().Errorf(err, "maintain dependency failed, unmarshal failed, consumer %s dependency: %s",
- consumerId, util.BytesToStringWithNoCopy(data))
-
- if err = h.removeKV(ctx, kv); err != nil {
- return err
- }
- continue
- }
+ r := kv.Value.(*pb.ConsumerDependency)
+ _, domainProject := backend.GetInfoFromDependencyQueueKV(kv)
res := NewDependencyEventHandlerResource(r, kv, domainProject)
dependencyTree.AddNode(res)
@@ -188,7 +173,7 @@ func (h *DependencyEventHandler) dependencyRuleHandle(res interface{}) error {
return nil
}
-func (h *DependencyEventHandler) removeKV(ctx context.Context, kv *mvccpb.KeyValue) error {
+func (h *DependencyEventHandler) removeKV(ctx context.Context, kv *backend.KeyValue) error {
dResp, err := backend.Registry().TxnWithCmp(ctx, []registry.PluginOp{registry.OpDel(registry.WithKey(kv.Key))},
[]registry.CompareOp{registry.OpCmp(registry.CmpVer(kv.Key), registry.CMP_EQUAL, kv.Version)},
nil)
diff --git a/server/service/event/instance_event_handler.go b/server/service/event/instance_event_handler.go
index a80376d..bd7e8eb 100644
--- a/server/service/event/instance_event_handler.go
+++ b/server/service/event/instance_event_handler.go
@@ -17,14 +17,13 @@
package event
import (
- "encoding/json"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
apt "github.com/apache/incubator-servicecomb-service-center/server/core"
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/service/cache"
nf "github.com/apache/incubator-servicecomb-service-center/server/service/notification"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"strings"
)
@@ -42,33 +41,26 @@ func (h *InstanceEventHandler) OnEvent(evt backend.KvEvent) {
return
}
- kv := evt.Object.(*mvccpb.KeyValue)
- providerId, providerInstanceId, domainProject, data := pb.GetInfoFromInstKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal provider service instance file failed, instance %s/%s [%s] event, data is nil",
- providerId, providerInstanceId, action)
- return
- }
+ providerId, providerInstanceId, domainProject := backend.GetInfoFromInstKV(evt.KV)
if action == pb.EVT_DELETE {
splited := strings.Split(domainProject, "/")
if len(splited) == 2 && !apt.IsDefaultDomainProject(domainProject) {
- domainName := splited[0]
- projectName := splited[1]
- ctx := util.SetDomainProject(context.Background(), domainName, projectName)
- serviceUtil.RemandInstanceQuota(ctx)
+ domainName, projectName := splited[0], splited[1]
+ serviceUtil.RemandInstanceQuota(
+ util.SetDomainProject(context.Background(), domainName, projectName))
}
}
if nf.GetNotifyService().Closed() {
- util.Logger().Warnf(nil, "caught instance %s/%s [%s] event, but notify service is closed",
- providerId, providerInstanceId, action)
+ util.Logger().Warnf(nil, "caught [%s] instance event %s/%s, but notify service is closed",
+ action, providerId, providerInstanceId)
return
}
- util.Logger().Infof("caught instance %s/%s [%s] event", providerId, providerInstanceId, action)
+ util.Logger().Infof("caught [%s] instance event %s/%s", action, providerId, providerInstanceId)
// 查询服务版本信息
- ms, err := serviceUtil.GetServiceInCache(context.Background(), domainProject, providerId)
+ ctx := util.SetContext(context.Background(), serviceUtil.CTX_CACHEONLY, "1")
+ ms, err := serviceUtil.GetService(ctx, domainProject, providerId)
if ms == nil {
util.Logger().Warnf(err, "get provider service %s/%s id in cache failed",
providerId, providerInstanceId)
@@ -76,29 +68,14 @@ func (h *InstanceEventHandler) OnEvent(evt backend.KvEvent) {
}
// 查询所有consumer
- consumerIds, _, err := serviceUtil.GetConsumerIdsByProvider(context.Background(), domainProject, ms)
+ consumerIds, _, err := serviceUtil.GetAllConsumerIds(ctx, domainProject, ms)
if err != nil {
util.Logger().Errorf(err, "query service %s consumers failed", providerId)
return
}
- if len(consumerIds) == 0 {
- return
- }
-
- var instance pb.MicroServiceInstance
- err = json.Unmarshal(data, &instance)
- if err != nil {
- util.Logger().Errorf(err, "unmarshal provider service instance %s/%s file failed",
- providerId, providerInstanceId)
- return
- }
- PublishInstanceEvent(domainProject, action, &pb.MicroServiceKey{
- Environment: ms.Environment,
- AppId: ms.AppId,
- ServiceName: ms.ServiceName,
- Version: ms.Version,
- }, &instance, evt.Revision, consumerIds)
+ PublishInstanceEvent(domainProject, action, pb.MicroServiceToKey(domainProject, ms),
+ evt.KV.Value.(*pb.MicroServiceInstance), evt.Revision, consumerIds)
}
func NewInstanceEventHandler() *InstanceEventHandler {
@@ -106,6 +83,12 @@ func NewInstanceEventHandler() *InstanceEventHandler {
}
func PublishInstanceEvent(domainProject string, action pb.EventType, serviceKey *pb.MicroServiceKey, instance *pb.MicroServiceInstance, rev int64, subscribers []string) {
+ defer cache.FindInstances.Remove(serviceKey)
+
+ if len(subscribers) == 0 {
+ return
+ }
+
response := &pb.WatchInstanceResponse{
Response: pb.CreateResponse(pb.Response_SUCCESS, "Watch instance successfully."),
Action: string(action),
@@ -113,9 +96,6 @@ func PublishInstanceEvent(domainProject string, action pb.EventType, serviceKey
Instance: instance,
}
for _, consumerId := range subscribers {
- // expires cache
- serviceUtil.FindInstancesCache.Delete(domainProject, consumerId, serviceKey)
-
// TODO add超时怎么处理?
job := nf.NewWatchJob(consumerId, apt.GetInstanceRootKey(domainProject)+"/", rev, response)
nf.GetNotifyService().AddJob(job)
diff --git a/server/service/event/rule_event_handler.go b/server/service/event/rule_event_handler.go
index 474811e..4e6cf92 100644
--- a/server/service/event/rule_event_handler.go
+++ b/server/service/event/rule_event_handler.go
@@ -17,7 +17,6 @@
package event
import (
- "encoding/json"
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/async"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
@@ -25,7 +24,6 @@ import (
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
nf "github.com/apache/incubator-servicecomb-service-center/server/service/notification"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
)
@@ -52,7 +50,9 @@ func (apt *RulesChangedTask) Err() error {
}
func (apt *RulesChangedTask) publish(ctx context.Context, domainProject, providerId string, rev int64) error {
- provider, err := serviceUtil.GetServiceInCache(ctx, domainProject, providerId)
+ ctx = util.SetContext(ctx, serviceUtil.CTX_CACHEONLY, "1")
+
+ provider, err := serviceUtil.GetService(ctx, domainProject, providerId)
if err != nil {
util.Logger().Errorf(err, "get provider %s service file failed", providerId)
return err
@@ -62,7 +62,7 @@ func (apt *RulesChangedTask) publish(ctx context.Context, domainProject, provide
return fmt.Errorf("provider %s does not exist", providerId)
}
- consumerIds, err := serviceUtil.GetConsumersInCache(ctx, domainProject, provider)
+ consumerIds, err := serviceUtil.GetConsumerIds(ctx, domainProject, provider)
if err != nil {
util.Logger().Errorf(err, "get consumer services by provider %s failed", providerId)
return err
@@ -86,29 +86,13 @@ func (h *RuleEventHandler) OnEvent(evt backend.KvEvent) {
return
}
- kv := evt.Object.(*mvccpb.KeyValue)
- providerId, ruleId, domainProject, data := pb.GetInfoFromRuleKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal service rule file failed, service %s rule %s [%s] event, data is nil",
- providerId, ruleId, action)
- return
- }
-
+ providerId, ruleId, domainProject := backend.GetInfoFromRuleKV(evt.KV)
if nf.GetNotifyService().Closed() {
- util.Logger().Warnf(nil, "caught service %s rule %s [%s] event, but notify service is closed",
- providerId, ruleId, action)
- return
- }
- util.Logger().Infof("caught service %s rule %s [%s] event", providerId, ruleId, action)
-
- var rule pb.ServiceRule
- err := json.Unmarshal(data, &rule)
- if err != nil {
- util.Logger().Errorf(err, "unmarshal service %s rule %s file failed",
- providerId, ruleId)
+ util.Logger().Warnf(nil, "caught [%s] service rule event %s/%s, but notify service is closed",
+ action, providerId, ruleId)
return
}
+ util.Logger().Infof("caught [%s] service rule event %s/%s", action, providerId, ruleId)
async.Service().Add(context.Background(),
NewRulesChangedAsyncTask(domainProject, providerId, evt.Revision))
diff --git a/server/service/event/service_event_handler.go b/server/service/event/service_event_handler.go
index 45392f1..dc17730 100644
--- a/server/service/event/service_event_handler.go
+++ b/server/service/event/service_event_handler.go
@@ -20,8 +20,8 @@ import (
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/service/cache"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"strings"
)
@@ -34,27 +34,30 @@ func (h *ServiceEventHandler) Type() backend.StoreType {
}
func (h *ServiceEventHandler) OnEvent(evt backend.KvEvent) {
- action := evt.Type
- if action != pb.EVT_CREATE && action != pb.EVT_INIT {
- return
- }
+ ms := evt.KV.Value.(*pb.MicroService)
+ _, domainProject := backend.GetInfoFromSvcKV(evt.KV)
- kv := evt.Object.(*mvccpb.KeyValue)
- serviceId, domainProject, data := pb.GetInfoFromSvcKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal service file failed, service %s [%s] event, data is nil",
- serviceId, action)
- return
+ switch evt.Type {
+ case pb.EVT_INIT, pb.EVT_CREATE:
+ newDomain := domainProject[:strings.Index(domainProject, "/")]
+ newProject := domainProject[strings.Index(domainProject, "/")+1:]
+ err := serviceUtil.NewDomainProject(context.Background(), newDomain, newProject)
+ if err != nil {
+ util.Logger().Errorf(err, "new domain(%s) or project(%s) failed", newDomain, newProject)
+ }
+ default:
}
- newDomain := domainProject[:strings.Index(domainProject, "/")]
- newProject := domainProject[strings.Index(domainProject, "/")+1:]
- err := serviceUtil.NewDomainProject(context.Background(), newDomain, newProject)
- if err != nil {
- util.Logger().Errorf(err, "new domain(%s) or project(%s) failed", newDomain, newProject)
+ if evt.Type == pb.EVT_INIT {
return
}
+
+ util.Logger().Infof("caught [%s] service %s/%s/%s event",
+ evt.Type, ms.AppId, ms.ServiceName, ms.Version)
+
+ // cache
+ providerKey := pb.MicroServiceToKey(domainProject, ms)
+ cache.FindInstances.Remove(providerKey)
}
func NewServiceEventHandler() *ServiceEventHandler {
diff --git a/server/service/event/tag_event_handler.go b/server/service/event/tag_event_handler.go
index a83d2c5..fe09ad8 100644
--- a/server/service/event/tag_event_handler.go
+++ b/server/service/event/tag_event_handler.go
@@ -17,15 +17,14 @@
package event
import (
- "encoding/json"
"fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/async"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/service/cache"
nf "github.com/apache/incubator-servicecomb-service-center/server/service/notification"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
- "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
)
@@ -52,34 +51,36 @@ func (apt *TagsChangedTask) Err() error {
}
func (apt *TagsChangedTask) publish(ctx context.Context, domainProject, consumerId string, rev int64) error {
- consumer, err := serviceUtil.GetServiceInCache(ctx, domainProject, consumerId)
+ ctx = util.SetContext(ctx, serviceUtil.CTX_CACHEONLY, "1")
+
+ consumer, err := serviceUtil.GetService(ctx, domainProject, consumerId)
if err != nil {
- util.Logger().Errorf(err, "get comsumer for publish event %s failed", consumerId)
+ util.Logger().Errorf(err, "get consumer for publish event %s failed", consumerId)
return err
}
if consumer == nil {
util.Logger().Errorf(nil, "service not exist, %s", consumerId)
return fmt.Errorf("service not exist, %s", consumerId)
}
- providerIds, err := serviceUtil.GetProvidersInCache(ctx, domainProject, consumer)
+
+ serviceKey := pb.MicroServiceToKey(domainProject, consumer)
+ cache.FindInstances.Remove(serviceKey)
+
+ providerIds, err := serviceUtil.GetProviderIds(ctx, domainProject, consumer)
if err != nil {
util.Logger().Errorf(err, "get provider services by consumer %s failed", consumerId)
return err
}
for _, providerId := range providerIds {
- provider, err := serviceUtil.GetServiceInCache(ctx, domainProject, providerId)
+ provider, err := serviceUtil.GetService(ctx, domainProject, providerId)
if provider == nil {
util.Logger().Warnf(err, "get service %s file failed", providerId)
continue
}
- PublishInstanceEvent(domainProject, pb.EVT_EXPIRE,
- &pb.MicroServiceKey{
- Environment: provider.Environment,
- AppId: provider.AppId,
- ServiceName: provider.ServiceName,
- Version: provider.Version,
- }, nil, rev, []string{consumerId})
+ providerKey := pb.MicroServiceToKey(domainProject, provider)
+
+ PublishInstanceEvent(domainProject, pb.EVT_EXPIRE, providerKey, nil, rev, []string{consumerId})
}
return nil
}
@@ -97,28 +98,14 @@ func (h *TagEventHandler) OnEvent(evt backend.KvEvent) {
return
}
- kv := evt.Object.(*mvccpb.KeyValue)
- consumerId, domainProject, data := pb.GetInfoFromTagKV(kv)
- if data == nil {
- util.Logger().Errorf(nil,
- "unmarshal service rule file failed, service %s tags [%s] event, data is nil",
- consumerId, action)
- return
- }
+ consumerId, domainProject := backend.GetInfoFromTagKV(evt.KV)
if nf.GetNotifyService().Closed() {
- util.Logger().Warnf(nil, "caught service %s tags [%s] event, but notify service is closed",
- consumerId, action)
- return
- }
- util.Logger().Infof("caught service %s tags [%s] event", consumerId, action)
-
- var rule pb.ServiceRule
- err := json.Unmarshal(data, &rule)
- if err != nil {
- util.Logger().Errorf(err, "unmarshal service %s tags file failed", consumerId)
+ util.Logger().Warnf(nil, "caught [%s] service tags event %s/%s, but notify service is closed",
+ action, consumerId, evt.KV.Value)
return
}
+ util.Logger().Infof("caught [%s] service tags event %s/%s", action, consumerId, evt.KV.Value)
async.Service().Add(context.Background(),
NewTagsChangedAsyncTask(domainProject, consumerId, evt.Revision))
diff --git a/server/service/instance.go b/server/service/instance.go
index 4b90968..848291f 100644
--- a/server/service/instance.go
+++ b/server/service/instance.go
@@ -28,6 +28,7 @@ import (
"github.com/apache/incubator-servicecomb-service-center/server/infra/quota"
"github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
"github.com/apache/incubator-servicecomb-service-center/server/plugin"
+ "github.com/apache/incubator-servicecomb-service-center/server/service/cache"
serviceUtil "github.com/apache/incubator-servicecomb-service-center/server/service/util"
"golang.org/x/net/context"
"math"
@@ -537,91 +538,87 @@ func (s *InstanceService) Find(ctx context.Context, in *pb.FindInstancesRequest)
provider.Tenant = util.ParseTargetDomainProject(ctx)
}
+ noCache, cacheOnly := ctx.Value(serviceUtil.CTX_NOCACHE) == "1", ctx.Value(serviceUtil.CTX_CACHEONLY) == "1"
+ rev, _ := ctx.Value(serviceUtil.CTX_REQUEST_REVISION).(string)
+ reqRev, _ := serviceUtil.ParseRevision(rev)
+ cloneCtx := util.CloneContext(ctx)
+
// cache
- if item := serviceUtil.FindInstancesCache.Get(provider.Tenant, in.ConsumerServiceId, provider); item != nil {
- noCache, cacheOnly := ctx.Value(serviceUtil.CTX_NOCACHE) == "1", ctx.Value(serviceUtil.CTX_CACHEONLY) == "1"
- rev, _ := ctx.Value(serviceUtil.CTX_REQUEST_REVISION).(string)
- reqRev, _ := serviceUtil.ParseRevision(rev)
- cacheRev, _ := serviceUtil.ParseRevision(item.Rev)
- if !noCache && (cacheOnly || reqRev <= cacheRev) {
- instances := item.Instances
- if rev == item.Rev {
- instances = instances[:0]
- }
- util.SetContext(ctx, serviceUtil.CTX_RESPONSE_REVISION, item.Rev)
+ var (
+ item *cache.VersionRuleCacheItem
+ i = 0
+ newVersionRule = !cache.DependencyRule.ExistVersionRule(cloneCtx, in.ConsumerServiceId, provider)
+ )
+ if noCache {
+ i = 1
+ }
+ for ; i < 2; i++ {
+ item, err = cache.FindInstances.Get(cloneCtx, service, provider, in.Tags)
+ if err != nil {
+ util.Logger().Errorf(err, "FindInstancesCache.Get failed, %s", findFlag)
return &pb.FindInstancesResponse{
- Response: pb.CreateResponse(pb.Response_SUCCESS, "Query service instances successfully."),
- Instances: instances,
+ Response: pb.CreateResponse(scerr.ErrInternal, err.Error()),
+ }, err
+ }
+ if item == nil {
+ mes := fmt.Sprintf("provider not exist, %s", findFlag)
+ util.Logger().Errorf(nil, "FindInstancesCache.Get failed, %s", mes)
+ return &pb.FindInstancesResponse{
+ Response: pb.CreateResponse(scerr.ErrServiceNotExists, mes),
}, nil
}
- }
- // 版本规则
- ids, err := serviceUtil.FindServiceIds(ctx, in.VersionRule, provider)
- if err != nil {
- util.Logger().Errorf(err, "find instance failed, %s: get providers failed.", findFlag)
- return &pb.FindInstancesResponse{
- Response: pb.CreateResponse(scerr.ErrInternal, err.Error()),
- }, err
- }
-
- if len(ids) == 0 {
- mes := fmt.Sprintf("provider not exist, %s", findFlag)
- util.Logger().Errorf(nil, "find instance failed, %s", mes)
- return &pb.FindInstancesResponse{
- Response: pb.CreateResponse(scerr.ErrServiceNotExists, mes),
- }, nil
- }
-
- for i := 0; i < len(ids); i++ {
- err := s.getInstancePreCheck(ctx, ids[i], in.ConsumerServiceId, in.Tags)
- if err != nil {
- ids = append(ids[:i], ids[i+1:]...)
- i--
+ cacheRev, _ := serviceUtil.ParseRevision(item.Rev)
+ if noCache || cacheOnly || reqRev <= cacheRev {
+ break
}
+ util.SetContext(cloneCtx, serviceUtil.CTX_NOCACHE, "1")
}
- if len(ids) > 0 {
- //维护version的规则,service name 可能是别名,所以重新获取
- providerService, err := serviceUtil.GetService(ctx, provider.Tenant, ids[0])
- if providerService == nil {
- util.Logger().Errorf(err, "find instance failed, %s: provider %s not exist.", findFlag, ids[0])
+ // add dependency queue
+ if newVersionRule {
+ provider, err = s.reshapeProviderKey(ctx, provider, item.ServiceIds[0])
+ if provider != nil {
+ err = serviceUtil.AddServiceVersionRule(ctx, domainProject, service, provider)
+ } else {
+ mes := fmt.Sprintf("provider not exist, %s", findFlag)
+ util.Logger().Errorf(nil, "AddServiceVersionRule failed, %s", mes)
return &pb.FindInstancesResponse{
- Response: pb.CreateResponse(scerr.ErrServiceNotExists, "No provider matched."),
+ Response: pb.CreateResponse(scerr.ErrServiceNotExists, mes),
}, nil
}
-
- provider = pb.MicroServiceToKey(provider.Tenant, providerService)
- provider.Version = in.VersionRule
-
- err = serviceUtil.AddServiceVersionRule(ctx, domainProject, service, provider)
if err != nil {
- util.Logger().Errorf(err, "find instance failed, %s: add service version rule failed.", findFlag)
+ util.Logger().Errorf(err, "AddServiceVersionRule failed, %s", findFlag)
return &pb.FindInstancesResponse{
Response: pb.CreateResponse(scerr.ErrInternal, err.Error()),
}, err
}
}
- instances, rev, err := serviceUtil.GetAllInstancesOfServices(ctx, util.ParseTargetDomainProject(ctx), ids)
- if err != nil {
- util.Logger().Errorf(err, "find instance failed, %s: GetAllInstancesOfServices failed.", findFlag)
- return &pb.FindInstancesResponse{
- Response: pb.CreateResponse(scerr.ErrInternal, err.Error()),
- }, err
+ instances := item.Instances
+ if rev == item.Rev {
+ instances = instances[:0]
}
-
- serviceUtil.FindInstancesCache.Set(provider.Tenant, in.ConsumerServiceId, provider, &serviceUtil.VersionRuleCacheItem{
- Instances: instances,
- Rev: rev,
- })
- util.SetContext(ctx, serviceUtil.CTX_RESPONSE_REVISION, rev)
+ util.SetContext(ctx, serviceUtil.CTX_RESPONSE_REVISION, item.Rev)
return &pb.FindInstancesResponse{
Response: pb.CreateResponse(pb.Response_SUCCESS, "Query service instances successfully."),
Instances: instances,
}, nil
}
+func (s *InstanceService) reshapeProviderKey(ctx context.Context, provider *pb.MicroServiceKey, providerId string) (*pb.MicroServiceKey, error) {
+ //维护version的规则,service name 可能是别名,所以重新获取
+ providerService, err := serviceUtil.GetService(ctx, provider.Tenant, providerId)
+ if providerService == nil {
+ return nil, err
+ }
+
+ versionRule := provider.Version
+ provider = pb.MicroServiceToKey(provider.Tenant, providerService)
+ provider.Version = versionRule
+ return provider, nil
+}
+
func (s *InstanceService) UpdateStatus(ctx context.Context, in *pb.UpdateInstanceStatusRequest) (*pb.UpdateInstanceStatusResponse, error) {
domainProject := util.ParseDomainProject(ctx)
updateStatusFlag := util.StringJoin([]string{in.ServiceId, in.InstanceId, in.Status}, "/")
diff --git a/server/service/instance_test.go b/server/service/instance_test.go
index c11866e..692e4d1 100644
--- a/server/service/instance_test.go
+++ b/server/service/instance_test.go
@@ -1235,8 +1235,7 @@ var _ = Describe("'Instance' service", func() {
VersionRule: "1.0.5",
})
Expect(err).To(BeNil())
- Expect(respFind.Response.Code).To(Equal(pb.Response_SUCCESS))
- Expect(len(respFind.Instances)).To(Equal(0))
+ Expect(respFind.Response.Code).To(Equal(scerr.ErrServiceNotExists))
By("provider tag does not exist")
respFind, err = instanceResource.Find(getContext(), &pb.FindInstancesRequest{
@@ -1247,8 +1246,7 @@ var _ = Describe("'Instance' service", func() {
Tags: []string{"notexisttag"},
})
Expect(err).To(BeNil())
- Expect(respFind.Response.Code).To(Equal(pb.Response_SUCCESS))
- Expect(len(respFind.Instances)).To(Equal(0))
+ Expect(respFind.Response.Code).To(Equal(scerr.ErrServiceNotExists))
By("shared service discovery")
os.Setenv("CSE_SHARED_SERVICES", "query_instance_shared_provider")
diff --git a/server/service/microservice.go b/server/service/microservice.go
index 2136f8c..b504648 100644
--- a/server/service/microservice.go
+++ b/server/service/microservice.go
@@ -189,7 +189,7 @@ func (s *MicroServiceService) CreateServicePri(ctx context.Context, in *pb.Creat
func checkQuota(ctx context.Context, domainProject string) (quota.QuotaReporter, *scerr.Error) {
if core.IsSCInstance(ctx) {
- util.Logger().Infof("it is service-center")
+ util.Logger().Debugf("service-center self register")
return nil, nil
}
res := quota.NewApplyQuotaResource(quota.MicroServiceQuotaType, domainProject, "", 1)
diff --git a/server/service/notification/common.go b/server/service/notification/common.go
index 7e2e16a..7b0728c 100644
--- a/server/service/notification/common.go
+++ b/server/service/notification/common.go
@@ -17,17 +17,17 @@
package notification
import (
- "fmt"
"strconv"
"time"
)
const (
- DEFAULT_MAX_QUEUE = 1000
- DEFAULT_INIT_SUBSCRIBERS = 1000
- DEFAULT_ON_MESSAGE_TIMEOUT = 100 * time.Millisecond
- DEFAULT_TIMEOUT = 30 * time.Second
+ DEFAULT_MAX_QUEUE = 1000
+ DEFAULT_ADD_JOB_TIMEOUT = 1 * time.Second
+ DEFAULT_SEND_TIMEOUT = 30 * time.Second
+)
+const (
NOTIFTY NotifyType = iota
INSTANCE
typeEnd
@@ -42,13 +42,21 @@ func (nt NotifyType) String() string {
return "NotifyType" + strconv.Itoa(int(nt))
}
-type NotifyServiceConfig struct {
- AddTimeout time.Duration
- NotifyTimeout time.Duration
- MaxQueue int64
+func (nt NotifyType) QueueSize() (s int) {
+ if int(nt) < len(notifyTypeQueues) {
+ s = notifyTypeQueues[nt]
+ }
+ if s <= 0 {
+ s = DEFAULT_MAX_QUEUE
+ }
+ return
+}
+
+var notifyTypeNames = []string{
+ NOTIFTY: "NOTIFTY",
+ INSTANCE: "INSTANCE",
}
-func (nsc NotifyServiceConfig) String() string {
- return fmt.Sprintf("{acceptQueue: %d, accept: %s, notify: %s}",
- nsc.MaxQueue, nsc.AddTimeout, nsc.NotifyTimeout)
+var notifyTypeQueues = []int{
+ INSTANCE: 100 * 1000,
}
diff --git a/server/service/util/tag_util_test.go b/server/service/notification/group.go
similarity index 51%
copy from server/service/util/tag_util_test.go
copy to server/service/notification/group.go
index 20fa7d5..679acfd 100644
--- a/server/service/util/tag_util_test.go
+++ b/server/service/notification/group.go
@@ -14,29 +14,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package notification
import (
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "golang.org/x/net/context"
- "testing"
)
-func TestAddTagIntoETCD(t *testing.T) {
- err := AddTagIntoETCD(context.Background(), "", "", map[string]string{"a": "1"})
- if err == nil {
- t.Fatalf(`AddTagIntoETCD with {"a": "1"} tags failed`)
- }
+type Group struct {
+ name string
+ subscribers *util.ConcurrentMap
+}
+
+func (g *Group) Name() string {
+ return g.name
}
-func TestGetTagsUtils(t *testing.T) {
- _, err := GetTagsUtils(util.SetContext(context.Background(), CTX_CACHEONLY, "1"), "", "")
- if err != nil {
- t.Fatalf(`GetTagsUtils WithCacheOnly failed`)
+func (g *Group) Notify(job NotifyJob) {
+ g.subscribers.ForEach(func(item util.MapItem) (next bool) {
+ item.Value.(Subscriber).OnMessage(job)
+ return true
+ })
+}
+
+func (g *Group) Subscribers(name string) Subscriber {
+ s, ok := g.subscribers.Get(name)
+ if !ok {
+ return nil
}
+ return s.(Subscriber)
+}
+
+func (g *Group) AddSubscriber(subscriber Subscriber) Subscriber {
+ return g.subscribers.PutIfAbsent(subscriber.Id(), subscriber).(Subscriber)
+}
+
+func (g *Group) Remove(name string) {
+ g.subscribers.Remove(name)
+}
+
+func (g *Group) Size() int {
+ return g.subscribers.Size()
+}
- _, err = GetTagsUtils(context.Background(), "", "")
- if err == nil {
- t.Fatalf(`GetTagsUtils failed`)
+func NewGroup(name string) *Group {
+ return &Group{
+ name: name,
+ subscribers: util.NewConcurrentMap(0),
}
}
diff --git a/server/service/notification/group_test.go b/server/service/notification/group_test.go
new file mode 100644
index 0000000..b241eb4
--- /dev/null
+++ b/server/service/notification/group_test.go
@@ -0,0 +1,69 @@
+/*
+ * 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 notification
+
+import "testing"
+
+type mockSubscriber struct {
+ *BaseSubscriber
+ job NotifyJob
+}
+
+func (s *mockSubscriber) OnMessage(job NotifyJob) {
+ s.job = job
+}
+
+func TestGroup_Add(t *testing.T) {
+ m := NewSubscriber(INSTANCE, "s1", "g1")
+ g := NewGroup("g1")
+ if g.Name() != "g1" {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ if g.AddSubscriber(m) != m {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ if g.AddSubscriber(NewSubscriber(INSTANCE, "s1", "g1")) == m {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ same := *m
+ if g.AddSubscriber(&same) != m {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ if g.Size() != 2 {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ g.Remove(m.Id())
+ if g.Size() != 1 {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ if g.Subscribers(m.Id()) == m {
+ t.Fatalf("TestGroup_Add failed")
+ }
+
+ mock := &mockSubscriber{BaseSubscriber: NewSubscriber(INSTANCE, "s1", "g1")}
+ if g.AddSubscriber(mock) != mock {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ if g.Subscribers(mock.Id()) != mock {
+ t.Fatalf("TestGroup_Add failed")
+ }
+ job := &BaseNotifyJob{nType: INSTANCE}
+ g.Notify(job)
+ if mock.job != job {
+ t.Fatalf("TestGroup_Add failed")
+ }
+}
diff --git a/server/service/notification/listwatcher.go b/server/service/notification/listwatcher.go
index a6942c9..0877b27 100644
--- a/server/service/notification/listwatcher.go
+++ b/server/service/notification/listwatcher.go
@@ -25,26 +25,30 @@ import (
// 状态变化推送
type WatchJob struct {
- BaseNotifyJob
+ *BaseNotifyJob
Revision int64
Response *pb.WatchInstanceResponse
}
type ListWatcher struct {
- BaseSubscriber
+ *BaseSubscriber
Job chan *WatchJob
ListRevision int64
ListFunc func() (results []*pb.WatchInstanceResponse, rev int64)
+ listCh chan struct{}
+}
- listCh chan struct{}
+func (s *ListWatcher) SetError(err error) {
+ s.BaseSubscriber.SetError(err)
+ // 触发清理job
+ s.Service().AddJob(NewNotifyServiceHealthCheckJob(s))
}
func (w *ListWatcher) OnAccept() {
if w.Err() != nil {
return
}
-
- util.Logger().Debugf("accepted by notify service, %s watcher %s %s", w.Type(), w.Id(), w.Subject())
+ util.Logger().Debugf("accepted by notify service, %s watcher %s %s", w.Type(), w.Group(), w.Subject())
util.Go(w.listAndPublishJobs)
}
@@ -56,7 +60,7 @@ func (w *ListWatcher) listAndPublishJobs(_ context.Context) {
results, rev := w.ListFunc()
w.ListRevision = rev
for _, response := range results {
- w.sendMessage(NewWatchJob(w.Id(), w.Subject(), w.ListRevision, response))
+ w.sendMessage(NewWatchJob(w.Group(), w.Subject(), w.ListRevision, response))
}
}
@@ -71,68 +75,73 @@ func (w *ListWatcher) OnMessage(job NotifyJob) {
return
}
- timer := time.NewTimer(DEFAULT_ON_MESSAGE_TIMEOUT)
select {
case <-w.listCh:
- timer.Stop()
- case <-timer.C:
- util.Logger().Errorf(nil,
- "the %s listwatcher %s %s is not ready[over %s], drop the event %v",
- w.Type(), w.Id(), w.Subject(), DEFAULT_ON_MESSAGE_TIMEOUT, job)
- return
+ default:
+ timer := time.NewTimer(w.Timeout())
+ select {
+ case <-w.listCh:
+ timer.Stop()
+ case <-timer.C:
+ util.Logger().Errorf(nil,
+ "the %s listwatcher %s %s is not ready[over %s], send the event %v",
+ w.Type(), w.Group(), w.Subject(), w.Timeout(), job)
+ }
}
if wJob.Revision <= w.ListRevision {
util.Logger().Warnf(nil,
"unexpected notify %s job is coming in, watcher %s %s, job is %v, current revision is %v",
- w.Type(), w.Id(), w.Subject(), job, w.ListRevision)
+ w.Type(), w.Group(), w.Subject(), job, w.ListRevision)
return
}
w.sendMessage(wJob)
}
func (w *ListWatcher) sendMessage(job *WatchJob) {
- util.Logger().Debugf("start to notify %s watcher %s %s, job is %v, current revision is %v", w.Type(),
- w.Id(), w.Subject(), job, w.ListRevision)
defer util.RecoverAndReport()
- timer := time.NewTimer(DEFAULT_ON_MESSAGE_TIMEOUT)
select {
case w.Job <- job:
- timer.Stop()
- case <-timer.C:
- util.Logger().Errorf(nil,
- "the %s watcher %s %s event queue is full[over %s], drop the event %v",
- w.Type(), w.Id(), w.Subject(), DEFAULT_ON_MESSAGE_TIMEOUT, job)
+ default:
+ timer := time.NewTimer(w.Timeout())
+ select {
+ case w.Job <- job:
+ timer.Stop()
+ case <-timer.C:
+ util.Logger().Errorf(nil,
+ "the %s watcher %s %s event queue is full[over %s], drop the event %v",
+ w.Type(), w.Group(), w.Subject(), w.Timeout(), job)
+ }
}
}
+func (w *ListWatcher) Timeout() time.Duration {
+ return DEFAULT_ADD_JOB_TIMEOUT
+}
+
func (w *ListWatcher) Close() {
close(w.Job)
}
-func NewWatchJob(subscriberId, subject string, rev int64, response *pb.WatchInstanceResponse) *WatchJob {
+func NewWatchJob(group, subject string, rev int64, response *pb.WatchInstanceResponse) *WatchJob {
return &WatchJob{
- BaseNotifyJob: BaseNotifyJob{
- subscriberId: subscriberId,
- subject: subject,
- nType: INSTANCE,
+ BaseNotifyJob: &BaseNotifyJob{
+ group: group,
+ subject: subject,
+ nType: INSTANCE,
},
Revision: rev,
Response: response,
}
}
-func NewListWatcher(id string, subject string,
+func NewListWatcher(group string, subject string,
listFunc func() (results []*pb.WatchInstanceResponse, rev int64)) *ListWatcher {
watcher := &ListWatcher{
- BaseSubscriber: BaseSubscriber{
- id: id,
- subject: subject,
- nType: INSTANCE,
- },
- Job: make(chan *WatchJob, DEFAULT_MAX_QUEUE),
- ListFunc: listFunc,
- listCh: make(chan struct{}),
+ BaseSubscriber: NewSubscriber(INSTANCE, subject, group),
+ Job: make(chan *WatchJob, DEFAULT_MAX_QUEUE),
+ ListFunc: listFunc,
+ listCh: make(chan struct{}),
}
return watcher
}
diff --git a/server/service/notification/notice.go b/server/service/notification/notice.go
index 498d435..26d5235 100644
--- a/server/service/notification/notice.go
+++ b/server/service/notification/notice.go
@@ -17,25 +17,25 @@
package notification
type NotifyJob interface {
- SubscriberId() string
- Subject() string
Type() NotifyType
+ Group() string
+ Subject() string
}
type BaseNotifyJob struct {
- subscriberId string
- subject string
- nType NotifyType
+ nType NotifyType
+ subject string
+ group string
}
-func (s *BaseNotifyJob) SubscriberId() string {
- return s.subscriberId
+func (s *BaseNotifyJob) Type() NotifyType {
+ return s.nType
}
-func (s *BaseNotifyJob) Subject() string {
- return s.subject
+func (s *BaseNotifyJob) Group() string {
+ return s.group
}
-func (s *BaseNotifyJob) Type() NotifyType {
- return s.nType
+func (s *BaseNotifyJob) Subject() string {
+ return s.subject
}
diff --git a/server/service/notification/notification_healthchecker.go b/server/service/notification/notification_healthchecker.go
index 310275b..597713c 100644
--- a/server/service/notification/notification_healthchecker.go
+++ b/server/service/notification/notification_healthchecker.go
@@ -29,7 +29,7 @@ type NotifyServiceHealthChecker struct {
}
type NotifyServiceHealthCheckJob struct {
- BaseNotifyJob
+ *BaseNotifyJob
ErrorSubscriber Subscriber
}
@@ -39,19 +39,19 @@ func (s *NotifyServiceHealthChecker) OnMessage(job NotifyJob) {
if j.ErrorSubscriber.Type() == NOTIFTY {
util.Logger().Errorf(nil, "remove %s watcher %s %s failed, here cause a dead lock",
- j.ErrorSubscriber.Type(), j.ErrorSubscriber.Subject(), j.ErrorSubscriber.Id())
+ j.ErrorSubscriber.Type(), j.ErrorSubscriber.Subject(), j.ErrorSubscriber.Group())
return
}
- util.Logger().Warnf(err, "notification service remove %s watcher %s %s",
- j.ErrorSubscriber.Type(), j.ErrorSubscriber.Subject(), j.ErrorSubscriber.Id())
+ util.Logger().Debugf("notification service remove %s watcher, error: %s, subject: %s, group: %s",
+ j.ErrorSubscriber.Type(), err.Error(), j.ErrorSubscriber.Subject(), j.ErrorSubscriber.Group())
s.Service().RemoveSubscriber(j.ErrorSubscriber)
}
func NewNotifyServiceHealthChecker() *NotifyServiceHealthChecker {
return &NotifyServiceHealthChecker{
BaseSubscriber: BaseSubscriber{
- id: NOTIFY_SERVER_CHECKER_NAME,
+ group: NOTIFY_SERVER_CHECKER_NAME,
subject: NOTIFY_SERVER_CHECK_SUBJECT,
nType: NOTIFTY,
},
@@ -60,10 +60,10 @@ func NewNotifyServiceHealthChecker() *NotifyServiceHealthChecker {
func NewNotifyServiceHealthCheckJob(s Subscriber) *NotifyServiceHealthCheckJob {
return &NotifyServiceHealthCheckJob{
- BaseNotifyJob: BaseNotifyJob{
- subscriberId: NOTIFY_SERVER_CHECKER_NAME,
- subject: NOTIFY_SERVER_CHECK_SUBJECT,
- nType: NOTIFTY,
+ BaseNotifyJob: &BaseNotifyJob{
+ group: NOTIFY_SERVER_CHECKER_NAME,
+ subject: NOTIFY_SERVER_CHECK_SUBJECT,
+ nType: NOTIFTY,
},
ErrorSubscriber: s,
}
diff --git a/server/service/notification/notification_service.go b/server/service/notification/notification_service.go
index 82cc9b1..a5ea4ea 100644
--- a/server/service/notification/notification_service.go
+++ b/server/service/notification/notification_service.go
@@ -17,19 +17,12 @@
package notification
import (
- "container/list"
"errors"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
"golang.org/x/net/context"
"sync"
- "time"
)
-var notifyTypeNames = []string{
- NOTIFTY: "NOTIFTY",
- INSTANCE: "INSTANCE",
-}
-
var notifyService *NotifyService
func init() {
@@ -39,102 +32,78 @@ func init() {
}
}
-type subscriberIndex map[string]*list.List
-
-type subscriberSubjectIndex map[string]subscriberIndex
-
-type serviceIndex map[NotifyType]subscriberSubjectIndex
-
type NotifyService struct {
- Config NotifyServiceConfig
-
- services serviceIndex
- queues map[NotifyType]chan NotifyJob
- waits sync.WaitGroup
- mutexes map[NotifyType]*sync.Mutex
- err chan error
- closeMux sync.RWMutex
- isClose bool
- goroutine *util.GoRoutine
+ processors *util.ConcurrentMap
+ goroutine *util.GoRoutine
+ err chan error
+ closeMux sync.RWMutex
+ isClose bool
}
func (s *NotifyService) Err() <-chan error {
return s.err
}
-func (s *NotifyService) AddSubscriber(n Subscriber) error {
- if s.Closed() {
- return errors.New("server is shutting down")
+func (s *NotifyService) init() {
+ s.processors = util.NewConcurrentMap(int(typeEnd))
+ s.err = make(chan error, 1)
+ for i := NotifyType(0); i != typeEnd; i++ {
+ s.processors.Put(i, NewProcessor(i.String(), i.QueueSize()))
}
+}
- s.mutexes[n.Type()].Lock()
- ss, ok := s.services[n.Type()]
- if !ok {
- s.mutexes[n.Type()].Unlock()
- return errors.New("Unknown subscribe type")
+func (s *NotifyService) Start() {
+ if !s.Closed() {
+ util.Logger().Warnf(nil, "notify service is already running")
+ return
}
+ s.closeMux.Lock()
+ s.isClose = false
+ s.closeMux.Unlock()
- sr, ok := ss[n.Subject()]
- if !ok {
- sr = make(subscriberIndex, DEFAULT_INIT_SUBSCRIBERS)
- ss[n.Subject()] = sr // add a subscriber
+ s.init()
+ // 错误subscriber清理
+ s.AddSubscriber(NewNotifyServiceHealthChecker())
+
+ util.Logger().Debugf("notify service is started")
+
+ s.processors.ForEach(func(item util.MapItem) (next bool) {
+ s.goroutine.Do(item.Value.(*Processor).Do)
+ return true
+ })
+}
+
+func (s *NotifyService) AddSubscriber(n Subscriber) error {
+ if s.Closed() {
+ return errors.New("server is shutting down")
}
- ns, ok := sr[n.Id()]
+ itf, ok := s.processors.Get(n.Type())
if !ok {
- ns = list.New()
+ return errors.New("Unknown subscribe type")
}
- ns.PushBack(n) // add a connection
- sr[n.Id()] = ns
-
n.SetService(s)
- s.mutexes[n.Type()].Unlock()
-
n.OnAccept()
+
+ itf.(*Processor).AddSubscriber(n)
return nil
}
func (s *NotifyService) RemoveSubscriber(n Subscriber) {
- s.mutexes[n.Type()].Lock()
- defer s.mutexes[n.Type()].Unlock()
- ss, ok := s.services[n.Type()]
- if !ok {
- return
- }
-
- m, ok := ss[n.Subject()]
- if !ok {
- return
- }
-
- ns, ok := m[n.Id()]
+ itf, ok := s.processors.Get(n.Type())
if !ok {
return
}
- for sr := ns.Front(); sr != nil; sr = sr.Next() {
- if sr.Value == n {
- ns.Remove(sr)
- n.Close()
- break
- }
- }
+ itf.(*Processor).Remove(n)
+ n.Close()
}
func (s *NotifyService) RemoveAllSubscribers() {
- for t, ss := range s.services {
- s.mutexes[t].Lock()
- for _, subscribers := range ss {
- for _, ns := range subscribers {
- for e, n := ns.Front(), ns.Front(); e != nil; e = n {
- e.Value.(Subscriber).Close()
- n = e.Next()
- ns.Remove(e)
- }
- }
- }
- s.mutexes[t].Unlock()
- }
+ s.processors.ForEach(func(item util.MapItem) (next bool) {
+ item.Value.(*Processor).Clear()
+ return true
+ })
}
//通知内容塞到队列里
@@ -143,109 +112,12 @@ func (s *NotifyService) AddJob(job NotifyJob) error {
return errors.New("add notify job failed for server shutdown")
}
- defer util.RecoverAndReport()
-
- timer := time.NewTimer(s.Config.AddTimeout)
- select {
- case s.queues[job.Type()] <- job:
- timer.Stop()
- return nil
- case <-timer.C:
- util.Logger().Errorf(nil, "Add job failed.%s")
- return errors.New("add notify job timeout")
- }
-}
-
-func (s *NotifyService) getPublish2SubscriberFunc(t NotifyType) func(context.Context) {
- return func(ctx context.Context) {
- defer s.waits.Done()
- for {
- select {
- case <-ctx.Done():
- return
- case job, ok := <-s.queues[t]:
- if !ok {
- return
- }
-
- util.Logger().Infof("notification service got a job %s: %s to notify subscriber %s",
- job.Type(), job.Subject(), job.SubscriberId())
-
- s.mutexes[t].Lock()
-
- if s.Closed() && len(s.services[t]) == 0 {
- s.mutexes[t].Unlock()
- return
- }
-
- m, ok := s.services[t][job.Subject()]
- if ok {
- // publish的subject如果带上id,则单播,否则广播
- if len(job.SubscriberId()) != 0 {
- ns, ok := m[job.SubscriberId()]
- if ok {
- for n := ns.Front(); n != nil; n = n.Next() {
- n.Value.(Subscriber).OnMessage(job)
- }
- }
- s.mutexes[t].Unlock()
- continue
- }
- for key := range m {
- ns := m[key]
- for n := ns.Front(); n != nil; n = n.Next() {
- n.Value.(Subscriber).OnMessage(job)
- }
- }
- }
-
- s.mutexes[t].Unlock()
- }
- }
- }
-}
-
-func (s *NotifyService) init() {
- if s.Config.AddTimeout <= 0 {
- s.Config.AddTimeout = DEFAULT_TIMEOUT
- }
- if s.Config.NotifyTimeout <= 0 {
- s.Config.NotifyTimeout = DEFAULT_TIMEOUT
- }
- if s.Config.MaxQueue <= 0 || s.Config.MaxQueue > DEFAULT_MAX_QUEUE {
- s.Config.MaxQueue = DEFAULT_MAX_QUEUE
- }
-
- s.services = make(serviceIndex, typeEnd)
- s.err = make(chan error, 1)
- s.queues = make(map[NotifyType]chan NotifyJob, typeEnd)
- s.mutexes = make(map[NotifyType]*sync.Mutex, typeEnd)
- for i := NotifyType(0); i != typeEnd; i++ {
- s.services[i] = make(subscriberSubjectIndex, DEFAULT_INIT_SUBSCRIBERS)
- s.queues[i] = make(chan NotifyJob, s.Config.MaxQueue)
- s.mutexes[i] = &sync.Mutex{}
- s.waits.Add(1)
- }
-}
-
-func (s *NotifyService) Start() {
- if !s.Closed() {
- util.Logger().Warnf(nil, "notify service is already running with config %s", s.Config)
- return
- }
- s.closeMux.Lock()
- s.isClose = false
- s.closeMux.Unlock()
-
- s.init()
- // 错误subscriber清理
- s.AddSubscriber(NewNotifyServiceHealthChecker())
-
- util.Logger().Debugf("notify service is started with config %s", s.Config)
-
- for i := NotifyType(0); i != typeEnd; i++ {
- s.goroutine.Do(s.getPublish2SubscriberFunc(i))
+ itf, ok := s.processors.Get(job.Type())
+ if !ok {
+ return errors.New("Unknown job type")
}
+ itf.(*Processor).Accept(job)
+ return nil
}
func (s *NotifyService) Closed() (b bool) {
@@ -263,17 +135,12 @@ func (s *NotifyService) Stop() {
s.isClose = true
s.closeMux.Unlock()
- for _, c := range s.queues {
- close(c)
- }
- s.waits.Wait()
+ s.goroutine.Close(true)
s.RemoveAllSubscribers()
close(s.err)
- s.goroutine.Close(true)
-
util.Logger().Debug("notify service stopped.")
}
diff --git a/server/service/notification/notification_test.go b/server/service/notification/notification_test.go
new file mode 100644
index 0000000..d7da51e
--- /dev/null
+++ b/server/service/notification/notification_test.go
@@ -0,0 +1,86 @@
+/*
+ * 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 notification
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "golang.org/x/net/context"
+ "testing"
+ "time"
+)
+
+func TestGetNotifyService(t *testing.T) {
+ notifyService := &NotifyService{
+ isClose: true,
+ goroutine: util.NewGo(context.Background()),
+ }
+ if notifyService == nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ if notifyService.Closed() != true {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+
+ err := notifyService.AddSubscriber(nil)
+ if err == nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ err = notifyService.AddJob(nil)
+ if err == nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+
+ notifyService.Start()
+ notifyService.Start()
+ if notifyService.Closed() != false {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ select {
+ case <-notifyService.Err():
+ t.Fatalf("TestGetNotifyService failed")
+ default:
+ }
+
+ s := NewSubscriber(-1, "s", "g")
+ err = notifyService.AddSubscriber(s)
+ if err == nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ s = NewSubscriber(INSTANCE, "s", "g")
+ err = notifyService.AddSubscriber(s)
+ if err != nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ j := &BaseNotifyJob{INSTANCE, "s", "g"}
+ err = notifyService.AddJob(j)
+ if err != nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ err = notifyService.AddJob(NewNotifyServiceHealthCheckJob(NewNotifyServiceHealthChecker()))
+ if err != nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ <-time.After(time.Second)
+ err = notifyService.AddJob(NewNotifyServiceHealthCheckJob(s))
+ if err != nil {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+ notifyService.Stop()
+ if notifyService.Closed() != true {
+ t.Fatalf("TestGetNotifyService failed")
+ }
+}
diff --git a/server/service/notification/processor.go b/server/service/notification/processor.go
new file mode 100644
index 0000000..7fe27f1
--- /dev/null
+++ b/server/service/notification/processor.go
@@ -0,0 +1,106 @@
+/*
+ * 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 notification
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "golang.org/x/net/context"
+)
+
+type Processor struct {
+ name string
+ subjects *util.ConcurrentMap
+ queue chan NotifyJob
+}
+
+func (p *Processor) Name() string {
+ return p.name
+}
+
+func (p *Processor) Notify(job NotifyJob) {
+ if itf, ok := p.subjects.Get(job.Subject()); ok {
+ itf.(*Subject).Notify(job)
+ }
+}
+
+func (p *Processor) Subjects(name string) *Subject {
+ itf, ok := p.subjects.Get(name)
+ if !ok {
+ return nil
+ }
+ return itf.(*Subject)
+}
+
+func (p *Processor) AddSubscriber(n Subscriber) {
+ item, _ := p.subjects.Fetch(n.Subject(), func() (interface{}, error) {
+ return NewSubject(n.Subject()), nil
+ })
+ item.(*Subject).GetOrNewGroup(n.Group()).AddSubscriber(n)
+}
+
+func (p *Processor) Remove(n Subscriber) {
+ itf, ok := p.subjects.Get(n.Subject())
+ if !ok {
+ return
+ }
+
+ s := itf.(*Subject)
+ g := s.Groups(n.Group())
+ if g == nil {
+ return
+ }
+
+ g.Remove(n.Id())
+
+ if g.Size() == 0 {
+ s.Remove(g.Name())
+ }
+ if s.Size() == 0 {
+ p.subjects.Remove(s.Name())
+ }
+}
+
+func (p *Processor) Clear() {
+ p.subjects.Clear()
+}
+
+func (p *Processor) Accept(job NotifyJob) {
+ defer util.RecoverAndReport()
+ p.queue <- job
+}
+
+func (p *Processor) Do(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case job, ok := <-p.queue:
+ if !ok {
+ return
+ }
+ p.Notify(job)
+ }
+ }
+}
+
+func NewProcessor(name string, queue int) *Processor {
+ return &Processor{
+ name: name,
+ subjects: util.NewConcurrentMap(0),
+ queue: make(chan NotifyJob, queue),
+ }
+}
diff --git a/server/service/notification/processor_test.go b/server/service/notification/processor_test.go
new file mode 100644
index 0000000..da4bd86
--- /dev/null
+++ b/server/service/notification/processor_test.go
@@ -0,0 +1,74 @@
+/*
+ * 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 notification
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "testing"
+)
+
+func TestProcessor_Do(t *testing.T) {
+ mock1 := &mockSubscriber{BaseSubscriber: NewSubscriber(INSTANCE, "s1", "g1")}
+ mock2 := &mockSubscriber{BaseSubscriber: NewSubscriber(INSTANCE, "s1", "g2")}
+ p := NewProcessor("p1", 0)
+ util.Go(p.Do)
+ if p.Name() != "p1" {
+ t.Fatalf("TestProcessor_Do")
+ }
+ if p.Subjects(mock1.Subject()) != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ p.AddSubscriber(mock1)
+ if p.Subjects(mock1.Subject()).Groups(mock1.Group()).Subscribers(mock1.Id()) != mock1 {
+ t.Fatalf("TestProcessor_Do")
+ }
+ p.Remove(NewSubscriber(INSTANCE, "s2", "g1"))
+ p.Remove(NewSubscriber(INSTANCE, "s1", "g2"))
+ p.Remove(mock1)
+ if p.Subjects(mock1.Subject()) != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ p.AddSubscriber(mock1)
+ p.Clear()
+ if p.Subjects(mock1.Subject()) != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ p.AddSubscriber(mock1)
+ job := &BaseNotifyJob{group: "g1"}
+ p.Accept(job)
+ if mock1.job != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ job.subject = "s1"
+ job.group = "g3"
+ p.Accept(job)
+ if mock1.job != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ job.subject = "s1"
+ job.group = "g1"
+ p.Accept(job)
+ if mock1.job != job || mock2.job != nil {
+ t.Fatalf("TestProcessor_Do")
+ }
+ job.subject = "s1"
+ job.group = ""
+ p.Accept(job)
+ if mock1.job != job && mock2.job != job {
+ t.Fatalf("TestProcessor_Do")
+ }
+}
diff --git a/server/service/notification/publisher.go b/server/service/notification/publisher.go
new file mode 100644
index 0000000..17bab45
--- /dev/null
+++ b/server/service/notification/publisher.go
@@ -0,0 +1,103 @@
+/*
+ * 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 notification
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ "golang.org/x/net/context"
+ "sync"
+ "time"
+)
+
+var publisher *Publisher
+
+func init() {
+ publisher = NewPublisher()
+ publisher.Run()
+}
+
+type Publisher struct {
+ wss []*WebSocket
+ lock sync.Mutex
+ goroutine *util.GoRoutine
+}
+
+func (wh *Publisher) Run() {
+ util.Go(publisher.loop)
+}
+
+func (wh *Publisher) Stop() {
+ wh.goroutine.Close(true)
+}
+
+func (wh *Publisher) dispatch(ws *WebSocket, payload interface{}) {
+ wh.goroutine.Do(func(ctx context.Context) {
+ ws.HandleWatchWebSocketJob(payload)
+ })
+}
+
+func (wh *Publisher) loop(ctx context.Context) {
+ defer wh.Stop()
+ ticker := time.NewTicker(500 * time.Millisecond)
+ for {
+ select {
+ case <-ctx.Done():
+ // server shutdown
+ return
+ case <-ticker.C:
+ var removes []int
+ for i, ws := range wh.wss {
+ if payload := ws.Pick(); payload != nil {
+ if _, ok := payload.(error); ok {
+ removes = append(removes, i)
+ }
+ wh.dispatch(ws, payload)
+ }
+ }
+ if len(removes) == 0 {
+ continue
+ }
+
+ wh.lock.Lock()
+ var (
+ news []*WebSocket
+ s int
+ )
+ for _, e := range removes {
+ news = append(news, wh.wss[s:e]...)
+ s = e + 1
+ }
+ if s < len(wh.wss) {
+ news = append(news, wh.wss[s:]...)
+ }
+ wh.wss = news
+ wh.lock.Unlock()
+ }
+ }
+}
+
+func (wh *Publisher) Accept(ws *WebSocket) {
+ wh.lock.Lock()
+ wh.wss = append(wh.wss, ws)
+ wh.lock.Unlock()
+}
+
+func NewPublisher() *Publisher {
+ return &Publisher{
+ goroutine: util.NewGo(context.Background()),
+ }
+}
diff --git a/server/service/notification/stream.go b/server/service/notification/stream.go
index 101473d..b9bbea2 100644
--- a/server/service/notification/stream.go
+++ b/server/service/notification/stream.go
@@ -23,32 +23,35 @@ import (
"time"
)
-func HandleWatchJob(watcher *ListWatcher, stream pb.ServiceInstanceCtrl_WatchServer, timeout time.Duration) (err error) {
+func HandleWatchJob(watcher *ListWatcher, stream pb.ServiceInstanceCtrl_WatchServer) (err error) {
+ timer := time.NewTimer(DEFAULT_SEND_TIMEOUT)
+ defer timer.Stop()
for {
- timer := time.NewTimer(timeout)
select {
case <-timer.C:
+ timer.Reset(DEFAULT_SEND_TIMEOUT)
+
// TODO grpc 长连接心跳?
case job := <-watcher.Job:
- timer.Stop()
-
if job == nil {
err = errors.New("channel is closed")
- util.Logger().Errorf(err, "watcher %s %s caught an exception",
- watcher.Subject(), watcher.Id())
+ util.Logger().Errorf(err, "watcher caught an exception, subject: %s, group: %s",
+ watcher.Subject(), watcher.Group())
return
}
resp := job.Response
- util.Logger().Infof("event is coming in, watcher %s %s",
- watcher.Subject(), watcher.Id())
+ util.Logger().Infof("event is coming in, watcher, subject: %s, group: %s",
+ watcher.Subject(), watcher.Group())
err = stream.Send(resp)
if err != nil {
- util.Logger().Errorf(err, "send message error, watcher %s %s",
- watcher.Subject(), watcher.Id())
+ util.Logger().Errorf(err, "send message error, subject: %s, group: %s",
+ watcher.Subject(), watcher.Group())
watcher.SetError(err)
return
}
+
+ util.ResetTimer(timer, DEFAULT_SEND_TIMEOUT)
}
}
}
diff --git a/server/service/util/schema_util_test.go b/server/service/notification/stream_test.go
similarity index 70%
copy from server/service/util/schema_util_test.go
copy to server/service/notification/stream_test.go
index 7ba7146..5193045 100644
--- a/server/service/util/schema_util_test.go
+++ b/server/service/notification/stream_test.go
@@ -14,22 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package util
+package notification
-import (
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
- "golang.org/x/net/context"
- "testing"
-)
+import "testing"
-func TestCheckSchemaInfoExist(t *testing.T) {
- _, err := CheckSchemaInfoExist(util.SetContext(context.Background(), CTX_CACHEONLY, "1"), "")
- if err != nil {
- t.FailNow()
- }
-
- _, err = CheckSchemaInfoExist(context.Background(), "")
+func TestHandleWatchJob(t *testing.T) {
+ defer func() { recover() }()
+ w := NewListWatcher("g", "s", nil)
+ w.Job <- nil
+ err := HandleWatchJob(w, nil)
if err == nil {
- t.FailNow()
+ t.Fatalf("TestHandleWatchJob failed")
+ }
+ w.Job <- NewWatchJob("g", "s", 1, nil)
+ err = HandleWatchJob(w, nil)
+ if err != nil {
+ t.Fatalf("TestHandleWatchJob failed")
}
}
diff --git a/server/service/notification/subject.go b/server/service/notification/subject.go
new file mode 100644
index 0000000..2e2ce57
--- /dev/null
+++ b/server/service/notification/subject.go
@@ -0,0 +1,76 @@
+/*
+ * 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 notification
+
+import (
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+)
+
+type Subject struct {
+ name string
+ groups *util.ConcurrentMap
+}
+
+func (s *Subject) Name() string {
+ return s.name
+}
+
+func (s *Subject) Notify(job NotifyJob) {
+ if len(job.Group()) == 0 {
+ s.groups.ForEach(func(item util.MapItem) (next bool) {
+ item.Value.(*Group).Notify(job)
+ return true
+ })
+ return
+ }
+
+ itf, ok := s.groups.Get(job.Group())
+ if !ok {
+ return
+ }
+ itf.(*Group).Notify(job)
+}
+
+func (s *Subject) Groups(name string) *Group {
+ g, ok := s.groups.Get(name)
+ if !ok {
+ return nil
+ }
+ return g.(*Group)
+}
+
+func (s *Subject) GetOrNewGroup(name string) *Group {
+ item, _ := s.groups.Fetch(name, func() (interface{}, error) {
+ return NewGroup(name), nil
+ })
+ return item.(*Group)
+}
+
+func (s *Subject) Remove(name string) {
+ s.groups.Remove(name)
+}
+
+func (s *Subject) Size() int {
+ return s.groups.Size()
+}
+
+func NewSubject(name string) *Subject {
+ return &Subject{
+ name: name,
+ groups: util.NewConcurrentMap(0),
+ }
+}
diff --git a/server/service/notification/subject_test.go b/server/service/notification/subject_test.go
new file mode 100644
index 0000000..293c500
--- /dev/null
+++ b/server/service/notification/subject_test.go
@@ -0,0 +1,65 @@
+/*
+ * 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 notification
+
+import "testing"
+
+func TestSubject_Fetch(t *testing.T) {
+ s := NewSubject("s1")
+ if s.Name() != "s1" {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ g := s.GetOrNewGroup("g1")
+ if g == nil {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ if s.GetOrNewGroup(g.Name()) != g {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ o := s.GetOrNewGroup("g2")
+ if s.Groups("g2") != o {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ if s.Size() != 2 {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ s.Remove(o.Name())
+ if s.Groups("g2") != nil {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ if s.Size() != 1 {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ mock1 := &mockSubscriber{BaseSubscriber: NewSubscriber(INSTANCE, "s1", "g1")}
+ mock2 := &mockSubscriber{BaseSubscriber: NewSubscriber(INSTANCE, "s1", "g2")}
+ g.AddSubscriber(mock1)
+ job := &BaseNotifyJob{group: "g3"}
+ s.Notify(job)
+ if mock1.job != nil || mock2.job != nil {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ job.group = "g1"
+ s.Notify(job)
+ if mock1.job != job || mock2.job != nil {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+ job.group = ""
+ s.Notify(job)
+ if mock1.job != job && mock2.job != job {
+ t.Fatalf("TestSubject_Fetch failed")
+ }
+}
diff --git a/server/service/notification/subscriber.go b/server/service/notification/subscriber.go
index 9f0b4df..ece7013 100644
--- a/server/service/notification/subscriber.go
+++ b/server/service/notification/subscriber.go
@@ -18,67 +18,54 @@ package notification
import (
"errors"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
)
type Subscriber interface {
- Err() error
- SetError(err error)
Id() string
Subject() string
+ Group() string
Type() NotifyType
Service() *NotifyService
SetService(*NotifyService)
+
+ Err() error
+ SetError(err error)
+
+ Close()
OnAccept()
// The event bus will callback this function, so it must be non-blocked.
OnMessage(job NotifyJob)
- Close()
}
type BaseSubscriber struct {
id string
subject string
+ group string
nType NotifyType
service *NotifyService
err error
}
-func (s *BaseSubscriber) Err() error {
- return s.err
-}
-
-func (s *BaseSubscriber) SetError(err error) {
- s.err = err
- // 触发清理job
- s.Service().AddJob(NewNotifyServiceHealthCheckJob(s))
-}
-
-func (s *BaseSubscriber) Id() string {
- return s.id
-}
-
-func (s *BaseSubscriber) Subject() string {
- return s.subject
-}
-
-func (s *BaseSubscriber) Type() NotifyType {
- return s.nType
-}
-
-func (s *BaseSubscriber) Service() *NotifyService {
- return s.service
-}
-
-func (s *BaseSubscriber) SetService(svc *NotifyService) {
- s.service = svc
-}
-
-func (s *BaseSubscriber) OnAccept() {
-}
-
+func (s *BaseSubscriber) Id() string { return s.id }
+func (s *BaseSubscriber) Subject() string { return s.subject }
+func (s *BaseSubscriber) Group() string { return s.group }
+func (s *BaseSubscriber) Type() NotifyType { return s.nType }
+func (s *BaseSubscriber) Service() *NotifyService { return s.service }
+func (s *BaseSubscriber) SetService(svc *NotifyService) { s.service = svc }
+func (s *BaseSubscriber) Err() error { return s.err }
+func (s *BaseSubscriber) SetError(err error) { s.err = err }
+func (s *BaseSubscriber) Close() {}
+func (s *BaseSubscriber) OnAccept() {}
func (s *BaseSubscriber) OnMessage(job NotifyJob) {
s.SetError(errors.New("do not call base notifier OnMessage method"))
}
-func (s *BaseSubscriber) Close() {
-
+func NewSubscriber(nType NotifyType, subject, group string) *BaseSubscriber {
+ return &BaseSubscriber{
+ id: util.GenerateUuid(),
+ group: group,
+ subject: subject,
+ nType: nType,
+ }
}
diff --git a/server/service/notification/websocket.go b/server/service/notification/websocket.go
index f53e641..362d7e2 100644
--- a/server/service/notification/websocket.go
+++ b/server/service/notification/websocket.go
@@ -28,221 +28,212 @@ import (
"time"
)
-type WebSocketHandler struct {
+type WebSocket struct {
ctx context.Context
+ ticker *time.Ticker
conn *websocket.Conn
watcher *ListWatcher
needPingWatcher bool
closed chan struct{}
- goroutine *util.GoRoutine
}
-func (wh *WebSocketHandler) Init() error {
+func (wh *WebSocket) Init() error {
+ wh.ticker = time.NewTicker(wh.Timeout())
+
remoteAddr := wh.conn.RemoteAddr().String()
+
+ // subscribe backend
if err := GetNotifyService().AddSubscriber(wh.watcher); err != nil {
err = fmt.Errorf("establish[%s] websocket watch failed: notify service error, %s",
remoteAddr, err.Error())
util.Logger().Errorf(nil, err.Error())
- err = wh.conn.WriteMessage(websocket.TextMessage, util.StringToBytesWithNoCopy(err.Error()))
- if err != nil {
- util.Logger().Errorf(err, "establish[%s] websocket watch failed: write message failed.", remoteAddr)
+ werr := wh.conn.WriteMessage(websocket.TextMessage, util.StringToBytesWithNoCopy(err.Error()))
+ if werr != nil {
+ util.Logger().Errorf(werr, "establish[%s] websocket watch failed: write message failed.", remoteAddr)
}
return err
}
- util.Logger().Debugf("start watching instance status, watcher[%s] %s %s",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
+
+ // publish events
+ publisher.Accept(wh)
+
+ util.Logger().Debugf("start watching instance status, watcher[%s], subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
return nil
}
-func (wh *WebSocketHandler) Timeout() time.Duration {
- return GetNotifyService().Config.NotifyTimeout
+func (wh *WebSocket) Timeout() time.Duration {
+ return DEFAULT_SEND_TIMEOUT
}
-func (wh *WebSocketHandler) websocketHeartbeat(messageType int) error {
+func (wh *WebSocket) heartbeat(messageType int) error {
err := wh.conn.WriteControl(messageType, []byte{}, time.Now().Add(wh.Timeout()))
if err != nil {
messageTypeName := "Ping"
if messageType == websocket.PongMessage {
messageTypeName = "Pong"
}
- util.Logger().Errorf(err, "fail to send '%s' to watcher[%s] %s %s", messageTypeName,
- wh.conn.RemoteAddr(), wh.watcher.Subject(), wh.watcher.Id())
+ util.Logger().Errorf(err, "fail to send '%s' to watcher[%s], subject: %s, group: %s",
+ messageTypeName, wh.conn.RemoteAddr(), wh.watcher.Subject(), wh.watcher.Group())
//wh.watcher.SetError(err)
return err
}
return nil
}
-func (wh *WebSocketHandler) HandleWatchWebSocketControlMessage(ctx context.Context) {
+func (wh *WebSocket) HandleWatchWebSocketControlMessage() {
defer close(wh.closed)
remoteAddr := wh.conn.RemoteAddr().String()
// PING
wh.conn.SetPingHandler(func(message string) error {
if wh.needPingWatcher {
- util.Logger().Infof("received 'Ping' message '%s' from watcher[%s] %s %s, no longer send 'Ping' to it",
- message, remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
+ util.Logger().Infof("received 'Ping' message '%s' from watcher[%s], no longer send 'Ping' to it, subject: %s, group: %s",
+ message, remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
}
wh.needPingWatcher = false
- return wh.websocketHeartbeat(websocket.PongMessage)
+ return wh.heartbeat(websocket.PongMessage)
})
// PONG
wh.conn.SetPongHandler(func(message string) error {
- util.Logger().Debugf("received 'Pong' message %s from watcher[%s] %s %s",
- message, remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
+ util.Logger().Debugf("received 'Pong' message '%s' from watcher[%s], subject: %s, group: %s",
+ message, remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
return nil
})
// CLOSE
wh.conn.SetCloseHandler(func(code int, text string) error {
- util.Logger().Warnf(nil, "watcher[%s] %s %s active closed", remoteAddr,
- wh.watcher.Subject(), wh.watcher.Id())
- return wh.Close(code, text)
+ util.Logger().Warnf(nil, "watcher[%s] active closed, subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
+ return wh.sendClose(code, text)
})
for {
- select {
- case <-ctx.Done():
+ _, _, err := wh.conn.ReadMessage()
+ if err != nil {
+ wh.watcher.SetError(err)
return
- default:
- _, _, err := wh.conn.ReadMessage()
- if err != nil {
- wh.watcher.SetError(err)
- return
- }
}
}
}
-func (wh *WebSocketHandler) HandleWatchWebSocketJob() {
- wh.goroutine.Do(wh.HandleWatchWebSocketControlMessage)
-
+func (wh *WebSocket) sendClose(code int, text string) error {
remoteAddr := wh.conn.RemoteAddr().String()
- for {
- timer := time.NewTimer(wh.Timeout())
- select {
- case <-wh.closed:
- timer.Stop()
- return
- case <-wh.ctx.Done():
- timer.Stop()
+ var message []byte
+ if code != websocket.CloseNoStatusReceived {
+ message = websocket.FormatCloseMessage(code, text)
+ }
+ err := wh.conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(wh.Timeout()))
+ if err != nil {
+ util.Logger().Errorf(err, "watcher[%s] catch an err, subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
+ return err
+ }
+ return nil
+}
- util.Logger().Warnf(nil, "handle timed out, watcher[%s] %s %s", remoteAddr,
- wh.watcher.Subject(), wh.watcher.Id())
- return
- case <-timer.C:
- if wh.watcher.Err() != nil {
- return
- }
-
- domainProject := util.ParseDomainProject(wh.ctx)
- if !serviceUtil.ServiceExist(context.Background(), domainProject, wh.watcher.Id()) {
- err := fmt.Errorf("Service does not exit.")
- util.Logger().Warnf(err, "watcher[%s] %s %s exit", remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- err = wh.conn.WriteMessage(websocket.TextMessage, util.StringToBytesWithNoCopy(err.Error()))
- if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: write message error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- }
- return
- }
-
- if !wh.needPingWatcher {
- continue
- }
-
- util.Logger().Debugf("send 'Ping' message to watcher[%s] %s %s", remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- err := wh.websocketHeartbeat(websocket.PingMessage)
- if err != nil {
- return
- }
- case job := <-wh.watcher.Job:
- timer.Stop()
-
- if wh.watcher.Err() != nil {
- return
- }
-
- if job == nil {
- err := wh.conn.WriteMessage(websocket.TextMessage,
- util.StringToBytesWithNoCopy("watcher catch an err: server shutdown"))
- if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: write message error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- return
- }
- util.Logger().Warnf(nil, "watcher[%s] %s %s catch an err: server shutdown",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- return
- }
-
- resp := job.Response
-
- providerFlag := fmt.Sprintf("%s/%s/%s", resp.Key.AppId, resp.Key.ServiceName, resp.Key.Version)
- if resp.Action != string(pb.EVT_EXPIRE) {
- providerFlag = fmt.Sprintf("%s/%s(%s)", resp.Instance.ServiceId, resp.Instance.InstanceId, providerFlag)
- }
- util.Logger().Infof("event[%s] is coming in, watcher[%s] %s %s, providers' info %s",
- resp.Action, remoteAddr, wh.watcher.Subject(), wh.watcher.Id(), providerFlag)
-
- resp.Response = nil
- data, err := json.Marshal(resp)
- if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: marshal output file error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- message := fmt.Sprintf("marshal output file error, %s", err.Error())
- err = wh.conn.WriteMessage(websocket.TextMessage, util.StringToBytesWithNoCopy(message))
- if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: write message error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- }
- return
- }
- err = wh.conn.WriteMessage(websocket.TextMessage, data)
- if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: write message error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- return
- }
+func (wh *WebSocket) Pick() interface{} {
+ if wh.watcher.Err() != nil {
+ return wh.watcher.Err()
+ }
+ select {
+ case t := <-wh.ticker.C:
+ return t
+ case j := <-wh.watcher.Job:
+ if j == nil {
+ return fmt.Errorf("server shutdown")
}
+ return j
+ default:
+ return nil
}
}
-func (wh *WebSocketHandler) Close(code int, text string) error {
- defer wh.goroutine.Close(true)
-
+func (wh *WebSocket) HandleWatchWebSocketJob(o interface{}) {
remoteAddr := wh.conn.RemoteAddr().String()
var message []byte
- if code != websocket.CloseNoStatusReceived {
- message = websocket.FormatCloseMessage(code, text)
+
+ switch o.(type) {
+ case error:
+ err := o.(error)
+ util.Logger().Warnf(err, "watcher[%s] catch an err, subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
+
+ message = util.StringToBytesWithNoCopy(fmt.Sprintf("watcher catch an err: %s", err.Error()))
+ case time.Time:
+ domainProject := util.ParseDomainProject(wh.ctx)
+ if !serviceUtil.ServiceExist(wh.ctx, domainProject, wh.watcher.Group()) {
+ err := fmt.Errorf("Service does not exit.")
+ wh.watcher.SetError(err)
+ message = util.StringToBytesWithNoCopy(err.Error())
+ break
+ }
+
+ if !wh.needPingWatcher {
+ return
+ }
+
+ util.Logger().Debugf("send 'Ping' message to watcher[%s], subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
+ err := wh.heartbeat(websocket.PingMessage)
+ if err != nil {
+ wh.watcher.SetError(err)
+ }
+ return
+ case *WatchJob:
+ job := o.(*WatchJob)
+ resp := job.Response
+
+ providerFlag := fmt.Sprintf("%s/%s/%s", resp.Key.AppId, resp.Key.ServiceName, resp.Key.Version)
+ if resp.Action != string(pb.EVT_EXPIRE) {
+ providerFlag = fmt.Sprintf("%s/%s(%s)", resp.Instance.ServiceId, resp.Instance.InstanceId, providerFlag)
+ }
+ util.Logger().Infof("event[%s] is coming in, watcher[%s] watch %s, subject: %s, group: %s",
+ resp.Action, remoteAddr, providerFlag, wh.watcher.Subject(), wh.watcher.Group())
+
+ resp.Response = nil
+ data, err := json.Marshal(resp)
+ if err != nil {
+ wh.watcher.SetError(err)
+ message = util.StringToBytesWithNoCopy(fmt.Sprintf("marshal output file error, %s", err.Error()))
+ break
+ }
+ message = data
+ default:
+ wh.watcher.SetError(fmt.Errorf("unknown input %v", o))
+ return
}
- err := wh.conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(wh.Timeout()))
+
+ select {
+ case <-wh.closed:
+ return
+ default:
+ }
+
+ err := wh.conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
- util.Logger().Errorf(err, "watcher[%s] %s %s catch an err: write 'Close' message error",
- remoteAddr, wh.watcher.Subject(), wh.watcher.Id())
- return err
+ util.Logger().Errorf(err, "watcher[%s] catch an err, subject: %s, group: %s",
+ remoteAddr, wh.watcher.Subject(), wh.watcher.Group())
}
- return nil
}
func DoWebSocketListAndWatch(ctx context.Context, serviceId string, f func() ([]*pb.WatchInstanceResponse, int64), conn *websocket.Conn) {
domainProject := util.ParseDomainProject(ctx)
- handler := &WebSocketHandler{
+ socket := &WebSocket{
ctx: ctx,
conn: conn,
watcher: NewListWatcher(serviceId, apt.GetInstanceRootKey(domainProject)+"/", f),
needPingWatcher: true,
closed: make(chan struct{}),
- goroutine: util.NewGo(context.Background()),
}
- processHandler(handler)
+ process(socket)
}
-func processHandler(handler *WebSocketHandler) {
- if err := handler.Init(); err != nil {
+func process(socket *WebSocket) {
+ if err := socket.Init(); err != nil {
return
}
- handler.HandleWatchWebSocketJob()
+ socket.HandleWatchWebSocketControlMessage()
}
func EstablishWebSocketError(conn *websocket.Conn, err error) {
diff --git a/server/service/notification/websocket_test.go b/server/service/notification/websocket_test.go
new file mode 100644
index 0000000..5b0a9f4
--- /dev/null
+++ b/server/service/notification/websocket_test.go
@@ -0,0 +1,120 @@
+/*
+ * 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 notification
+
+import (
+ "context"
+ "errors"
+ "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ _ "github.com/apache/incubator-servicecomb-service-center/server/plugin/infra/registry/buildin"
+ "github.com/gorilla/websocket"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+)
+
+var closeCh = make(chan struct{})
+
+type watcherConn struct {
+}
+
+func (h *watcherConn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var upgrader = websocket.Upgrader{}
+ conn, _ := upgrader.Upgrade(w, r, nil)
+ for {
+ conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second))
+ conn.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(time.Second))
+ _, _, err := conn.ReadMessage()
+ if err != nil {
+ return
+ }
+ <-closeCh
+ conn.WriteControl(websocket.CloseMessage, []byte{}, time.Now().Add(time.Second))
+ conn.Close()
+ return
+ }
+}
+
+func TestDoWebSocketListAndWatch(t *testing.T) {
+ s := httptest.NewServer(&watcherConn{})
+
+ GetNotifyService().Start()
+
+ conn, _, _ := websocket.DefaultDialer.Dial(
+ strings.Replace(s.URL, "http://", "ws://", 1), nil)
+
+ go DoWebSocketListAndWatch(context.Background(), "", nil, conn)
+
+ EstablishWebSocketError(conn, errors.New("error"))
+
+ w := NewListWatcher("g", "s", func() (results []*proto.WatchInstanceResponse, rev int64) {
+ return
+ })
+ w.nType = -1
+
+ ws := &WebSocket{
+ ctx: context.Background(),
+ conn: conn,
+ watcher: w,
+ needPingWatcher: true,
+ closed: make(chan struct{}),
+ }
+ err := ws.Init()
+ if err == nil {
+ t.Fatalf("TestPublisher_Run")
+ }
+
+ w.nType = INSTANCE
+ err = ws.Init()
+ if err != nil {
+ t.Fatalf("TestPublisher_Run")
+ }
+ go ws.HandleWatchWebSocketControlMessage()
+
+ w.SetError(errors.New("err"))
+ w.OnMessage(nil)
+ w.SetError(nil)
+ w.OnMessage(nil)
+ w.OnMessage(&WatchJob{})
+
+ GetNotifyService().AddJob(NewWatchJob("g", "s", 1, &proto.WatchInstanceResponse{
+ Response: proto.CreateResponse(proto.Response_SUCCESS, "ok"),
+ Action: string(proto.EVT_CREATE),
+ Key: &proto.MicroServiceKey{},
+ Instance: &proto.MicroServiceInstance{},
+ }))
+
+ <-time.After(time.Second)
+
+ ws.HandleWatchWebSocketJob(nil)
+
+ ws.heartbeat(websocket.PingMessage)
+ ws.heartbeat(websocket.PongMessage)
+
+ ws.HandleWatchWebSocketJob(time.Now())
+
+ closeCh <- struct{}{}
+
+ <-time.After(time.Second)
+
+ ws.heartbeat(websocket.PingMessage)
+ ws.heartbeat(websocket.PongMessage)
+
+ publisher.Stop()
+}
diff --git a/server/service/schema.go b/server/service/schema.go
index b59a38d..17b1f08 100644
--- a/server/service/schema.go
+++ b/server/service/schema.go
@@ -75,7 +75,7 @@ func (s *MicroServiceService) GetSchemaInfo(ctx context.Context, in *pb.GetSchem
return &pb.GetSchemaResponse{
Response: pb.CreateResponse(pb.Response_SUCCESS, "Get schema info successfully."),
- Schema: util.BytesToStringWithNoCopy(resp.Kvs[0].Value),
+ Schema: util.BytesToStringWithNoCopy(resp.Kvs[0].Value.([]byte)),
SchemaSummary: schemaSummary,
}, nil
}
@@ -123,7 +123,7 @@ func (s *MicroServiceService) GetAllSchemaInfo(ctx context.Context, in *pb.GetAl
}, errDo
}
- respWithSchema := ®istry.PluginResponse{}
+ respWithSchema := &backend.Response{}
if in.WithSchema {
key := apt.GenerateServiceSchemaKey(domainProject, in.ServiceId, "")
opts := append(serviceUtil.FromContext(ctx), registry.WithStrKey(key), registry.WithPrefix())
@@ -141,16 +141,16 @@ func (s *MicroServiceService) GetAllSchemaInfo(ctx context.Context, in *pb.GetAl
tempSchema := &pb.Schema{}
tempSchema.SchemaId = schemaId
for _, summarySchema := range resp.Kvs {
- schemaIdOfSummary, summaryData := pb.GetInfoFromSchemaSummaryKV(summarySchema)
+ schemaIdOfSummary := backend.GetInfoFromSchemaSummaryKV(summarySchema)
if schemaId == schemaIdOfSummary {
- tempSchema.Summary = util.BytesToStringWithNoCopy(summaryData)
+ tempSchema.Summary = summarySchema.Value.(string)
}
}
for _, contentSchema := range respWithSchema.Kvs {
- schemaIdOfSchema, schemaData := pb.GetInfoFromSchemaKV(contentSchema)
+ schemaIdOfSchema := backend.GetInfoFromSchemaKV(contentSchema)
if schemaId == schemaIdOfSchema {
- tempSchema.Schema = util.BytesToStringWithNoCopy(schemaData)
+ tempSchema.Schema = util.BytesToStringWithNoCopy(contentSchema.Value.([]byte))
}
}
schemas = append(schemas, tempSchema)
@@ -467,7 +467,7 @@ func GetSchemasFromDatabase(ctx context.Context, domainProject string, serviceId
key := util.BytesToStringWithNoCopy(kv.Key)
tmp := strings.Split(key, "/")
schemaId := tmp[len(tmp)-1]
- schema := util.BytesToStringWithNoCopy(kv.Value)
+ schema := util.BytesToStringWithNoCopy(kv.Value.([]byte))
schemaStruct := &pb.Schema{
SchemaId: schemaId,
Schema: schema,
@@ -672,5 +672,5 @@ func getSchemaSummary(ctx context.Context, domainProject string, serviceId strin
if len(resp.Kvs) == 0 {
return "", nil
}
- return util.BytesToStringWithNoCopy(resp.Kvs[0].Value), nil
+ return resp.Kvs[0].Value.(string), nil
}
diff --git a/server/service/tag_test.go b/server/service/tag_test.go
index cee8799..1ca9112 100644
--- a/server/service/tag_test.go
+++ b/server/service/tag_test.go
@@ -375,8 +375,7 @@ var _ = Describe("'Tag' service", func() {
VersionRule: "1.0.0+",
Tags: []string{"not-exist-tag"},
})
- Expect(findResp.Response.Code).To(Equal(pb.Response_SUCCESS))
- Expect(len(findResp.Instances)).To(Equal(0))
+ Expect(findResp.Response.Code).To(Equal(scerr.ErrServiceNotExists))
findResp, err = instanceResource.Find(getContext(), &pb.FindInstancesRequest{
ConsumerServiceId: consumerId,
@@ -394,7 +393,7 @@ var _ = Describe("'Tag' service", func() {
Rules: []*pb.AddOrUpdateServiceRule{
&pb.AddOrUpdateServiceRule{
RuleType: "WHITE",
- Attribute: "tag_filter_tag",
+ Attribute: "tag_consumer_tag",
Pattern: "f*",
Description: "test white",
},
@@ -410,12 +409,11 @@ var _ = Describe("'Tag' service", func() {
VersionRule: "1.0.0+",
Tags: []string{"filter_tag"},
})
- Expect(findResp.Response.Code).To(Equal(pb.Response_SUCCESS))
- Expect(len(findResp.Instances)).To(Equal(0))
+ Expect(findResp.Response.Code).To(Equal(scerr.ErrServiceNotExists))
addTagResp, err = serviceResource.AddTags(getContext(), &pb.AddServiceTagsRequest{
ServiceId: consumerId,
- Tags: map[string]string{"filter_tag": "filter"},
+ Tags: map[string]string{"consumer_tag": "filter"},
})
Expect(err).To(BeNil())
Expect(addTagResp.Response.Code).To(Equal(pb.Response_SUCCESS))
diff --git a/server/service/util/common.go b/server/service/util/common.go
index 9e31b3f..6c25e51 100644
--- a/server/service/util/common.go
+++ b/server/service/util/common.go
@@ -16,14 +16,10 @@
*/
package util
-import "time"
-
const (
HEADER_REV = "X-Resource-Revision"
CTX_NOCACHE = "noCache"
CTX_CACHEONLY = "cacheOnly"
CTX_REQUEST_REVISION = "requestRev"
CTX_RESPONSE_REVISION = "responseRev"
-
- cacheTTL = 5 * time.Minute
)
diff --git a/server/service/util/dependency.go b/server/service/util/dependency.go
index 4a5ed1b..f26f0f7 100644
--- a/server/service/util/dependency.go
+++ b/server/service/util/dependency.go
@@ -19,532 +19,14 @@ package util
import (
"encoding/json"
"errors"
- "fmt"
"github.com/apache/incubator-servicecomb-service-center/pkg/util"
apt "github.com/apache/incubator-servicecomb-service-center/server/core"
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
- scerr "github.com/apache/incubator-servicecomb-service-center/server/error"
"github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
"golang.org/x/net/context"
- "strings"
)
-func GetConsumersInCache(ctx context.Context, domainProject string, provider *pb.MicroService) ([]string, error) {
- // 查询所有consumer
- dr := NewProviderDependencyRelation(util.SetContext(util.CloneContext(ctx), CTX_CACHEONLY, "1"),
- domainProject, provider)
- consumerIds, err := dr.GetDependencyConsumerIds()
- if err != nil {
- util.Logger().Errorf(err, "Get dependency consumerIds failed.%s", provider.ServiceId)
- return nil, err
- }
- return consumerIds, nil
-}
-
-func GetProvidersInCache(ctx context.Context, domainProject string, consumer *pb.MicroService) ([]string, error) {
- // 查询所有provider
- dr := NewConsumerDependencyRelation(util.SetContext(util.CloneContext(ctx), CTX_CACHEONLY, "1"),
- domainProject, consumer)
- providerIds, err := dr.GetDependencyProviderIds()
- if err != nil {
- util.Logger().Errorf(err, "Get dependency providerIds failed.%s", consumer.ServiceId)
- return nil, err
- }
- return providerIds, nil
-}
-
-func GetConsumerIdsByProvider(ctx context.Context, domainProject string, provider *pb.MicroService) (allow []string, deny []string, _ error) {
- if provider == nil || len(provider.ServiceId) == 0 {
- return nil, nil, fmt.Errorf("invalid provider")
- }
-
- //todo 删除服务,最后实例推送有误差
- providerRules, err := GetRulesUtil(util.SetContext(util.CloneContext(ctx), CTX_CACHEONLY, "1"),
- domainProject, provider.ServiceId)
- if err != nil {
- return nil, nil, err
- }
- if len(providerRules) == 0 {
- return getConsumerIdsWithFilter(ctx, domainProject, provider, noFilter)
- }
-
- rf := RuleFilter{
- DomainProject: domainProject,
- Provider: provider,
- ProviderRules: providerRules,
- }
-
- allow, deny, err = getConsumerIdsWithFilter(ctx, domainProject, provider, rf.Filter)
- if err != nil {
- return nil, nil, err
- }
- return allow, deny, nil
-}
-
-func getConsumerIdsWithFilter(ctx context.Context, domainProject string, provider *pb.MicroService,
- filter func(ctx context.Context, consumerId string) (bool, error)) (allow []string, deny []string, err error) {
- consumerIds, err := GetConsumersInCache(ctx, domainProject, provider)
- if err != nil {
- return nil, nil, err
- }
- return filterConsumerIds(ctx, consumerIds, filter)
-}
-
-func filterConsumerIds(ctx context.Context, consumerIds []string,
- filter func(ctx context.Context, consumerId string) (bool, error)) (allow []string, deny []string, err error) {
- l := len(consumerIds)
- if l == 0 {
- return nil, nil, nil
- }
- allowIdx, denyIdx := 0, l
- consumers := make([]string, l)
- for _, consumerId := range consumerIds {
- ok, err := filter(ctx, consumerId)
- if err != nil {
- return nil, nil, err
- }
- if ok {
- consumers[allowIdx] = consumerId
- allowIdx++
- } else {
- denyIdx--
- consumers[denyIdx] = consumerId
- }
- }
- return consumers[:allowIdx], consumers[denyIdx:], nil
-}
-
-func noFilter(_ context.Context, _ string) (bool, error) {
- return true, nil
-}
-
-func GetProviderIdsByConsumer(ctx context.Context, domainProject string, service *pb.MicroService) (allow []string, deny []string, _ error) {
- providerIdsInCache, err := GetProvidersInCache(ctx, domainProject, service)
- if err != nil {
- return nil, nil, err
- }
- l := len(providerIdsInCache)
- rf := RuleFilter{
- DomainProject: domainProject,
- }
- allowIdx, denyIdx := 0, l
- providerIds := make([]string, l)
- copyCtx := util.SetContext(util.CloneContext(ctx), CTX_CACHEONLY, "1")
- for _, providerId := range providerIdsInCache {
- provider, err := GetService(ctx, domainProject, providerId)
- if provider == nil {
- continue
- }
- providerRules, err := GetRulesUtil(copyCtx, domainProject, provider.ServiceId)
- if err != nil {
- return nil, nil, err
- }
- if len(providerRules) == 0 {
- providerIds[allowIdx] = providerId
- allowIdx++
- continue
- }
- rf.Provider = provider
- rf.ProviderRules = providerRules
- ok, err := rf.Filter(ctx, service.ServiceId)
- if err != nil {
- return nil, nil, err
- }
- if ok {
- providerIds[allowIdx] = providerId
- allowIdx++
- } else {
- denyIdx--
- providerIds[denyIdx] = providerId
- }
- }
- return providerIds[:allowIdx], providerIds[denyIdx:], nil
-}
-
-func DependencyRuleExist(ctx context.Context, provider *pb.MicroServiceKey, consumer *pb.MicroServiceKey) (bool, error) {
- targetDomainProject := provider.Tenant
- if len(targetDomainProject) == 0 {
- targetDomainProject = consumer.Tenant
- }
-
- consumerKey := apt.GenerateConsumerDependencyRuleKey(consumer.Tenant, consumer)
- existed, err := dependencyRuleExistUtil(ctx, consumerKey, provider)
- if err != nil || existed {
- return existed, err
- }
-
- providerKey := apt.GenerateProviderDependencyRuleKey(targetDomainProject, provider)
- return dependencyRuleExistUtil(ctx, providerKey, consumer)
-}
-
-func dependencyRuleExistUtil(ctx context.Context, key string, target *pb.MicroServiceKey) (bool, error) {
- compareData, err := TransferToMicroServiceDependency(ctx, key)
- if err != nil {
- return false, err
- }
- if len(compareData.Dependency) != 0 {
- isEqual, err := containServiceDependency(compareData.Dependency, target)
- if err != nil {
- return false, err
- }
- if isEqual {
- //删除之前的依赖
- return true, nil
- }
- }
- return false, nil
-}
-
-func AddServiceVersionRule(ctx context.Context, domainProject string, consumer *pb.MicroService, provider *pb.MicroServiceKey) error {
- //创建依赖一致
- consumerKey := pb.MicroServiceToKey(domainProject, consumer)
- exist, err := DependencyRuleExist(ctx, provider, consumerKey)
- if exist || err != nil {
- return err
- }
-
- r := &pb.ConsumerDependency{
- Consumer: consumerKey,
- Providers: []*pb.MicroServiceKey{provider},
- Override: false,
- }
- data, err := json.Marshal(r)
- if err != nil {
- return err
- }
-
- id := util.StringJoin([]string{provider.AppId, provider.ServiceName}, "_")
- key := apt.GenerateConsumerDependencyQueueKey(domainProject, consumer.ServiceId, id)
- resp, err := backend.Registry().TxnWithCmp(ctx,
- nil,
- []registry.CompareOp{registry.OpCmp(registry.CmpStrVal(key), registry.CMP_EQUAL, util.BytesToStringWithNoCopy(data))},
- []registry.PluginOp{registry.OpPut(registry.WithStrKey(key), registry.WithValue(data))})
- if err != nil {
- return err
- }
- if !resp.Succeeded {
- util.Logger().Infof("find request into dependency queue successfully, %s: %v", key, r)
- }
- return nil
-}
-
-func TransferToMicroServiceDependency(ctx context.Context, key string) (*pb.MicroServiceDependency, error) {
- microServiceDependency := &pb.MicroServiceDependency{
- Dependency: []*pb.MicroServiceKey{},
- }
-
- opts := append(FromContext(ctx), registry.WithStrKey(key))
- res, err := backend.Store().DependencyRule().Search(ctx, opts...)
- if err != nil {
- util.Logger().Errorf(nil, "Get dependency rule failed.")
- return nil, err
- }
- if len(res.Kvs) != 0 {
- err = json.Unmarshal(res.Kvs[0].Value, microServiceDependency)
- if err != nil {
- util.Logger().Errorf(nil, "Unmarshal res failed.")
- return nil, err
- }
- }
- return microServiceDependency, nil
-}
-
-func updateProviderDependencyRuleUtil(consumersOfProvideRules *pb.MicroServiceDependency, consumer *pb.MicroServiceKey, providerRuleKey string) (registry.PluginOp, error) {
- for key, consumerInner := range consumersOfProvideRules.Dependency {
- if ok := equalServiceDependency(consumerInner, consumer); ok {
- consumersOfProvideRules.Dependency = append(consumersOfProvideRules.Dependency[:key], consumersOfProvideRules.Dependency[key+1:]...)
- break
- }
- }
- if len(consumersOfProvideRules.Dependency) == 0 {
- util.Logger().Infof("delete dependency rule key is %s", providerRuleKey)
- return registry.OpDel(registry.WithStrKey(providerRuleKey)), nil
- } else {
- data, err := json.Marshal(consumersOfProvideRules)
- if err != nil {
- util.Logger().Errorf(nil, "update dependency key Marshal tmpValue failed.")
- return registry.PluginOp{}, err
- }
- util.Logger().Infof("put provider's dependency rule, key is %s, value is %v.", providerRuleKey, consumersOfProvideRules)
- return registry.OpPut(registry.WithStrKey(providerRuleKey), registry.WithValue(data)), nil
- }
-}
-
-func equalServiceDependency(serviceA *pb.MicroServiceKey, serviceB *pb.MicroServiceKey) bool {
- stringA := toString(serviceA)
- stringB := toString(serviceB)
- if stringA == stringB {
- return true
- }
- return false
-}
-
-func diffServiceVersion(serviceA *pb.MicroServiceKey, serviceB *pb.MicroServiceKey) bool {
- stringA := toString(serviceA)
- stringB := toString(serviceB)
- if stringA != stringB &&
- stringA[:strings.LastIndex(stringA, "/")+1] == stringB[:strings.LastIndex(stringB, "/")+1] {
- return true
- }
- return false
-}
-
-func toString(in *pb.MicroServiceKey) string {
- return apt.GenerateProviderDependencyRuleKey(in.Tenant, in)
-}
-
-func parseAddOrUpdateRules(ctx context.Context, dep *Dependency) (newDependencyRuleList, existDependencyRuleList, deleteDependencyRuleList []*pb.MicroServiceKey) {
- conKey := apt.GenerateConsumerDependencyRuleKey(dep.DomainProject, dep.Consumer)
-
- oldProviderRules, err := TransferToMicroServiceDependency(ctx, conKey)
- if err != nil {
- util.Logger().Errorf(err, "maintain dependency rule failed, consumer %s/%s/%s: get consumer depedency rule failed.",
- dep.Consumer.AppId, dep.Consumer.ServiceName, dep.Consumer.Version)
- return
- }
-
- deleteDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(oldProviderRules.Dependency))
- newDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(dep.ProvidersRule))
- existDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(oldProviderRules.Dependency))
- for _, tmpProviderRule := range dep.ProvidersRule {
- if ok, _ := containServiceDependency(oldProviderRules.Dependency, tmpProviderRule); ok {
- continue
- }
-
- if tmpProviderRule.ServiceName == "*" {
- newDependencyRuleList = append([]*pb.MicroServiceKey{}, tmpProviderRule)
- deleteDependencyRuleList = oldProviderRules.Dependency
- break
- }
-
- newDependencyRuleList = append(newDependencyRuleList, tmpProviderRule)
- old := isNeedUpdate(oldProviderRules.Dependency, tmpProviderRule)
- if old != nil {
- deleteDependencyRuleList = append(deleteDependencyRuleList, old)
- }
- }
- for _, oldProviderRule := range oldProviderRules.Dependency {
- if oldProviderRule.ServiceName == "*" {
- newDependencyRuleList = nil
- deleteDependencyRuleList = nil
- return
- }
- if ok, _ := containServiceDependency(deleteDependencyRuleList, oldProviderRule); !ok {
- existDependencyRuleList = append(existDependencyRuleList, oldProviderRule)
- }
- }
-
- dep.ProvidersRule = append(newDependencyRuleList, existDependencyRuleList...)
- return
-}
-
-func parseOverrideRules(ctx context.Context, dep *Dependency) (newDependencyRuleList, existDependencyRuleList, deleteDependencyRuleList []*pb.MicroServiceKey) {
- conKey := apt.GenerateConsumerDependencyRuleKey(dep.DomainProject, dep.Consumer)
-
- oldProviderRules, err := TransferToMicroServiceDependency(ctx, conKey)
- if err != nil {
- util.Logger().Errorf(err, "maintain dependency rule failed, consumer %s/%s/%s: get consumer depedency rule failed.",
- dep.Consumer.AppId, dep.Consumer.ServiceName, dep.Consumer.Version)
- return
- }
-
- deleteDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(oldProviderRules.Dependency))
- newDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(dep.ProvidersRule))
- existDependencyRuleList = make([]*pb.MicroServiceKey, 0, len(oldProviderRules.Dependency))
- for _, oldProviderRule := range oldProviderRules.Dependency {
- if ok, _ := containServiceDependency(dep.ProvidersRule, oldProviderRule); !ok {
- deleteDependencyRuleList = append(deleteDependencyRuleList, oldProviderRule)
- } else {
- existDependencyRuleList = append(existDependencyRuleList, oldProviderRule)
- }
- }
- for _, tmpProviderRule := range dep.ProvidersRule {
- if ok, _ := containServiceDependency(existDependencyRuleList, tmpProviderRule); !ok {
- newDependencyRuleList = append(newDependencyRuleList, tmpProviderRule)
- }
- }
- return
-}
-
-func syncDependencyRule(ctx context.Context, dep *Dependency, filter func(context.Context, *Dependency) (_, _, _ []*pb.MicroServiceKey)) error {
- //更新consumer的providers的值,consumer的版本是确定的
- consumerFlag := strings.Join([]string{dep.Consumer.AppId, dep.Consumer.ServiceName, dep.Consumer.Version}, "/")
-
- newDependencyRuleList, existDependencyRuleList, deleteDependencyRuleList := filter(ctx, dep)
- if len(newDependencyRuleList) == 0 && len(existDependencyRuleList) == 0 && len(deleteDependencyRuleList) == 0 {
- return nil
- }
-
- dep.err = make(chan error, 5)
- dep.chanNum = 0
- if len(deleteDependencyRuleList) != 0 {
- util.Logger().Infof("Delete dependency rule remove for consumer %s, %v, ", consumerFlag, deleteDependencyRuleList)
- dep.removedDependencyRuleList = deleteDependencyRuleList
- dep.RemoveConsumerOfProviderRule()
- }
-
- if len(newDependencyRuleList) != 0 {
- util.Logger().Infof("New dependency rule add for consumer %s, %v, ", consumerFlag, newDependencyRuleList)
- dep.NewDependencyRuleList = newDependencyRuleList
- dep.AddConsumerOfProviderRule()
- }
-
- conKey := apt.GenerateConsumerDependencyRuleKey(dep.DomainProject, dep.Consumer)
- err := dep.UpdateProvidersRuleOfConsumer(conKey)
- if err != nil {
- return err
- }
-
- if dep.chanNum != 0 {
- for tmpErr := range dep.err {
- dep.chanNum--
- if tmpErr != nil {
- return tmpErr
- }
- if 0 == dep.chanNum {
- close(dep.err)
- }
- }
- }
- return nil
-}
-
-func AddDependencyRule(ctx context.Context, dep *Dependency) error {
- return syncDependencyRule(ctx, dep, parseAddOrUpdateRules)
-}
-
-func CreateDependencyRule(ctx context.Context, dep *Dependency) error {
- return syncDependencyRule(ctx, dep, parseOverrideRules)
-}
-
-func isDependencyAll(dep *pb.MicroServiceDependency) bool {
- for _, serviceDep := range dep.Dependency {
- if serviceDep.ServiceName == "*" {
- return true
- }
- }
- return false
-}
-
-func isExist(services []*pb.MicroServiceKey, service *pb.MicroServiceKey) bool {
- for _, tmp := range services {
- if equalServiceDependency(tmp, service) {
- return true
- }
- }
- return false
-}
-
-func deleteConsumerDepOfProviderRule(ctx context.Context, domainProject string, providerRule *pb.MicroServiceKey, deleteConsumer *pb.MicroServiceKey) (registry.PluginOp, error) {
- proKey := apt.GenerateProviderDependencyRuleKey(domainProject, providerRule)
- consumerDepRules, err := TransferToMicroServiceDependency(ctx, proKey)
- if err != nil {
- return registry.PluginOp{}, err
- }
- return deleteDepRuleUtil(proKey, consumerDepRules, deleteConsumer)
-}
-
-func deleteDepRuleUtil(key string, deps *pb.MicroServiceDependency, deleteDepRule *pb.MicroServiceKey) (registry.PluginOp, error) {
- for key, consumerDepRule := range deps.Dependency {
- if equalServiceDependency(consumerDepRule, deleteDepRule) {
- deps.Dependency = append(deps.Dependency[:key], deps.Dependency[key+1:]...)
- break
- }
- }
- data, err := json.Marshal(deps)
- if err != nil {
- return registry.PluginOp{}, err
- }
- return registry.OpPut(registry.WithStrKey(key), registry.WithValue(data)), nil
-}
-
-func addDepRuleUtil(key string, deps *pb.MicroServiceDependency, updateDepRule *pb.MicroServiceKey) (registry.PluginOp, error) {
- deps.Dependency = append(deps.Dependency, updateDepRule)
- data, err := json.Marshal(deps)
- if err != nil {
- util.Logger().Errorf(err, "marshal consumerDepRules failed for delete consumer rule from provider rule's dep.")
- return registry.PluginOp{}, err
- }
- return registry.OpPut(registry.WithStrKey(key), registry.WithValue(data)), nil
-}
-
-func updateDepRuleUtil(key string, deps *pb.MicroServiceDependency, updateDepRule *pb.MicroServiceKey) (string, registry.PluginOp, error) {
- oldRule := ""
- for _, serviceRule := range deps.Dependency {
- if diffServiceVersion(serviceRule, updateDepRule) {
- oldRule = serviceRule.Version
- serviceRule.Version = updateDepRule.Version
- break
- }
- }
- data, err := json.Marshal(deps)
- if err != nil {
- return oldRule, registry.PluginOp{}, err
- }
- return oldRule, registry.OpPut(registry.WithStrKey(key), registry.WithValue(data)), nil
-}
-
-func isNeedUpdate(services []*pb.MicroServiceKey, service *pb.MicroServiceKey) *pb.MicroServiceKey {
- for _, tmp := range services {
- if diffServiceVersion(tmp, service) {
- return tmp
- }
- }
- return nil
-}
-
-func containServiceDependency(services []*pb.MicroServiceKey, service *pb.MicroServiceKey) (bool, error) {
- if services == nil || service == nil {
- return false, errors.New("Invalid params input.")
- }
- for _, value := range services {
- rst := equalServiceDependency(service, value)
- if rst {
- return true, nil
- }
- }
- return false, nil
-}
-
-func BadParamsResponse(detailErr string) *pb.CreateDependenciesResponse {
- util.Logger().Errorf(nil, "Request params is invalid. %s", detailErr)
- if len(detailErr) == 0 {
- detailErr = "Request params is invalid."
- }
- return &pb.CreateDependenciesResponse{
- Response: pb.CreateResponse(scerr.ErrInvalidParams, detailErr),
- }
-}
-
-func ParamsChecker(consumerInfo *pb.MicroServiceKey, providersInfo []*pb.MicroServiceKey) *pb.CreateDependenciesResponse {
- flag := make(map[string]bool, len(providersInfo))
- for _, providerInfo := range providersInfo {
- //存在带*的情况,后面的数据就不校验了
- if providerInfo.ServiceName == "*" {
- util.Logger().Debugf("%s 's provider contains *.", consumerInfo.ServiceName)
- break
- }
- if len(providerInfo.AppId) == 0 {
- providerInfo.AppId = consumerInfo.AppId
- }
-
- version := providerInfo.Version
- if len(version) == 0 {
- return BadParamsResponse("Required provider version")
- }
-
- providerInfo.Version = ""
- if _, ok := flag[toString(providerInfo)]; ok {
- return BadParamsResponse("Invalid request body for provider info.Duplicate provider or (serviceName and appId is same).")
- } else {
- flag[toString(providerInfo)] = true
- }
- providerInfo.Version = version
- }
- return nil
-}
-
type Dependency struct {
ConsumerId string
DomainProject string
@@ -673,352 +155,3 @@ func (dep *Dependency) UpdateProvidersRuleOfConsumer(conKey string) error {
}
return nil
}
-
-type DependencyRelation struct {
- ctx context.Context
- domainProject string
- consumer *pb.MicroService
- provider *pb.MicroService
-}
-
-func NewProviderDependencyRelation(ctx context.Context, domainProject string, provider *pb.MicroService) *DependencyRelation {
- return NewDependencyRelation(ctx, domainProject, nil, provider)
-}
-
-func NewConsumerDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService) *DependencyRelation {
- return NewDependencyRelation(ctx, domainProject, consumer, nil)
-}
-
-func NewDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService, provider *pb.MicroService) *DependencyRelation {
- return &DependencyRelation{
- ctx: ctx,
- domainProject: domainProject,
- consumer: consumer,
- provider: provider,
- }
-}
-
-func (dr *DependencyRelation) GetDependencyProviders(opts ...DependencyRelationFilterOption) ([]*pb.MicroService, error) {
- keys, err := dr.getProviderKeys()
- if err != nil {
- return nil, err
- }
- services := make([]*pb.MicroService, 0, len(keys))
- op := toDependencyRelationFilterOpt(opts...)
- for _, key := range keys {
- if op.SameDomainProject && key.Tenant != dr.domainProject {
- continue
- }
-
- providerIds, err := dr.parseDependencyRule(key)
- if err != nil {
- return nil, err
- }
-
- if key.ServiceName == "*" {
- services = services[:0]
- }
-
- for _, providerId := range providerIds {
- provider, err := GetService(dr.ctx, key.Tenant, providerId)
- if err != nil {
- util.Logger().Warnf(nil, "Provider does not exist, %s/%s/%s",
- key.AppId, key.ServiceName, key.Version)
- continue
- }
- if provider == nil {
- continue
- }
- if op.NonSelf && providerId == dr.consumer.ServiceId {
- continue
- }
- services = append(services, provider)
- }
-
- if key.ServiceName == "*" {
- break
- }
- }
- return services, nil
-}
-
-func (dr *DependencyRelation) GetDependencyProviderIds() ([]string, error) {
- keys, err := dr.getProviderKeys()
- if err != nil {
- return nil, err
- }
- return dr.getDependencyProviderIds(keys)
-}
-
-func (dr *DependencyRelation) getProviderKeys() ([]*pb.MicroServiceKey, error) {
- if dr.consumer == nil {
- util.LOGGER.Infof("dr.consumer is nil ------->")
- return nil, fmt.Errorf("Invalid consumer")
- }
- consumerMicroServiceKey := pb.MicroServiceToKey(dr.domainProject, dr.consumer)
-
- conKey := apt.GenerateConsumerDependencyRuleKey(dr.domainProject, consumerMicroServiceKey)
- consumerDependency, err := TransferToMicroServiceDependency(dr.ctx, conKey)
- if err != nil {
- return nil, err
- }
- return consumerDependency.Dependency, nil
-}
-
-func (dr *DependencyRelation) getDependencyProviderIds(providerRules []*pb.MicroServiceKey) ([]string, error) {
- provideServiceIds := make([]string, 0, len(providerRules))
- for _, provider := range providerRules {
- serviceIds, err := dr.parseDependencyRule(provider)
- switch {
- case provider.ServiceName == "*":
- if err != nil {
- util.Logger().Errorf(err, "Add dependency failed, rely all service: get all services failed.")
- return provideServiceIds, err
- }
- return serviceIds, nil
- default:
- if err != nil {
- util.Logger().Errorf(err, "Get providerIds failed, service: %s/%s/%s",
- provider.AppId, provider.ServiceName, provider.Version)
- return provideServiceIds, err
- }
- if len(serviceIds) == 0 {
- util.Logger().Warnf(nil, "Get providerIds is empty, service: %s/%s/%s does not exist",
- provider.AppId, provider.ServiceName, provider.Version)
- continue
- }
- provideServiceIds = append(provideServiceIds, serviceIds...)
- }
- }
- return provideServiceIds, nil
-}
-
-func (dr *DependencyRelation) parseDependencyRule(dependencyRule *pb.MicroServiceKey) (serviceIds []string, err error) {
- opts := FromContext(dr.ctx)
- switch {
- case dependencyRule.ServiceName == "*":
- util.Logger().Infof("Rely all service, * type, consumerId %s", dr.consumer.ServiceId)
- splited := strings.Split(apt.GenerateServiceIndexKey(dependencyRule), "/")
- allServiceKey := util.StringJoin(splited[:len(splited)-3], "/") + "/"
- sopts := append(opts,
- registry.WithStrKey(allServiceKey),
- registry.WithPrefix())
- resp, err := backend.Store().Service().Search(dr.ctx, sopts...)
- if err != nil {
- return nil, err
- }
-
- for _, kv := range resp.Kvs {
- serviceIds = append(serviceIds, util.BytesToStringWithNoCopy(kv.Value))
- }
- default:
- serviceIds, err = FindServiceIds(dr.ctx, dependencyRule.Version, dependencyRule)
- }
- return
-}
-
-func (dr *DependencyRelation) GetDependencyConsumers(opts ...DependencyRelationFilterOption) ([]*pb.MicroService, error) {
- consumerDependAllList, err := dr.getDependencyConsumersOfProvider()
- if err != nil {
- util.Logger().Errorf(err, "Get consumers of provider rule failed, %s", dr.provider.ServiceId)
- return nil, err
- }
- consumers := make([]*pb.MicroService, 0)
- op := toDependencyRelationFilterOpt(opts...)
- for _, consumer := range consumerDependAllList {
- if op.SameDomainProject && consumer.Tenant != dr.domainProject {
- continue
- }
-
- service, err := dr.getServiceByMicroServiceKey(consumer)
- if err != nil {
- return nil, err
- }
- if service == nil {
- util.Logger().Warnf(nil, "Consumer does not exist, %v", consumer)
- continue
- }
-
- if op.NonSelf && service.ServiceId == dr.provider.ServiceId {
- continue
- }
-
- consumers = append(consumers, service)
- }
- return consumers, nil
-}
-
-func (dr *DependencyRelation) getServiceByMicroServiceKey(service *pb.MicroServiceKey) (*pb.MicroService, error) {
- serviceId, err := GetServiceId(dr.ctx, service)
- if err != nil {
- return nil, err
- }
- if len(serviceId) == 0 {
- util.Logger().Warnf(nil, "Service not exist,%v", service)
- return nil, nil
- }
- return GetService(dr.ctx, service.Tenant, serviceId)
-}
-
-func (dr *DependencyRelation) GetDependencyConsumerIds() ([]string, error) {
- consumerDependAllList, err := dr.getDependencyConsumersOfProvider()
- if err != nil {
- return nil, err
- }
- consumerIds := make([]string, 0, len(consumerDependAllList))
- for _, consumer := range consumerDependAllList {
- consumerId, err := GetServiceId(dr.ctx, consumer)
- if err != nil {
- util.Logger().Errorf(err, "Get consumer failed, %v", consumer)
- return nil, err
- }
- if len(consumerId) == 0 {
- util.Logger().Warnf(nil, "Get consumer not exist, %v", consumer)
- continue
- }
- consumerIds = append(consumerIds, consumerId)
- }
- return consumerIds, nil
-
-}
-
-func (dr *DependencyRelation) getDependencyConsumersOfProvider() ([]*pb.MicroServiceKey, error) {
- if dr.provider == nil {
- util.LOGGER.Infof("dr.provider is nil ------->")
- return nil, fmt.Errorf("Invalid provider")
- }
- providerService := pb.MicroServiceToKey(dr.domainProject, dr.provider)
- consumerDependAllList, err := dr.getConsumerOfDependAllServices()
- if err != nil {
- util.Logger().Errorf(err, "Get consumer that depend on all services failed, %s", dr.provider.ServiceId)
- return nil, err
- }
-
- consumerDependList, err := dr.getConsumerOfSameServiceNameAndAppId(providerService)
- if err != nil {
- util.Logger().Errorf(err, "Get consumer that depend on same serviceName and appid rule failed, %s",
- dr.provider.ServiceId)
- return nil, err
- }
- consumerDependAllList = append(consumerDependAllList, consumerDependList...)
- return consumerDependAllList, nil
-}
-
-func (dr *DependencyRelation) getConsumerOfDependAllServices() ([]*pb.MicroServiceKey, error) {
- providerService := pb.MicroServiceToKey(dr.domainProject, dr.provider)
- providerService.ServiceName = "*"
- relyAllKey := apt.GenerateProviderDependencyRuleKey(dr.domainProject, providerService)
- opts := append(FromContext(dr.ctx), registry.WithStrKey(relyAllKey))
- rsp, err := backend.Store().DependencyRule().Search(dr.ctx, opts...)
- if err != nil {
- util.Logger().Errorf(err, "get consumer that rely all service failed.")
- return nil, err
- }
- dependency := &pb.MicroServiceDependency{}
- if len(rsp.Kvs) != 0 {
- util.Logger().Infof("consumer that rely all service exist.ServiceName: %s.", dr.provider.ServiceName)
- err = json.Unmarshal(rsp.Kvs[0].Value, dependency)
- if err != nil {
- return nil, err
- }
- return dependency.Dependency, nil
- }
- return dependency.Dependency, nil
-}
-
-func (dr *DependencyRelation) getConsumerOfSameServiceNameAndAppId(provider *pb.MicroServiceKey) ([]*pb.MicroServiceKey, error) {
- providerVersion := provider.Version
- provider.Version = ""
- prefix := apt.GenerateProviderDependencyRuleKey(dr.domainProject, provider)
- provider.Version = providerVersion
-
- opts := append(FromContext(dr.ctx),
- registry.WithStrKey(prefix),
- registry.WithPrefix())
- rsp, err := backend.Store().DependencyRule().Search(dr.ctx, opts...)
- if err != nil {
- util.Logger().Errorf(err, "get all dependency rule failed: provider rule key %v.", provider)
- return nil, err
- }
-
- allConsumers := make([]*pb.MicroServiceKey, 0, len(rsp.Kvs))
- var latestServiceId []string
-
- for _, kv := range rsp.Kvs {
- dependency := &pb.MicroServiceDependency{
- Dependency: []*pb.MicroServiceKey{},
- }
- providerVersionRuleArr := strings.Split(util.BytesToStringWithNoCopy(kv.Key), "/")
- providerVersionRule := providerVersionRuleArr[len(providerVersionRuleArr)-1]
- if providerVersionRule == "latest" {
- if latestServiceId == nil {
- latestServiceId, err = FindServiceIds(dr.ctx, providerVersionRule, provider)
- if err != nil {
- util.Logger().Errorf(err, "Get latest service failed.")
- return nil, err
- }
- }
- if len(latestServiceId) == 0 {
- util.Logger().Infof("%s 's providerId is empty,no this service.", provider.ServiceName)
- continue
- }
- if dr.provider.ServiceId != latestServiceId[0] {
- continue
- }
-
- } else {
- if !VersionMatchRule(providerVersion, providerVersionRule) {
- continue
- }
- }
-
- util.Logger().Debugf("providerETCD is %s", providerVersionRuleArr)
- err = json.Unmarshal(kv.Value, dependency)
- if err != nil {
- util.Logger().Errorf(err, "Unmarshal consumers failed.")
- return nil, err
- }
- allConsumers = append(allConsumers, dependency.Dependency...)
- }
- return allConsumers, nil
-}
-
-func DeleteDependencyForDeleteService(domainProject string, serviceId string, service *pb.MicroServiceKey) (registry.PluginOp, error) {
- key := apt.GenerateConsumerDependencyQueueKey(domainProject, serviceId, "0")
- conDep := new(pb.ConsumerDependency)
- conDep.Consumer = service
- conDep.Providers = []*pb.MicroServiceKey{}
- conDep.Override = true
- data, err := json.Marshal(conDep)
- if err != nil {
- return registry.PluginOp{}, err
- }
- return registry.OpPut(registry.WithStrKey(key), registry.WithValue(data)), nil
-}
-
-type DependencyRelationFilterOpt struct {
- SameDomainProject bool
- NonSelf bool
-}
-
-type DependencyRelationFilterOption func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt
-
-func WithSameDomainProject() DependencyRelationFilterOption {
- return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt {
- opt.SameDomainProject = true
- return opt
- }
-}
-func WithoutSelfDependency() DependencyRelationFilterOption {
- return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt {
- opt.NonSelf = true
- return opt
- }
-}
-
-func toDependencyRelationFilterOpt(opts ...DependencyRelationFilterOption) (op DependencyRelationFilterOpt) {
- for _, opt := range opts {
- op = opt(op)
- }
- return
-}
diff --git a/server/service/util/dependency_query.go b/server/service/util/dependency_query.go
new file mode 100644
index 0000000..343e9fa
--- /dev/null
+++ b/server/service/util/dependency_query.go
@@ -0,0 +1,351 @@
+/*
+ * 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 util
+
+import (
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ apt "github.com/apache/incubator-servicecomb-service-center/server/core"
+ "github.com/apache/incubator-servicecomb-service-center/server/core/backend"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ "github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
+ "golang.org/x/net/context"
+ "strings"
+)
+
+type DependencyRelationFilterOpt struct {
+ SameDomainProject bool
+ NonSelf bool
+}
+
+type DependencyRelationFilterOption func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt
+
+func WithSameDomainProject() DependencyRelationFilterOption {
+ return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt {
+ opt.SameDomainProject = true
+ return opt
+ }
+}
+func WithoutSelfDependency() DependencyRelationFilterOption {
+ return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt {
+ opt.NonSelf = true
+ return opt
+ }
+}
+
+func toDependencyRelationFilterOpt(opts ...DependencyRelationFilterOption) (op DependencyRelationFilterOpt) {
+ for _, opt := range opts {
+ op = opt(op)
+ }
+ return
+}
+
+type DependencyRelation struct {
+ ctx context.Context
+ domainProject string
+ consumer *pb.MicroService
+ provider *pb.MicroService
+}
+
+func (dr *DependencyRelation) GetDependencyProviders(opts ...DependencyRelationFilterOption) ([]*pb.MicroService, error) {
+ keys, err := dr.getProviderKeys()
+ if err != nil {
+ return nil, err
+ }
+ services := make([]*pb.MicroService, 0, len(keys))
+ op := toDependencyRelationFilterOpt(opts...)
+ for _, key := range keys {
+ if op.SameDomainProject && key.Tenant != dr.domainProject {
+ continue
+ }
+
+ providerIds, err := dr.parseDependencyRule(key)
+ if err != nil {
+ return nil, err
+ }
+
+ if key.ServiceName == "*" {
+ services = services[:0]
+ }
+
+ for _, providerId := range providerIds {
+ provider, err := GetService(dr.ctx, key.Tenant, providerId)
+ if err != nil {
+ util.Logger().Warnf(nil, "Provider does not exist, %s/%s/%s",
+ key.AppId, key.ServiceName, key.Version)
+ continue
+ }
+ if provider == nil {
+ continue
+ }
+ if op.NonSelf && providerId == dr.consumer.ServiceId {
+ continue
+ }
+ services = append(services, provider)
+ }
+
+ if key.ServiceName == "*" {
+ break
+ }
+ }
+ return services, nil
+}
+
+func (dr *DependencyRelation) GetDependencyProviderIds() ([]string, error) {
+ keys, err := dr.getProviderKeys()
+ if err != nil {
+ return nil, err
+ }
+ return dr.getDependencyProviderIds(keys)
+}
+
+func (dr *DependencyRelation) getProviderKeys() ([]*pb.MicroServiceKey, error) {
+ if dr.consumer == nil {
+ util.LOGGER.Infof("dr.consumer is nil ------->")
+ return nil, fmt.Errorf("Invalid consumer")
+ }
+ consumerMicroServiceKey := pb.MicroServiceToKey(dr.domainProject, dr.consumer)
+
+ conKey := apt.GenerateConsumerDependencyRuleKey(dr.domainProject, consumerMicroServiceKey)
+ consumerDependency, err := TransferToMicroServiceDependency(dr.ctx, conKey)
+ if err != nil {
+ return nil, err
+ }
+ return consumerDependency.Dependency, nil
+}
+
+func (dr *DependencyRelation) getDependencyProviderIds(providerRules []*pb.MicroServiceKey) ([]string, error) {
+ provideServiceIds := make([]string, 0, len(providerRules))
+ for _, provider := range providerRules {
+ serviceIds, err := dr.parseDependencyRule(provider)
+ switch {
+ case provider.ServiceName == "*":
+ if err != nil {
+ util.Logger().Errorf(err, "Add dependency failed, rely all service: get all services failed.")
+ return provideServiceIds, err
+ }
+ return serviceIds, nil
+ default:
+ if err != nil {
+ util.Logger().Errorf(err, "Get providerIds failed, service: %s/%s/%s",
+ provider.AppId, provider.ServiceName, provider.Version)
+ return provideServiceIds, err
+ }
+ if len(serviceIds) == 0 {
+ util.Logger().Warnf(nil, "Get providerIds is empty, service: %s/%s/%s does not exist",
+ provider.AppId, provider.ServiceName, provider.Version)
+ continue
+ }
+ provideServiceIds = append(provideServiceIds, serviceIds...)
+ }
+ }
+ return provideServiceIds, nil
+}
+
+func (dr *DependencyRelation) parseDependencyRule(dependencyRule *pb.MicroServiceKey) (serviceIds []string, err error) {
+ opts := FromContext(dr.ctx)
+ switch {
+ case dependencyRule.ServiceName == "*":
+ util.Logger().Infof("Rely all service, * type, consumerId %s", dr.consumer.ServiceId)
+ splited := strings.Split(apt.GenerateServiceIndexKey(dependencyRule), "/")
+ allServiceKey := util.StringJoin(splited[:len(splited)-3], "/") + "/"
+ sopts := append(opts,
+ registry.WithStrKey(allServiceKey),
+ registry.WithPrefix())
+ resp, err := backend.Store().Service().Search(dr.ctx, sopts...)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, kv := range resp.Kvs {
+ serviceIds = append(serviceIds, kv.Value.(string))
+ }
+ default:
+ serviceIds, err = FindServiceIds(dr.ctx, dependencyRule.Version, dependencyRule)
+ }
+ return
+}
+
+func (dr *DependencyRelation) GetDependencyConsumers(opts ...DependencyRelationFilterOption) ([]*pb.MicroService, error) {
+ consumerDependAllList, err := dr.getDependencyConsumersOfProvider()
+ if err != nil {
+ util.Logger().Errorf(err, "Get consumers of provider rule failed, %s", dr.provider.ServiceId)
+ return nil, err
+ }
+ consumers := make([]*pb.MicroService, 0)
+ op := toDependencyRelationFilterOpt(opts...)
+ for _, consumer := range consumerDependAllList {
+ if op.SameDomainProject && consumer.Tenant != dr.domainProject {
+ continue
+ }
+
+ service, err := dr.getServiceByMicroServiceKey(consumer)
+ if err != nil {
+ return nil, err
+ }
+ if service == nil {
+ util.Logger().Warnf(nil, "Consumer does not exist, %v", consumer)
+ continue
+ }
+
+ if op.NonSelf && service.ServiceId == dr.provider.ServiceId {
+ continue
+ }
+
+ consumers = append(consumers, service)
+ }
+ return consumers, nil
+}
+
+func (dr *DependencyRelation) getServiceByMicroServiceKey(service *pb.MicroServiceKey) (*pb.MicroService, error) {
+ serviceId, err := GetServiceId(dr.ctx, service)
+ if err != nil {
+ return nil, err
+ }
+ if len(serviceId) == 0 {
+ util.Logger().Warnf(nil, "Service not exist,%v", service)
+ return nil, nil
+ }
+ return GetService(dr.ctx, service.Tenant, serviceId)
+}
+
+func (dr *DependencyRelation) GetDependencyConsumerIds() ([]string, error) {
+ consumerDependAllList, err := dr.getDependencyConsumersOfProvider()
+ if err != nil {
+ return nil, err
+ }
+ consumerIds := make([]string, 0, len(consumerDependAllList))
+ for _, consumer := range consumerDependAllList {
+ consumerId, err := GetServiceId(dr.ctx, consumer)
+ if err != nil {
+ util.Logger().Errorf(err, "Get consumer failed, %v", consumer)
+ return nil, err
+ }
+ if len(consumerId) == 0 {
+ util.Logger().Warnf(nil, "Get consumer not exist, %v", consumer)
+ continue
+ }
+ consumerIds = append(consumerIds, consumerId)
+ }
+ return consumerIds, nil
+
+}
+
+func (dr *DependencyRelation) getDependencyConsumersOfProvider() ([]*pb.MicroServiceKey, error) {
+ if dr.provider == nil {
+ util.LOGGER.Infof("dr.provider is nil ------->")
+ return nil, fmt.Errorf("Invalid provider")
+ }
+ providerService := pb.MicroServiceToKey(dr.domainProject, dr.provider)
+ consumerDependAllList, err := dr.getConsumerOfDependAllServices()
+ if err != nil {
+ util.Logger().Errorf(err, "Get consumer that depend on all services failed, %s", dr.provider.ServiceId)
+ return nil, err
+ }
+
+ consumerDependList, err := dr.getConsumerOfSameServiceNameAndAppId(providerService)
+ if err != nil {
+ util.Logger().Errorf(err, "Get consumer that depend on same serviceName and appid rule failed, %s",
+ dr.provider.ServiceId)
+ return nil, err
+ }
+ consumerDependAllList = append(consumerDependAllList, consumerDependList...)
+ return consumerDependAllList, nil
+}
+
+func (dr *DependencyRelation) getConsumerOfDependAllServices() ([]*pb.MicroServiceKey, error) {
+ providerService := pb.MicroServiceToKey(dr.domainProject, dr.provider)
+ providerService.ServiceName = "*"
+ relyAllKey := apt.GenerateProviderDependencyRuleKey(dr.domainProject, providerService)
+ opts := append(FromContext(dr.ctx), registry.WithStrKey(relyAllKey))
+ rsp, err := backend.Store().DependencyRule().Search(dr.ctx, opts...)
+ if err != nil {
+ util.Logger().Errorf(err, "get consumer that rely all service failed.")
+ return nil, err
+ }
+ if len(rsp.Kvs) != 0 {
+ util.Logger().Infof("consumer that rely all service exist.ServiceName: %s.", dr.provider.ServiceName)
+ return rsp.Kvs[0].Value.(*pb.MicroServiceDependency).Dependency, nil
+ }
+ return nil, nil
+}
+
+func (dr *DependencyRelation) getConsumerOfSameServiceNameAndAppId(provider *pb.MicroServiceKey) ([]*pb.MicroServiceKey, error) {
+ providerVersion := provider.Version
+ provider.Version = ""
+ prefix := apt.GenerateProviderDependencyRuleKey(dr.domainProject, provider)
+ provider.Version = providerVersion
+
+ opts := append(FromContext(dr.ctx),
+ registry.WithStrKey(prefix),
+ registry.WithPrefix())
+ rsp, err := backend.Store().DependencyRule().Search(dr.ctx, opts...)
+ if err != nil {
+ util.Logger().Errorf(err, "get all dependency rule failed: provider rule key %v.", provider)
+ return nil, err
+ }
+
+ allConsumers := make([]*pb.MicroServiceKey, 0, len(rsp.Kvs))
+ var latestServiceId []string
+
+ for _, kv := range rsp.Kvs {
+ providerVersionRuleArr := strings.Split(util.BytesToStringWithNoCopy(kv.Key), "/")
+ providerVersionRule := providerVersionRuleArr[len(providerVersionRuleArr)-1]
+ if providerVersionRule == "latest" {
+ if latestServiceId == nil {
+ latestServiceId, err = FindServiceIds(dr.ctx, providerVersionRule, provider)
+ if err != nil {
+ util.Logger().Errorf(err, "Get latest service failed.")
+ return nil, err
+ }
+ }
+ if len(latestServiceId) == 0 {
+ util.Logger().Infof("%s 's providerId is empty,no this service.", provider.ServiceName)
+ continue
+ }
+ if dr.provider.ServiceId != latestServiceId[0] {
+ continue
+ }
+
+ } else {
+ if !VersionMatchRule(providerVersion, providerVersionRule) {
+ continue
+ }
+ }
+
+ util.Logger().Debugf("providerETCD is %s", providerVersionRuleArr)
+ allConsumers = append(allConsumers, kv.Value.(*pb.MicroServiceDependency).Dependency...)
+ }
+ return allConsumers, nil
+}
+
+func NewProviderDependencyRelation(ctx context.Context, domainProject string, provider *pb.MicroService) *DependencyRelation {
+ return NewDependencyRelation(ctx, domainProject, nil, provider)
+}
+
+func NewConsumerDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService) *DependencyRelation {
+ return NewDependencyRelation(ctx, domainProject, consumer, nil)
+}
+
+func NewDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService, provider *pb.MicroService) *DependencyRelation {
+ return &DependencyRelation{
+ ctx: ctx,
+ domainProject: domainProject,
+ consumer: consumer,
+ provider: provider,
+ }
+}
diff --git a/server/service/util/dependency_test.go b/server/service/util/dependency_test.go
index ab63fd7..af7b3a4 100644
--- a/server/service/util/dependency_test.go
+++ b/server/service/util/dependency_test.go
@@ -17,7 +17,6 @@
package util
import (
- "github.com/apache/incubator-servicecomb-service-center/pkg/util"
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
"golang.org/x/net/context"
"testing"
@@ -28,51 +27,10 @@ func TestDeleteDependencyForService(t *testing.T) {
if err != nil {
t.Fatalf(`DeleteDependencyForDeleteService failed`)
}
-
- _, err = updateProviderDependencyRuleUtil(
- &proto.MicroServiceDependency{
- Dependency: []*proto.MicroServiceKey{
- {AppId: "a"},
- },
- },
- &proto.MicroServiceKey{
- AppId: "a",
- }, "")
- if err != nil {
- t.Fatalf(`deleteDependencyRuleUtil with the same deps failed`)
- }
-
- _, err = updateProviderDependencyRuleUtil(
- &proto.MicroServiceDependency{
- Dependency: []*proto.MicroServiceKey{
- {AppId: "b"},
- },
- },
- &proto.MicroServiceKey{
- AppId: "a",
- }, "")
- if err != nil {
- t.Fatalf(`deleteDependencyRuleUtil failed`)
- }
-
- _, err = deleteConsumerDepOfProviderRule(context.Background(), "", &proto.MicroServiceKey{}, &proto.MicroServiceKey{})
- if err == nil {
- t.Fatalf(`deleteConsumerDepOfProviderRule failed`)
- }
-
- _, err = deleteDepRuleUtil("", &proto.MicroServiceDependency{}, &proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`deleteDepRuleUtil failed`)
- }
}
func TestTransferToMicroServiceDependency(t *testing.T) {
- _, err := TransferToMicroServiceDependency(util.SetContext(context.Background(), "cacheOnly", "1"), "")
- if err != nil {
- t.Fatalf(`TransferToMicroServiceDependency WithCacheOnly failed`)
- }
-
- _, err = TransferToMicroServiceDependency(context.Background(), "")
+ _, err := TransferToMicroServiceDependency(context.Background(), "")
if err == nil {
t.Fatalf(`TransferToMicroServiceDependency failed`)
}
@@ -114,11 +72,6 @@ func TestCreateDependencyRule(t *testing.T) {
t.Fatalf(`AddServiceVersionRule failed`)
}
- _, err = addDepRuleUtil("", &proto.MicroServiceDependency{}, &proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`addDepRuleUtil failed`)
- }
-
b, err := containServiceDependency([]*proto.MicroServiceKey{
{AppId: "a"},
}, &proto.MicroServiceKey{
@@ -154,33 +107,6 @@ func TestCreateDependencyRule(t *testing.T) {
if !ok {
t.Fatalf(`diffServiceVersion failed`)
}
-
- ok = isDependencyAll(&proto.MicroServiceDependency{})
- if ok {
- t.Fatalf(`isDependencyAll not * failed`)
- }
-
- ok = isDependencyAll(&proto.MicroServiceDependency{
- Dependency: []*proto.MicroServiceKey{
- {
- ServiceName: "*",
- },
- },
- })
- if !ok {
- t.Fatalf(`isDependencyAll * failed`)
- }
-
- ok = isExist([]*proto.MicroServiceKey{
- {
- ServiceName: "*",
- },
- }, &proto.MicroServiceKey{
- ServiceName: "*",
- })
- if !ok {
- t.Fatalf(`isExist failed`)
- }
}
func TestBadParamsResponse(t *testing.T) {
@@ -248,23 +174,13 @@ func TestParamsChecker(t *testing.T) {
}
func TestServiceDependencyRuleExist(t *testing.T) {
- _, err := DependencyRuleExist(util.SetContext(context.Background(), "cacheOnly", "1"), &proto.MicroServiceKey{}, &proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`ServiceDependencyRuleExist WithCacheOnly failed`)
- }
-
- _, err = DependencyRuleExist(context.Background(), &proto.MicroServiceKey{}, &proto.MicroServiceKey{})
+ _, err := DependencyRuleExist(context.Background(), &proto.MicroServiceKey{}, &proto.MicroServiceKey{})
if err == nil {
t.Fatalf(`ServiceDependencyRuleExist failed`)
}
}
func TestUpdateServiceForAddDependency(t *testing.T) {
- _, _, err := updateDepRuleUtil("", &proto.MicroServiceDependency{}, &proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`updateDepRuleUtil failed`)
- }
-
old := isNeedUpdate([]*proto.MicroServiceKey{
{
AppId: "a",
@@ -281,33 +197,6 @@ func TestUpdateServiceForAddDependency(t *testing.T) {
}
}
-func TestFilter(t *testing.T) {
- _, _, err := getConsumerIdsWithFilter(context.Background(), "", &proto.MicroService{}, noFilter)
- if err != nil {
- t.Fatalf(`getConsumerIdsWithFilter failed`)
- }
-
- _, _, err = filterConsumerIds(context.Background(), []string{}, noFilter)
- if err != nil {
- t.Fatalf(`filterConsumerIds invalid failed`)
- }
-
- _, _, err = filterConsumerIds(context.Background(), []string{"a"}, noFilter)
- if err != nil {
- t.Fatalf(`filterConsumerIds invalid failed`)
- }
-
- rf := RuleFilter{
- DomainProject: "",
- Provider: &proto.MicroService{},
- ProviderRules: []*proto.ServiceRule{},
- }
- _, _, err = filterConsumerIds(context.Background(), []string{"a"}, rf.Filter)
- if err != nil {
- t.Fatalf(`filterConsumerIds invalid failed`)
- }
-}
-
func TestDependency(t *testing.T) {
d := &Dependency{
removedDependencyRuleList: []*proto.MicroServiceKey{
@@ -327,47 +216,6 @@ func TestDependency(t *testing.T) {
dr := &DependencyRelation{
provider: &proto.MicroService{},
consumer: &proto.MicroService{},
- ctx: util.SetContext(context.Background(), "cacheOnly", "1"),
- }
- _, err = dr.GetDependencyProviders()
- if err != nil {
- t.Fatalf(`DependencyRelation_GetDependencyProviders failed`)
- }
-
- _, err = dr.getDependencyProviderIds([]*proto.MicroServiceKey{
- {ServiceName: "*"},
- {ServiceName: "a", Version: "1.0.0"},
- {ServiceName: "b", Version: "latest"},
- })
- if err != nil {
- t.Fatalf(`DependencyRelation_getDependencyProviderIds * WithCacheOnly failed`)
- }
- _, err = dr.getDependencyProviderIds([]*proto.MicroServiceKey{
- {ServiceName: "a", Version: "1.0.0"},
- {ServiceName: "b", Version: "latest"},
- })
- if err != nil {
- t.Fatalf(`DependencyRelation_getDependencyProviderIds WithCacheOnly failed`)
- }
-
- _, err = dr.GetDependencyConsumers()
- if err != nil {
- t.Fatalf(`DependencyRelation_GetDependencyConsumers WithCacheOnly failed`)
- }
-
- _, err = dr.getServiceByMicroServiceKey(&proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`DependencyRelation_getServiceByMicroServiceKey WithCacheOnly failed`)
- }
-
- _, err = dr.getConsumerOfSameServiceNameAndAppId(&proto.MicroServiceKey{})
- if err != nil {
- t.Fatalf(`DependencyRelation_getConsumerOfSameServiceNameAndAppId WithCacheOnly failed`)
- }
-
- dr = &DependencyRelation{
- provider: &proto.MicroService{},
- consumer: &proto.MicroService{},
ctx: context.Background(),
}
_, err = dr.getDependencyProviderIds([]*proto.MicroServiceKey{
@@ -407,6 +255,10 @@ func TestDependency(t *testing.T) {
if err == nil {
t.Fatalf(`DependencyRelation_getDependencyConsumersOfProvider failed`)
}
+ _, err = dr.GetDependencyProviders()
+ if err == nil {
+ t.Fatalf(`DependencyRelation_GetDependencyProviders failed`)
+ }
}
func TestDependencyRelationFilterOpt(t *testing.T) {
@@ -418,3 +270,10 @@ func TestDependencyRelationFilterOpt(t *testing.T) {
t.Fatalf(`toDependencyRelationFilterOpt failed`)
}
}
+
+func TestGetConsumerIdsWithFilter(t *testing.T) {
+ _, _, err := getConsumerIdsWithFilter(context.Background(), "", &proto.MicroService{}, nil)
+ if err == nil {
+ t.Fatalf(`TestGetConsumerIdsWithFilter failed`)
+ }
+}
diff --git a/server/service/util/dependency_util.go b/server/service/util/dependency_util.go
new file mode 100644
index 0000000..f7f4aae
--- /dev/null
+++ b/server/service/util/dependency_util.go
@@ -0,0 +1,427 @@
+/*
+ * 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 util
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+ apt "github.com/apache/incubator-servicecomb-service-center/server/core"
+ "github.com/apache/incubator-servicecomb-service-center/server/core/backend"
+ pb "github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+ scerr "github.com/apache/incubator-servicecomb-service-center/server/error"
+ "github.com/apache/incubator-servicecomb-service-center/server/infra/registry"
+ "golang.org/x/net/context"
+ "strings"
+)
+
+func GetConsumerIds(ctx context.Context, domainProject string, provider *pb.MicroService) ([]string, error) {
+ // 查询所有consumer
+ dr := NewProviderDependencyRelation(ctx, domainProject, provider)
+ consumerIds, err := dr.GetDependencyConsumerIds()
+ if err != nil {
+ util.Logger().Errorf(err, "Get dependency consumerIds failed.%s", provider.ServiceId)
+ return nil, err
+ }
+ return consumerIds, nil
+}
+
+func GetProviderIds(ctx context.Context, domainProject string, consumer *pb.MicroService) ([]string, error) {
... 1838 lines suppressed ...