diff --git a/README.md b/README.md index 29447d21c95493282670cf53737e1da077036184..617eb03bdc03827fe882f47c46ae62bf3fbb026e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Go for Apache Dubbo [中文](./README_CN.md) # +# Apache Dubbo-go [中文](./README_CN.md) # -[](https://travis-ci.com/apache/dubbo-go) +[](https://travis-ci.org/apache/dubbo-go) [](https://codecov.io/gh/apache/dubbo-go) --- @@ -16,11 +16,11 @@ Apache License, Version 2.0 ## Project Architecture ## -Extension module and layered code design based on dubbo (include protocol layer,registry layer,cluster layer,config layer and so on), Our goal is: you can implement these layered interfaces in a new way, and override the default implementation of dubbo-go[same go-for-apache-dubbo] by calling 'extension.SetXXX' of extension, and complete your special needs without modifying the source code. At the same time, you are welcome to contribute implementation of useful expansion to the community. +Both extension module and layered project architecture is according to Apache Dubbo (including protocol layer, registry layer, cluster layer, config layer and so on), the advantage of this arch is as following: you can implement these layered interfaces in your own way, override the default implementation of dubbo-go by calling 'extension.SetXXX' of extension, complete your special needs without modifying the source code. At the same time, you are welcome to contribute implementation of useful extension to the community.  -About detail design please refer to [code layered design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) +If you wanna know more about dubbo-go, please visit this reference [Project Architeture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) ## Feature list ## @@ -31,13 +31,13 @@ Finished List: - Codec: JsonRPC v2(√), Hessian v2(√) - Registry: ZooKeeper(√) - Cluster Strategy: Failover(√) -- Load Balance: Random(√) +- Load Balance: Random(√), RoundRobin(√), LeastActive(√) - Filter: Echo Health Check(√) Working List: - Cluster Strategy: Failfast/Failsafe/Failback/Forking -- Load Balance: RoundRobin/LeastActive/ConsistentHash +- Load Balance: ConsistentHash - Filter: TokenFilter/AccessLogFilter/CountFilter/ActiveLimitFilter/ExecuteLimitFilter/GenericFilter/TpsLimitFilter - Registry: etcd/k8s/consul @@ -67,4 +67,3 @@ About dubbo-go benchmarking report, please refer to [dubbo benchmarking report]( ## Stargazers [](https://starchart.cc/apache/dubbo-go) - diff --git a/README_CN.md b/README_CN.md index 656ea286d54cff66c68659fb6a107213809c4eb3..14ba3dd8fed983699e06fd113d6d4f6f5e90aee6 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,70 +1,70 @@ -# Go for Apache Dubbo [English](./README.md) # - -[](https://travis-ci.com/apache/dubbo-go) -[](https://codecov.io/gh/apache/dubbo-go) - ---- -Apache Dubbo Go 语言实现 - -## 证书 ## - -Apache License, Version 2.0 - -## 发布日志 ## - -[v1.0.0 - 2019年5月29日 兼容dubbo v2.6.5 版本](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) - -## 工程架构 ## - -基于dubbo的extension模块和分层的代码设计(包括 protocol layer, registry layer, cluster layer, config 等等)。我们的目标是:你可以对这些分层接口进行新的实现,并通过调用 extension 模块的“ extension.SetXXX ”方法来覆盖 dubbo-go [同 go-for-apache-dubbo ]的默认实现,以完成自己的特殊需求而无需修改源代码。同时,欢迎你为社区贡献有用的拓展实现。 - - - -关于详细设计请阅读 [code layered design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) - -## 功能列表 ## - -实现列表: - -- Role: Consumer(√), Provider(√) -- Transport: HTTP(√), TCP(√) -- Codec: JsonRPC v2(√), Hessian v2(√) -- Registry: ZooKeeper(√) -- Cluster Strategy: Failover(√) -- Load Balance: Random(√) -- Filter: Echo Health Check(√) - -开发中列表: - -- Cluster Strategy: Failfast/Failsafe/Failback/Forking -- Load Balance: RoundRobin/LeastActive/ConsistentHash -- Filter: TokenFilter/AccessLogFilter/CountFilter/ActiveLimitFilter/ExecuteLimitFilter/GenericFilter/TpsLimitFilter -- Registry: etcd/k8s/consul - -任务列表: - -- routing rule (dubbo v2.6.x) -- metrics (dubbo v2.7.x) waiting dubbo's quota -- dynamic configuration center & metadata center (dubbo v2.7.x) -- tracing (dubbo ecosystem) - -你可以通过访问 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 知道更多关于 dubbo-go 的信息 - -## 快速开始 ## - -这个子目录下的例子展示了如何使用 dubbo-go 。请仔细阅读 [examples/README.md](https://github.com/apache/dubbo-go/blob/develop/examples/README.md) 学习如何处理配置并编译程序。 - -## 性能测试 ## - -性能测试项目是 [go-for-apache-dubbo-benchmark](https://github.com/dubbogo/go-for-apache-dubbo-benchmark) - -关于 dubbo-go 性能测试报告,请阅读 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc) - -## [User List](https://github.com/apache/dubbo-go/issues/2) - - - -## Stargazers - -[](https://starchart.cc/apache/dubbo-go) - +# Apache Dubbo-go [English](./README.md) # + +[](https://travis-ci.org/apache/dubbo-go) +[](https://codecov.io/gh/apache/dubbo-go) + +--- +Apache Dubbo Go 语言实现 + +## 证书 ## + +Apache License, Version 2.0 + +## 发布日志 ## + +[v1.0.0 - 2019年5月29日 兼容dubbo v2.6.5 版本](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) + +## 工程架构 ## + +基于dubbo的extension模块和分层的代码设计(包括 protocol layer, registry layer, cluster layer, config 等等)。我们的目标是:你可以对这些分层接口进行新的实现,并通过调用 extension 模块的“ extension.SetXXX ”方法来覆盖 dubbo-go [同 go-for-apache-dubbo ]的默认实现,以完成自己的特殊需求而无需修改源代码。同时,欢迎你为社区贡献有用的拓展实现。 + + + +关于详细设计请阅读 [code layered design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) + +## 功能列表 ## + +实现列表: + +- Role: Consumer(√), Provider(√) +- Transport: HTTP(√), TCP(√) +- Codec: JsonRPC v2(√), Hessian v2(√) +- Registry: ZooKeeper(√) +- Cluster Strategy: Failover(√) +- Load Balance: Random(√), RoundRobin(√), LeastActive(√) +- Filter: Echo Health Check(√) + +开发中列表: + +- Cluster Strategy: Failfast/Failsafe/Failback/Forking +- Load Balance: ConsistentHash +- Filter: TokenFilter/AccessLogFilter/CountFilter/ActiveLimitFilter/ExecuteLimitFilter/GenericFilter/TpsLimitFilter +- Registry: etcd/k8s/consul + +任务列表: + +- routing rule (dubbo v2.6.x) +- metrics (dubbo v2.7.x) waiting dubbo's quota +- dynamic configuration center & metadata center (dubbo v2.7.x) +- tracing (dubbo ecosystem) + +你可以通过访问 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 知道更多关于 dubbo-go 的信息 + +## 快速开始 ## + +这个子目录下的例子展示了如何使用 dubbo-go 。请仔细阅读 [examples/README.md](https://github.com/apache/dubbo-go/blob/develop/examples/README.md) 学习如何处理配置并编译程序。 + +## 性能测试 ## + +性能测试项目是 [go-for-apache-dubbo-benchmark](https://github.com/dubbogo/go-for-apache-dubbo-benchmark) + +关于 dubbo-go 性能测试报告,请阅读 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc) + +## [User List](https://github.com/apache/dubbo-go/issues/2) + + + +## Stargazers + +[](https://starchart.cc/apache/dubbo-go) + diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go new file mode 100644 index 0000000000000000000000000000000000000000..d7d305681808c64b452750e7d475c384b5f31d22 --- /dev/null +++ b/cluster/loadbalance/least_active.go @@ -0,0 +1,101 @@ +// Licensed 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. + +// @author yiji@apache.org +package loadbalance + +import ( + "math/rand" +) + +import ( + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" +) + +const ( + LeastActive = "leastactive" +) + +func init() { + extension.SetLoadbalance(LeastActive, NewLeastActiveLoadBalance) +} + +type leastActiveLoadBalance struct { +} + +func NewLeastActiveLoadBalance() cluster.LoadBalance { + return &leastActiveLoadBalance{} +} + +func (lb *leastActiveLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { + count := len(invokers) + if count == 0 { + return nil + } + if count == 1 { + return invokers[0] + } + + var ( + leastActive int32 = -1 // The least active value of all invokers + totalWeight int64 = 0 // The number of invokers having the same least active value (LEAST_ACTIVE) + firstWeight int64 = 0 // Initial value, used for comparision + leastIndexes = make([]int, count) // The index of invokers having the same least active value (LEAST_ACTIVE) + leastCount = 0 // The number of invokers having the same least active value (LEAST_ACTIVE) + sameWeight = true // Every invoker has the same weight value? + ) + + for i := 0; i < count; i++ { + invoker := invokers[i] + // Active number + active := protocol.GetStatus(invoker.GetUrl(), invocation.MethodName()).GetActive() + // current weight (maybe in warmUp) + weight := GetWeight(invoker, invocation) + // There are smaller active services + if leastActive == -1 || active < leastActive { + leastActive = active + leastIndexes[0] = i + leastCount = 1 // next available leastIndex offset + totalWeight = weight + firstWeight = weight + sameWeight = true + } else if active == leastActive { + leastIndexes[leastCount] = i + totalWeight += weight + leastCount++ + + if sameWeight && (i > 0) && weight != firstWeight { + sameWeight = false + } + } + } + + if leastCount == 1 { + return invokers[0] + } + + if !sameWeight && totalWeight > 0 { + offsetWeight := rand.Int63n(totalWeight) + 1 + for i := 0; i < leastCount; i++ { + leastIndex := leastIndexes[i] + offsetWeight -= GetWeight(invokers[i], invocation) + if offsetWeight <= 0 { + return invokers[leastIndex] + } + } + } + + index := leastIndexes[rand.Intn(leastCount)] + return invokers[index] +} diff --git a/cluster/loadbalance/least_active_test.go b/cluster/loadbalance/least_active_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c29a2092a19161d0dd75ee4098ee786b620880b0 --- /dev/null +++ b/cluster/loadbalance/least_active_test.go @@ -0,0 +1,67 @@ +package loadbalance + +import ( + "context" + "fmt" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestLeastActiveSelect(t *testing.T) { + loadBalance := NewLeastActiveLoadBalance() + + var invokers []protocol.Invoker + + url, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + invokers = append(invokers, protocol.NewBaseInvoker(url)) + i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) + assert.True(t, i.GetUrl().URLEqual(url)) + + for i := 1; i < 10; i++ { + url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) + invokers = append(invokers, protocol.NewBaseInvoker(url)) + } + loadBalance.Select(invokers, &invocation.RPCInvocation{}) +} + +func TestLeastActiveByWeight(t *testing.T) { + loadBalance := NewLeastActiveLoadBalance() + + var invokers []protocol.Invoker + loop := 3 + for i := 1; i <= loop; i++ { + url, _ := common.NewURL(context.TODO(), fmt.Sprintf("test%v://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i, i)) + invokers = append(invokers, protocol.NewBaseInvoker(url)) + } + + inv := new(invocation.RPCInvocation) + inv.SetMethod("test") + protocol.BeginCount(invokers[2].GetUrl(), inv.MethodName()) + + loop = 10000 + + var ( + firstCount int + secondCount int + ) + + for i := 1; i <= loop; i++ { + invoker := loadBalance.Select(invokers, inv) + if invoker.GetUrl().Protocol == "test1" { + firstCount++ + } else if invoker.GetUrl().Protocol == "test2" { + secondCount++ + } + } + + assert.Equal(t, firstCount+secondCount, loop) +} diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go new file mode 100644 index 0000000000000000000000000000000000000000..e173e211c3630f4d4786edc2c1a09709fd7bf0a1 --- /dev/null +++ b/cluster/loadbalance/round_robin.go @@ -0,0 +1,153 @@ +// +// Licensed 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 loadbalance + +import ( + "math" + "sync" + "sync/atomic" + "time" +) + +import ( + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" +) + +const ( + RoundRobin = "roundrobin" + + COMPLETE = 0 + UPDATING = 1 +) + +var ( + methodWeightMap sync.Map // [string]invokers + state int32 = COMPLETE // update lock acquired ? + recyclePeriod int64 = 60 * time.Second.Nanoseconds() +) + +func init() { + extension.SetLoadbalance(RoundRobin, NewRoundRobinLoadBalance) +} + +type roundRobinLoadBalance struct{} + +func NewRoundRobinLoadBalance() cluster.LoadBalance { + return &roundRobinLoadBalance{} +} + +func (lb *roundRobinLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { + count := len(invokers) + if count == 0 { + return nil + } + if count == 1 { + return invokers[0] + } + + key := invokers[0].GetUrl().Path + "." + invocation.MethodName() + cache, _ := methodWeightMap.LoadOrStore(key, &cachedInvokers{}) + cachedInvokers := cache.(*cachedInvokers) + + var ( + clean = false + totalWeight = int64(0) + maxCurrentWeight = int64(math.MinInt64) + now = time.Now() + selectedInvoker protocol.Invoker + selectedWeightRobin *weightedRoundRobin + ) + + for _, invoker := range invokers { + var weight = GetWeight(invoker, invocation) + if weight < 0 { + weight = 0 + } + + identifier := invoker.GetUrl().Key() + loaded, found := cachedInvokers.LoadOrStore(identifier, &weightedRoundRobin{weight: weight}) + weightRobin := loaded.(*weightedRoundRobin) + if !found { + clean = true + } + + if weightRobin.Weight() != weight { + weightRobin.setWeight(weight) + } + + currentWeight := weightRobin.increaseCurrent() + weightRobin.lastUpdate = &now + + if currentWeight > maxCurrentWeight { + maxCurrentWeight = currentWeight + selectedInvoker = invoker + selectedWeightRobin = weightRobin + } + totalWeight += weight + } + + cleanIfRequired(clean, cachedInvokers, &now) + + if selectedWeightRobin != nil { + selectedWeightRobin.Current(totalWeight) + return selectedInvoker + } + + // should never happen + return invokers[0] +} + +func cleanIfRequired(clean bool, invokers *cachedInvokers, now *time.Time) { + if clean && atomic.CompareAndSwapInt32(&state, COMPLETE, UPDATING) { + defer atomic.CompareAndSwapInt32(&state, UPDATING, COMPLETE) + invokers.Range(func(identify, robin interface{}) bool { + weightedRoundRobin := robin.(*weightedRoundRobin) + elapsed := now.Sub(*weightedRoundRobin.lastUpdate).Nanoseconds() + if elapsed > recyclePeriod { + invokers.Delete(identify) + } + return true + }) + } +} + +// Record the weight of the invoker +type weightedRoundRobin struct { + weight int64 + current int64 + lastUpdate *time.Time +} + +func (robin *weightedRoundRobin) Weight() int64 { + return atomic.LoadInt64(&robin.weight) +} + +func (robin *weightedRoundRobin) setWeight(weight int64) { + robin.weight = weight + robin.current = 0 +} + +func (robin *weightedRoundRobin) increaseCurrent() int64 { + return atomic.AddInt64(&robin.current, robin.weight) +} + +func (robin *weightedRoundRobin) Current(delta int64) { + atomic.AddInt64(&robin.current, -1*delta) +} + +type cachedInvokers struct { + sync.Map /*[string]weightedRoundRobin*/ +} diff --git a/cluster/loadbalance/round_robin_test.go b/cluster/loadbalance/round_robin_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e261884b55971d95ccfbfdc34e32a07dc256fcca --- /dev/null +++ b/cluster/loadbalance/round_robin_test.go @@ -0,0 +1,59 @@ +package loadbalance + +import ( + "context" + "fmt" + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestRoundRobinSelect(t *testing.T) { + loadBalance := NewRoundRobinLoadBalance() + + var invokers []protocol.Invoker + + url, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + invokers = append(invokers, protocol.NewBaseInvoker(url)) + i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) + assert.True(t, i.GetUrl().URLEqual(url)) + + for i := 1; i < 10; i++ { + url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) + invokers = append(invokers, protocol.NewBaseInvoker(url)) + } + loadBalance.Select(invokers, &invocation.RPCInvocation{}) +} + +func TestRoundRobinByWeight(t *testing.T) { + loadBalance := NewRoundRobinLoadBalance() + + var invokers []protocol.Invoker + loop := 10 + for i := 1; i <= loop; i++ { + url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i)) + invokers = append(invokers, protocol.NewBaseInvoker(url)) + } + + loop = (1 + loop) * loop / 2 + selected := make(map[protocol.Invoker]int) + + for i := 1; i <= loop; i++ { + invoker := loadBalance.Select(invokers, &invocation.RPCInvocation{}) + selected[invoker]++ + } + + for _, i := range invokers { + w, _ := strconv.Atoi(i.GetUrl().GetParam("weight", "-1")) + assert.True(t, selected[i] == w) + } +} diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go index 736952159d83a9f4ade2c2d0cb190b60f1ef3fbe..7e0c2e265073c0a96032a6dd3294a6d73c1a4001 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -28,7 +28,8 @@ import ( func GetWeight(invoker protocol.Invoker, invocation protocol.Invocation) int64 { url := invoker.GetUrl() - weight := url.GetMethodParamInt(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) + weight := url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) + if weight > 0 { //get service register time an do warm up time now := time.Now().Unix() diff --git a/common/constant/default.go b/common/constant/default.go index 51d98ce1d2ec779b88e32c2f3b59e0d312ae6a4f..85722c573185c97277bd9991ea1da6ca03eefa39 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -26,7 +26,6 @@ const ( DEFAULT_LOADBALANCE = "random" DEFAULT_RETRIES = 2 DEFAULT_PROTOCOL = "dubbo" - DEFAULT_VERSION = "" DEFAULT_REG_TIMEOUT = "10s" DEFAULT_CLUSTER = "failover" ) @@ -38,3 +37,7 @@ const ( DEFAULT_REFERENCE_FILTERS = "" ECHO = "$echo" ) + +const ( + ANY_VALUE = "*" +) diff --git a/common/constant/key.go b/common/constant/key.go index 54702db02d969a33f1589731bf4563c2cffcbd7e..55bf7b7c8642c7535f16341ec929f7bfc5fcb223 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -44,6 +44,7 @@ const ( WEIGHT_KEY = "weight" WARMUP_KEY = "warmup" RETRIES_KEY = "retries" + BEAN_NAME = "bean.name" ) const ( @@ -70,3 +71,8 @@ const ( METHOD_KEYS = "methods" RULE_KEY = "rule" ) + +const ( + CONFIG_NAMESPACE_KEY = "config.namespace" + CONFIG_TIMEOUT_KET = "config.timeout" +) diff --git a/common/extension/config_center.go b/common/extension/config_center.go new file mode 100644 index 0000000000000000000000000000000000000000..be4b62ccdd9c36500c306c7f16abd054f91ae86b --- /dev/null +++ b/common/extension/config_center.go @@ -0,0 +1,39 @@ +/* + * 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 extension + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/config_center" +) + +var ( + configCenters = make(map[string]func(config *common.URL) (config_center.DynamicConfiguration, error)) +) + +func SetConfigCenter(name string, v func(config *common.URL) (config_center.DynamicConfiguration, error)) { + configCenters[name] = v +} + +func GetConfigCenter(name string, config *common.URL) (config_center.DynamicConfiguration, error) { + if configCenters[name] == nil { + panic("config center for " + name + " is not existing, make sure you have import the package.") + } + return configCenters[name](config) + +} diff --git a/common/logger/logger.go b/common/logger/logger.go index f6522c489d047a1d4ec7000565bf4cfda6a50466..e8dd5a608b68c7cbde0d8c6b85c1601c847fea01 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -56,7 +56,7 @@ func init() { logConfFile := os.Getenv(constant.APP_LOG_CONF_FILE) err := InitLog(logConfFile) if err != nil { - log.Printf("[InitLog] error: %v", err) + log.Printf("[InitLog] warn: %v", err) } } @@ -110,7 +110,7 @@ func InitLogger(conf *zap.Config) { } else { zapLoggerConfig = *conf } - zapLogger, _ := zapLoggerConfig.Build() + zapLogger, _ := zapLoggerConfig.Build(zap.AddCallerSkip(1)) logger = zapLogger.Sugar() } diff --git a/common/logger/logger_test.go b/common/logger/logger_test.go index ed963ce54dcabfbf9f35f8a9f8b94723766617c9..e29b7cbc8e9bbd67df41df5ac687a079621c3360 100644 --- a/common/logger/logger_test.go +++ b/common/logger/logger_test.go @@ -18,9 +18,13 @@ package logger import ( + "fmt" "path/filepath" + "runtime" "testing" +) +import ( "github.com/stretchr/testify/assert" ) @@ -41,7 +45,13 @@ func TestInitLog(t *testing.T) { path, err = filepath.Abs("./logger.yml") assert.NoError(t, err) err = InitLog(path) - assert.EqualError(t, err, "ioutil.ReadFile(file:"+path+") = error:open "+path+": no such file or directory") + var errMsg string + if runtime.GOOS == "windows" { + errMsg = fmt.Sprintf("open %s: The system cannot find the file specified.", path) + } else { + errMsg = fmt.Sprintf("open %s: no such file or directory", path) + } + assert.EqualError(t, err, fmt.Sprintf("ioutil.ReadFile(file:%s) = error:%s", path, errMsg)) err = InitLog("./log.yml") assert.NoError(t, err) diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index fe4c003306360af928a0b1a6612042e9811a9af3..96d42eb21152e64d170f50276bbce88e1bf8db69 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -19,6 +19,7 @@ package proxy import ( "reflect" + "sync" ) import ( @@ -34,6 +35,8 @@ type Proxy struct { invoke protocol.Invoker callBack interface{} attachments map[string]string + + once sync.Once } var typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type() @@ -78,23 +81,31 @@ func (p *Proxy) Implement(v common.RPCService) { methodName = "$echo" } - start := 0 - end := len(in) - if in[0].Type().String() == "context.Context" { - start += 1 - } - if len(outs) == 1 { - end -= 1 - reply = in[len(in)-1] - } else { + if len(outs) == 2 { if outs[0].Kind() == reflect.Ptr { reply = reflect.New(outs[0].Elem()) } else { reply = reflect.New(outs[0]) } + } else { + reply = valueOf + } + + start := 0 + end := len(in) + if end > 0 { + if in[0].Type().String() == "context.Context" { + start += 1 + } + if len(outs) == 1 && in[end-1].Type().Kind() == reflect.Ptr { + end -= 1 + reply = in[len(in)-1] + } } - if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 { + if end-start <= 0 { + inArr = []interface{}{} + } else if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 { inArr = v } else { inArr = make([]interface{}, end-start) @@ -134,7 +145,6 @@ func (p *Proxy) Implement(v common.RPCService) { } f := valueOfElem.Field(i) if f.Kind() == reflect.Func && f.IsValid() && f.CanSet() { - inNum := t.Type.NumIn() outNum := t.Type.NumOut() if outNum != 1 && outNum != 2 { @@ -149,12 +159,6 @@ func (p *Proxy) Implement(v common.RPCService) { continue } - // reply must be Ptr when outNum == 1 - if outNum == 1 && t.Type.In(inNum-1).Kind() != reflect.Ptr { - logger.Warnf("reply type of method %q is not a pointer", t.Name) - continue - } - var funcOuts = make([]reflect.Type, outNum) for i := 0; i < outNum; i++ { funcOuts[i] = t.Type.Out(i) @@ -166,7 +170,9 @@ func (p *Proxy) Implement(v common.RPCService) { } } - p.rpc = v + p.once.Do(func() { + p.rpc = v + }) } diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go index 016bbcf514514e3bea67d87c83f01aebb166be23..1665a7346e09016570dd36c56d231d3706b96a54 100644 --- a/common/proxy/proxy_factory/default.go +++ b/common/proxy/proxy_factory/default.go @@ -51,6 +51,6 @@ func (factory *DefaultProxyFactory) GetProxy(invoker protocol.Invoker, url *comm return proxy.NewProxy(invoker, nil, attachments) } func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker { - //TODO:yincheng need to do the service invoker refactor + // todo: call service return protocol.NewBaseInvoker(url) } diff --git a/common/proxy/proxy_factory/default_test.go b/common/proxy/proxy_factory/default_test.go index d9c8ffa2525551fc0197e2476e85b5ee51a18ec6..b6a6b675baf992b2d64ffd19291ee2dc009bd1e3 100644 --- a/common/proxy/proxy_factory/default_test.go +++ b/common/proxy/proxy_factory/default_test.go @@ -21,22 +21,25 @@ import ( "testing" ) +import ( + "github.com/stretchr/testify/assert" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" - "github.com/stretchr/testify/assert" ) func Test_GetProxy(t *testing.T) { proxyFactory := NewDefaultProxyFactory() - url := common.NewURLWithOptions("testservice") + url := common.NewURLWithOptions() proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(*url), url) assert.NotNil(t, proxy) } func Test_GetInvoker(t *testing.T) { proxyFactory := NewDefaultProxyFactory() - url := common.NewURLWithOptions("testservice") + url := common.NewURLWithOptions() invoker := proxyFactory.GetInvoker(*url) assert.True(t, invoker.IsAvailable()) } diff --git a/common/proxy/proxy_test.go b/common/proxy/proxy_test.go index 44bdaec4b845a3a864c8637ced16ac4493fc2b80..1cc30457c3b021ec139b57fe764d6ac6b9104dbc 100644 --- a/common/proxy/proxy_test.go +++ b/common/proxy/proxy_test.go @@ -36,9 +36,10 @@ import ( type TestService struct { MethodOne func(context.Context, int, bool, *interface{}) error - MethodTwo func([]interface{}, *interface{}) error + MethodTwo func([]interface{}) error MethodThree func(int, bool) (interface{}, error) MethodFour func(int, bool) (*interface{}, error) `dubbo:"methodFour"` + MethodFive func() error Echo func(interface{}, *interface{}) error } @@ -64,19 +65,25 @@ func TestProxy_Implement(t *testing.T) { p := NewProxy(invoker, nil, map[string]string{constant.ASYNC_KEY: "false"}) s := &TestService{} p.Implement(s) + err := p.Get().(*TestService).MethodOne(nil, 0, false, nil) assert.NoError(t, err) - err = p.Get().(*TestService).MethodTwo(nil, nil) + + err = p.Get().(*TestService).MethodTwo(nil) assert.NoError(t, err) ret, err := p.Get().(*TestService).MethodThree(0, false) assert.NoError(t, err) assert.Nil(t, ret) // ret is nil, because it doesn't be injection yet + ret2, err := p.Get().(*TestService).MethodFour(0, false) assert.NoError(t, err) assert.Equal(t, "*interface {}", reflect.TypeOf(ret2).String()) err = p.Get().(*TestService).Echo(nil, nil) assert.NoError(t, err) + err = p.Get().(*TestService).MethodFive() + assert.NoError(t, err) + // inherit & lowercase p.rpc = nil type S1 struct { @@ -108,24 +115,14 @@ func TestProxy_Implement(t *testing.T) { p.Implement(s2) assert.Nil(t, s2.MethodOne) - // reply type + // returns type p.rpc = nil type S3 struct { TestService - MethodOne func(context.Context, []interface{}, struct{}) error + MethodOne func(context.Context, []interface{}, *struct{}) interface{} } s3 := &S3{TestService: *s} p.Implement(s3) assert.Nil(t, s3.MethodOne) - // returns type - p.rpc = nil - type S4 struct { - TestService - MethodOne func(context.Context, []interface{}, *struct{}) interface{} - } - s4 := &S4{TestService: *s} - p.Implement(s4) - assert.Nil(t, s4.MethodOne) - } diff --git a/common/rpc_service.go b/common/rpc_service.go index d8578a143eccb05696e633cc3fd61dd6f00c3bc7..0444f0c17e7e9d96d1563c72fde2fd62b81fb744 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -40,6 +40,12 @@ type RPCService interface { Version() string } +// for lowercase func +// func MethodMapper() map[string][string] { +// return map[string][string]{} +// } +const METHOD_MAPPER = "MethodMapper" + var ( // Precompute the reflect type for error. Can't use error directly // because Typeof takes an empty interface value. This is annoying. @@ -210,20 +216,26 @@ func isExportedOrBuiltinType(t reflect.Type) bool { // suitableMethods returns suitable Rpc methods of typ func suitableMethods(typ reflect.Type) (string, map[string]*MethodType) { methods := make(map[string]*MethodType) - mts := "" + var mts []string logger.Debugf("[%s] NumMethod is %d", typ.String(), typ.NumMethod()) + method, ok := typ.MethodByName(METHOD_MAPPER) + var methodMapper map[string]string + if ok && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).String() == "map[string]string" { + methodMapper = method.Func.Call([]reflect.Value{reflect.New(typ.Elem())})[0].Interface().(map[string]string) + } + for m := 0; m < typ.NumMethod(); m++ { - method := typ.Method(m) + method = typ.Method(m) if mt := suiteMethod(method); mt != nil { - methods[method.Name] = mt - if m == 0 { - mts += method.Name - } else { - mts += "," + method.Name + methodName, ok := methodMapper[method.Name] + if !ok { + methodName = method.Name } + methods[methodName] = mt + mts = append(mts, methodName) } } - return mts, methods + return strings.Join(mts, ","), methods } // suiteMethod returns a suitable Rpc methodType @@ -256,12 +268,7 @@ func suiteMethod(method reflect.Method) *MethodType { } // replyType - if outNum == 1 { - if mtype.In(inNum-1).Kind() != reflect.Ptr { - logger.Errorf("reply type of method %q is not a pointer %v", mname, replyType) - return nil - } - } else { + if outNum == 2 { replyType = mtype.Out(0) if !isExportedOrBuiltinType(replyType) { logger.Errorf("reply type of method %s not exported{%v}", mname, replyType) @@ -272,7 +279,7 @@ func suiteMethod(method reflect.Method) *MethodType { index := 1 // ctxType - if mtype.In(1).String() == "context.Context" { + if inNum > 1 && mtype.In(1).String() == "context.Context" { ctxType = mtype.In(1) index = 2 } diff --git a/common/rpc_service_test.go b/common/rpc_service_test.go index e5bab2c7153daeb34068c2005def6811b8478532..ec4371da4768298fe0928ba6ef88c2be7060832e 100644 --- a/common/rpc_service_test.go +++ b/common/rpc_service_test.go @@ -30,18 +30,26 @@ import ( type TestService struct { } -func (s *TestService) MethodOne(ctx context.Context, args []interface{}, rsp *struct{}) error { +func (s *TestService) MethodOne(ctx context.Context, arg1, arg2, arg3 interface{}) error { return nil } -func (s *TestService) MethodTwo(args []interface{}) (struct{}, error) { +func (s *TestService) MethodTwo(arg1, arg2, arg3 interface{}) (interface{}, error) { return struct{}{}, nil } +func (s *TestService) MethodThree() error { + return nil +} func (s *TestService) Service() string { return "com.test.Path" } func (s *TestService) Version() string { return "" } +func (s *TestService) MethodMapper() map[string]string { + return map[string]string{ + "MethodTwo": "methodTwo", + } +} type testService struct { } @@ -49,15 +57,12 @@ type testService struct { func (s *testService) Method1(ctx context.Context, args testService, rsp *struct{}) error { return nil } -func (s *testService) Method2(ctx context.Context, args []interface{}, rsp struct{}) error { - return nil -} -func (s *testService) Method3(ctx context.Context, args []interface{}) (testService, error) { +func (s *testService) Method2(ctx context.Context, args []interface{}) (testService, error) { return testService{}, nil } -func (s *testService) Method4(ctx context.Context, args []interface{}, rsp *struct{}) { +func (s *testService) Method3(ctx context.Context, args []interface{}, rsp *struct{}) { } -func (s *testService) Method5(ctx context.Context, args []interface{}, rsp *struct{}) *testService { +func (s *testService) Method4(ctx context.Context, args []interface{}, rsp *struct{}) *testService { return nil } func (s *testService) Service() string { @@ -87,7 +92,7 @@ func TestServiceMap_Register(t *testing.T) { s := &TestService{} methods, err = ServiceMap.Register("testporotocol", s) assert.NoError(t, err) - assert.Equal(t, "MethodOne,MethodTwo", methods) + assert.Equal(t, "MethodOne,MethodThree,methodTwo", methods) // repeat methods, err = ServiceMap.Register("testporotocol", s) @@ -139,10 +144,11 @@ func TestSuiteMethod(t *testing.T) { assert.True(t, ok) methodType := suiteMethod(method) method = methodType.Method() - assert.Equal(t, "func(*common.TestService, context.Context, []interface {}, *struct {}) error", method.Type.String()) + assert.Equal(t, "func(*common.TestService, context.Context, interface {}, interface {}, interface {}) error", method.Type.String()) at := methodType.ArgsType() - assert.Equal(t, "[]interface {}", at[0].String()) - assert.Equal(t, "*struct {}", at[1].String()) + assert.Equal(t, "interface {}", at[0].String()) + assert.Equal(t, "interface {}", at[1].String()) + assert.Equal(t, "interface {}", at[2].String()) ct := methodType.CtxType() assert.Equal(t, "context.Context", ct.String()) rt := methodType.ReplyType() @@ -152,12 +158,25 @@ func TestSuiteMethod(t *testing.T) { assert.True(t, ok) methodType = suiteMethod(method) method = methodType.Method() - assert.Equal(t, "func(*common.TestService, []interface {}) (struct {}, error)", method.Type.String()) + assert.Equal(t, "func(*common.TestService, interface {}, interface {}, interface {}) (interface {}, error)", method.Type.String()) + at = methodType.ArgsType() + assert.Equal(t, "interface {}", at[0].String()) + assert.Equal(t, "interface {}", at[1].String()) + assert.Equal(t, "interface {}", at[2].String()) + assert.Nil(t, methodType.CtxType()) + rt = methodType.ReplyType() + assert.Equal(t, "interface {}", rt.String()) + + method, ok = reflect.TypeOf(s).MethodByName("MethodThree") + assert.True(t, ok) + methodType = suiteMethod(method) + method = methodType.Method() + assert.Equal(t, "func(*common.TestService) error", method.Type.String()) at = methodType.ArgsType() - assert.Equal(t, "[]interface {}", at[0].String()) + assert.Equal(t, 0, len(at)) assert.Nil(t, methodType.CtxType()) rt = methodType.ReplyType() - assert.Equal(t, "struct {}", rt.String()) + assert.Nil(t, rt) // wrong number of in return s1 := &testService{} @@ -172,26 +191,20 @@ func TestSuiteMethod(t *testing.T) { methodType = suiteMethod(method) assert.Nil(t, methodType) - // replyType != Ptr - method, ok = reflect.TypeOf(s1).MethodByName("Method2") - assert.True(t, ok) - methodType = suiteMethod(method) - assert.Nil(t, methodType) - // Reply not exported - method, ok = reflect.TypeOf(s1).MethodByName("Method3") + method, ok = reflect.TypeOf(s1).MethodByName("Method2") assert.True(t, ok) methodType = suiteMethod(method) assert.Nil(t, methodType) // no return - method, ok = reflect.TypeOf(s1).MethodByName("Method4") + method, ok = reflect.TypeOf(s1).MethodByName("Method3") assert.True(t, ok) methodType = suiteMethod(method) assert.Nil(t, methodType) // return value is not error - method, ok = reflect.TypeOf(s1).MethodByName("Method5") + method, ok = reflect.TypeOf(s1).MethodByName("Method4") assert.True(t, ok) methodType = suiteMethod(method) assert.Nil(t, methodType) diff --git a/common/url.go b/common/url.go index 2429653002967fdf2cb917d9e52c2f8323c6bba3..6d4eef00b4e7cf1be33b7d5fafdbfaf57212b8ba 100644 --- a/common/url.go +++ b/common/url.go @@ -18,9 +18,11 @@ package common import ( + "bytes" "context" "encoding/base64" "fmt" + "math" "net" "net/url" "strconv" @@ -128,16 +130,14 @@ func WithPort(port string) option { } } -//func WithPath(path string) option { -// return func(url *URL) { -// url.Path = path -// } -//} - -func NewURLWithOptions(service string, opts ...option) *URL { - url := &URL{ - Path: "/" + service, +func WithPath(path string) option { + return func(url *URL) { + url.Path = "/" + strings.TrimPrefix(path, "/") } +} + +func NewURLWithOptions(opts ...option) *URL { + url := &URL{} for _, opt := range opts { opt(url) } @@ -205,32 +205,27 @@ func NewURL(ctx context.Context, urlString string, opts ...option) (URL, error) return s, nil } -// -//func (c URL) Key() string { -// return fmt.Sprintf( -// "%s://%s:%s@%s:%s/%s", -// c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path) -//} - func (c URL) URLEqual(url URL) bool { c.Ip = "" c.Port = "" url.Ip = "" url.Port = "" - if c.Key() != url.Key() { + cGroup := c.GetParam(constant.GROUP_KEY, "") + urlGroup := url.GetParam(constant.GROUP_KEY, "") + cKey := c.Key() + urlKey := url.Key() + + if cGroup == constant.ANY_VALUE { + cKey = strings.Replace(cKey, "group=*", "group="+urlGroup, 1) + } else if urlGroup == constant.ANY_VALUE { + urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1) + } + if cKey != urlKey { return false } return true } -//func (c SubURL) String() string { -// return fmt.Sprintf( -// "DefaultServiceURL{protocol:%s, Location:%s, Path:%s, Ip:%s, Port:%s, "+ -// "Timeout:%s, Version:%s, Group:%s, Params:%+v}", -// c.protocol, c.Location, c.Path, c.Ip, c.Port, -// c.Timeout, c.Version, c.Group, c.Params) -//} - func (c URL) String() string { buildString := fmt.Sprintf( "%s://%s:%s@%s:%s%s?", @@ -241,10 +236,33 @@ func (c URL) String() string { func (c URL) Key() string { buildString := fmt.Sprintf( - "%s://%s:%s@%s:%s/%s?group=%s&version=%s", - c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")), c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, constant.DEFAULT_VERSION)) - + "%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s", + c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, "")) return buildString + //return c.ServiceKey() +} + +func (c URL) ServiceKey() string { + intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) + if intf == "" { + return "" + } + buf := &bytes.Buffer{} + group := c.GetParam(constant.GROUP_KEY, "") + if group != "" { + buf.WriteString(group) + buf.WriteString("/") + } + + buf.WriteString(intf) + + version := c.GetParam(constant.VERSION_KEY, "") + if version != "" && version != "0.0.0" { + buf.WriteString(":") + buf.WriteString(version) + } + + return buf.String() } func (c URL) Context() context.Context { @@ -252,11 +270,11 @@ func (c URL) Context() context.Context { } func (c URL) Service() string { - service := strings.TrimPrefix(c.Path, "/") + service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if service != "" { return service } else if c.SubURL != nil { - service = strings.TrimPrefix(c.SubURL.Path, "/") + service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if service != "" { //if url.path is "" then return suburl's path, special for registry Url return service } @@ -332,6 +350,15 @@ func (c URL) GetMethodParamInt(method string, key string, d int64) int64 { return int64(r) } +func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { + r := c.GetMethodParamInt(method, key, math.MinInt64) + if r == math.MinInt64 { + return c.GetParamInt(key, d) + } + + return r +} + func (c URL) GetMethodParam(method string, key string, d string) string { var r string if r = c.Params.Get("methods." + method + "." + key); r == "" { diff --git a/common/url_test.go b/common/url_test.go index 1093516ce5f5ccedcee2ab22898e0124eae0eca2..bae37237f1b7c70a0b5214acdc4252e5382c7ab3 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -24,15 +24,18 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/constant" "github.com/stretchr/testify/assert" ) +import ( + "github.com/apache/dubbo-go/common/constant" +) + func TestNewURLWithOptions(t *testing.T) { methods := []string{"Methodone,methodtwo"} params := url.Values{} params.Set("key", "value") - u := NewURLWithOptions("com.test.Service", + u := NewURLWithOptions(WithPath("com.test.Service"), WithUsername("username"), WithPassword("password"), WithProtocol("testprotocol"), diff --git a/config/config_loader.go b/config/config_loader.go index 2560b78bd15686d221cdb7f2e0e613954688e20e..e8c3951bcf6f9997731eeb8ef2a909598cba96df 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -33,6 +33,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/version" @@ -203,8 +204,13 @@ func loadProtocol(protocolsIds string, protocols []ProtocolConfig) []ProtocolCon return returnProtocols } +var ( + refConfigs map[string]*ReferenceConfig // record reference config loaded + srvConfigs map[string]*ServiceConfig // record service config loaded +) + // Dubbo Init -func Load() (map[string]*ReferenceConfig, map[string]*ServiceConfig) { +func Load() (int, int) { var refMap map[string]*ReferenceConfig var srvMap map[string]*ServiceConfig @@ -217,13 +223,13 @@ func Load() (map[string]*ReferenceConfig, map[string]*ServiceConfig) { for index := 0; index < length; index++ { con := &consumerConfig.References[index] rpcService := GetConsumerService(con.InterfaceName) + con.Refer() + refMap[con.InterfaceName] = con if rpcService == nil { logger.Warnf("%s is not exsist!", con.InterfaceName) continue } - con.Refer() con.Implement(rpcService) - refMap[con.InterfaceName] = con } //wait for invoker is available, if wait over default 3s, then panic @@ -278,5 +284,17 @@ func Load() (map[string]*ReferenceConfig, map[string]*ServiceConfig) { } } - return refMap, srvMap + refConfigs = refMap + srvConfigs = srvMap + return len(refMap), len(srvMap) +} + +// get rpc service for consumer +func GetRPCService(name string) common.RPCService { + return refConfigs[name].GetRPCService() +} + +// create rpc service for consumer +func RPCService(service common.RPCService) { + refConfigs[service.Service()].Implement(service) } diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 7c3343fd35a1ce9d3e519d3f45435be775755bdf..9d062ce2fc5a043ceb07f9175ba5f62eba055abd 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -59,17 +59,25 @@ func TestLoad(t *testing.T) { doInit() doinit() - SetConsumerService(&MockService{}) - SetProviderService(&MockService{}) + ms := &MockService{} + SetConsumerService(ms) + SetProviderService(ms) extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) extension.SetProxyFactory("default", proxy_factory.NewDefaultProxyFactory) consumerConfig.References[0].Registries = []ConfigRegistry{"shanghai_reg1"} - refConfigs, svcConfigs := Load() - assert.NotEqual(t, 0, len(refConfigs)) - assert.NotEqual(t, 0, len(svcConfigs)) + refLen, svcLen := Load() + assert.NotEqual(t, 0, refLen) + assert.NotEqual(t, 0, svcLen) + + assert.Equal(t, ms, GetRPCService(ms.Service())) + ms2 := &struct { + MockService + }{} + RPCService(ms2) + assert.NotEqual(t, ms2, GetRPCService(ms2.Service())) conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} diff --git a/config/reference_config.go b/config/reference_config.go index 229954563cb916ffec004e87e80090d728185a9b..ffac5b4267b94b717da31980c13f8c63a28b7a66 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -77,7 +77,7 @@ func (refconfig *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) erro } func (refconfig *ReferenceConfig) Refer() { - url := common.NewURLWithOptions(refconfig.InterfaceName, common.WithProtocol(refconfig.Protocol), common.WithParams(refconfig.getUrlMap())) + url := common.NewURLWithOptions(common.WithPath(refconfig.InterfaceName), common.WithProtocol(refconfig.Protocol), common.WithParams(refconfig.getUrlMap())) //1. user specified URL, could be peer-to-peer address, or register center's address. if refconfig.Url != "" { @@ -92,7 +92,7 @@ func (refconfig *ReferenceConfig) Refer() { refconfig.urls = append(refconfig.urls, &serviceUrl) } else { if serviceUrl.Path == "" { - serviceUrl.Path = refconfig.InterfaceName + "/" + serviceUrl.Path = "/" + refconfig.InterfaceName } // merge url need to do newUrl := common.MergeUrl(serviceUrl, url) diff --git a/config/service_config.go b/config/service_config.go index 9db0f99facbc69671153a713b5ff4a347e32d749..e225c954231ca647bfe18167c41b005fb2054e3d 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -19,6 +19,7 @@ package config import ( "context" + "fmt" "net/url" "strconv" "strings" @@ -37,6 +38,7 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/protocolwrapper" ) type ServiceConfig struct { @@ -102,30 +104,40 @@ func (srvconfig *ServiceConfig) Export() error { //if contextPath == "" { // contextPath = providerConfig.Path //} - url := common.NewURLWithOptions(srvconfig.InterfaceName, + url := common.NewURLWithOptions(common.WithPath(srvconfig.InterfaceName), common.WithProtocol(proto.Name), common.WithIp(proto.Ip), common.WithPort(proto.Port), common.WithParams(urlMap), common.WithMethods(strings.Split(methods, ","))) - for _, regUrl := range regUrls { - regUrl.SubURL = url - - srvconfig.cacheMutex.Lock() - if srvconfig.cacheProtocol == nil { - logger.Infof("First load the registry protocol!") - srvconfig.cacheProtocol = extension.GetProtocol("registry") + if len(regUrls) > 0 { + for _, regUrl := range regUrls { + regUrl.SubURL = url + + srvconfig.cacheMutex.Lock() + if srvconfig.cacheProtocol == nil { + logger.Infof(fmt.Sprintf("First load the registry protocol , url is {%v}!", url)) + srvconfig.cacheProtocol = extension.GetProtocol("registry") + } + srvconfig.cacheMutex.Unlock() + + invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) + exporter := srvconfig.cacheProtocol.Export(invoker) + if exporter == nil { + panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, url))) + } + srvconfig.exporters = append(srvconfig.exporters, exporter) } - srvconfig.cacheMutex.Unlock() - - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) - exporter := srvconfig.cacheProtocol.Export(invoker) + } else { + invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*url) + exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { - panic(perrors.New("New exporter error")) + panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", url))) } srvconfig.exporters = append(srvconfig.exporters, exporter) } + } return nil diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..8115fed8203438c9371a1b7fa20815843a2e81fd --- /dev/null +++ b/config_center/dynamic_configuration.go @@ -0,0 +1,57 @@ +/* + * 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 config_center + +import ( + "time" +) +import ( + "github.com/apache/dubbo-go/remoting" +) + +////////////////////////////////////////// +// DynamicConfiguration +////////////////////////////////////////// +const DEFAULT_GROUP = "dubbo" +const DEFAULT_CONFIG_TIMEOUT = "10s" + +type DynamicConfiguration interface { + AddListener(string, remoting.ConfigurationListener, ...Option) + RemoveListener(string, remoting.ConfigurationListener, ...Option) + GetConfig(string, ...Option) string + GetConfigs(string, ...Option) string +} + +type Options struct { + Group string + Timeout time.Duration +} + +type Option func(*Options) + +func WithGroup(group string) Option { + return func(opt *Options) { + opt.Group = group + } +} + +func WithTimeout(time time.Duration) Option { + return func(opt *Options) { + opt.Timeout = time + } +} diff --git a/config_center/zookeeper/dynamic_configuration.go b/config_center/zookeeper/dynamic_configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..c998c586fb0a22223acaee634cb5f187e04223fc --- /dev/null +++ b/config_center/zookeeper/dynamic_configuration.go @@ -0,0 +1,134 @@ +/* + * 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 zookeeper + +import ( + "sync" +) +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/zookeeper" +) + +const ZK_CLIENT = "zk config_center" + +type ZookeeperDynamicConfiguration struct { + url common.URL + rootPath string + wg sync.WaitGroup + cltLock sync.Mutex + done chan struct{} + client *zookeeper.ZookeeperClient + + listenerLock sync.Mutex + listener *zookeeper.ZkEventListener +} + +func NewZookeeperDynamicConfiguration(url common.URL) (config_center.DynamicConfiguration, error) { + c := &ZookeeperDynamicConfiguration{ + url: url, + rootPath: "/" + url.GetParam(constant.CONFIG_NAMESPACE_KEY, config_center.DEFAULT_GROUP) + "/config", + } + err := zookeeper.ValidateZookeeperClient(c, zookeeper.WithZkName(ZK_CLIENT)) + if err != nil { + return nil, err + } + c.wg.Add(1) + go zookeeper.HandleClientRestart(c) + + c.listener = zookeeper.NewZkEventListener(c.client) + //c.configListener = NewRegistryConfigurationListener(c.client, c) + //c.dataListener = NewRegistryDataListener(c.configListener) + return c, nil + +} + +func (*ZookeeperDynamicConfiguration) AddListener(key string, listener remoting.ConfigurationListener, opions ...config_center.Option) { + +} + +func (*ZookeeperDynamicConfiguration) RemoveListener(key string, listener remoting.ConfigurationListener, opions ...config_center.Option) { + +} + +func (*ZookeeperDynamicConfiguration) GetConfig(key string, opions ...config_center.Option) string { + return "" +} + +func (*ZookeeperDynamicConfiguration) GetConfigs(key string, opions ...config_center.Option) string { + return "" +} + +func (r *ZookeeperDynamicConfiguration) ZkClient() *zookeeper.ZookeeperClient { + return r.client +} + +func (r *ZookeeperDynamicConfiguration) SetZkClient(client *zookeeper.ZookeeperClient) { + r.client = client +} + +func (r *ZookeeperDynamicConfiguration) ZkClientLock() *sync.Mutex { + return &r.cltLock +} + +func (r *ZookeeperDynamicConfiguration) WaitGroup() *sync.WaitGroup { + return &r.wg +} + +func (r *ZookeeperDynamicConfiguration) GetDone() chan struct{} { + return r.done +} + +func (r *ZookeeperDynamicConfiguration) GetUrl() common.URL { + return r.url +} + +func (r *ZookeeperDynamicConfiguration) Destroy() { + if r.listener != nil { + r.listener.Close() + } + close(r.done) + r.wg.Wait() + r.closeConfigs() +} + +func (r *ZookeeperDynamicConfiguration) IsAvailable() bool { + select { + case <-r.done: + return false + default: + return true + } +} + +func (r *ZookeeperDynamicConfiguration) closeConfigs() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + logger.Infof("begin to close provider zk client") + // 先关闭旧client,以关闭tmp node + r.client.Close() + r.client = nil +} + +func (r *ZookeeperDynamicConfiguration) RestartCallBack() bool { + return true +} diff --git a/examples/README.md b/examples/README.md index c71e843e060a198f23b747fa80adee81554bcc0d..497926f1d3822f7b7d33640fa18cbc2bd65bdbb9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,19 +19,19 @@ sh build.sh ``` go server + +* sh ./assembly/\[os]/\[environment].sh ```bash cd dubbo/go-server -#linux, mac windows represent the os -#release, dev and test represent the environment -sh ./assembly/linux/release.sh +# $ARCH = [linux, mac, windows] and $ENV = [dev, release, test] +sh ./assembly/$ARCH/$ENV.sh ``` go client ```bash cd dubbo/go-client -#linux, mac windows represent the os -#release, dev and test represent the environment -sh ./assembly/linux/release.sh +# $ARCH = [linux, mac, windows] and $ENV = [dev, release, test] +sh ./assembly/$ARCH/$ENV.sh ``` #### Run by these command: @@ -53,8 +53,6 @@ sh ./bin/server.sh start ``` go server -> It must not listen on IP 127.0.0.1 when called by java-client. -> You should change IP in dubbo/go-server/target/linux/user_info_server-0.3.1-20190517-0930-release/conf/server.yml ```bash cd dubbo/go-server/target/linux/user_info_server-0.3.1-20190517-0930-release #conf suffix appoint config file, @@ -66,10 +64,10 @@ sh ./bin/load.sh start [conf suffix] go client ```bash cd dubbo/go-client/target/linux/user_info_client-0.3.1-20190517-0921-release -#conf suffix appoint config file, -#such as client_zookeeper.yml when "sh ./bin/load.sh start is zookeeper", -#default client.yml -sh ./bin/load_user_info_client.sh start [conf suffix] +# $SUFFIX is a suffix of config file, +# such as client_zookeeper.yml when $SUFFIX = zookeeper", +# if $SUFFIX = "", config file is client.yml +sh ./bin/load_user_info_client.sh start $SUFFIX ``` ## jsonrpc diff --git a/examples/dubbo/go-client/app/client.go b/examples/dubbo/go-client/app/client.go index e65d93c59754975854f3e456e12c38562b4ab3bb..8fea06a05b18012a0aa00ce3f0d227d8bfac97e5 100644 --- a/examples/dubbo/go-client/app/client.go +++ b/examples/dubbo/go-client/app/client.go @@ -27,11 +27,11 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/logger" - hessian "github.com/dubbogo/hessian2" + "github.com/dubbogo/hessian2" ) import ( + "github.com/apache/dubbo-go/common/logger" _ "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/config" _ "github.com/apache/dubbo-go/protocol/dubbo" @@ -57,13 +57,13 @@ func main() { hessian.RegisterJavaEnum(Gender(WOMAN)) hessian.RegisterPOJO(&User{}) - conMap, _ := config.Load() - if conMap == nil { + conLen, _ := config.Load() + if conLen == 0 { panic("conMap is nil") } println("\n\n\necho") - res, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).Echo(context.TODO(), "OK") + res, err := userProvider.Echo(context.TODO(), "OK") if err != nil { panic(err) } @@ -73,21 +73,21 @@ func main() { println("\n\n\nstart to test dubbo") user := &User{} - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser(context.TODO(), []interface{}{"A003"}, user) + err = userProvider.GetUser(context.TODO(), []interface{}{"A003"}, user) if err != nil { panic(err) } println("response result: %v", user) println("\n\n\nstart to test dubbo - GetUser0") - ret, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser0("A003", "Moorse") + ret, err := userProvider.GetUser0("A003", "Moorse") if err != nil { panic(err) } println("response result: %v", ret) println("\n\n\nstart to test dubbo - GetUsers") - ret1, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUsers([]interface{}{[]interface{}{"A002", "A003"}}) + ret1, err := userProvider.GetUsers([]interface{}{[]interface{}{"A002", "A003"}}) if err != nil { panic(err) } @@ -95,15 +95,29 @@ func main() { println("\n\n\nstart to test dubbo - getUser") user = &User{} - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser2(context.TODO(), []interface{}{1}, user) + var i int32 = 1 + err = userProvider.GetUser2(context.TODO(), []interface{}{i}, user) + if err != nil { + panic(err) + } + println("response result: %v", user) + + println("\n\n\nstart to test dubbo - GetUser3") + err = userProvider.GetUser3() + if err != nil { + panic(err) + } + println("succ!") + + println("\n\n\nstart to test dubbo - getErr") + user = &User{} + err = userProvider.GetErr(context.TODO(), []interface{}{"A003"}, user) if err != nil { - println("getUser - error: %v", err) - } else { - println("response result: %v", user) + println("getErr - error: %v", err) } println("\n\n\nstart to test dubbo illegal method") - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser1(context.TODO(), []interface{}{"A003"}, user) + err = userProvider.GetUser1(context.TODO(), []interface{}{"A003"}, user) if err != nil { panic(err) } diff --git a/examples/dubbo/go-client/app/user.go b/examples/dubbo/go-client/app/user.go index 81a7adb2af0a8afac4aec8c164db35f0b4d77b82..d491c3633384ad9ee6acdb2786d383e420f26db3 100644 --- a/examples/dubbo/go-client/app/user.go +++ b/examples/dubbo/go-client/app/user.go @@ -25,14 +25,19 @@ import ( ) import ( - "github.com/apache/dubbo-go/config" hessian "github.com/dubbogo/hessian2" ) +import ( + "github.com/apache/dubbo-go/config" +) + type Gender hessian.JavaEnum +var userProvider = new(UserProvider) + func init() { - config.SetConsumerService(new(UserProvider)) + config.SetConsumerService(userProvider) } const ( @@ -94,10 +99,12 @@ func (User) JavaClassName() string { type UserProvider struct { GetUsers func(req []interface{}) ([]interface{}, error) + GetErr func(ctx context.Context, req []interface{}, rsp *User) error GetUser func(ctx context.Context, req []interface{}, rsp *User) error GetUser0 func(id string, name string) (User, error) GetUser1 func(ctx context.Context, req []interface{}, rsp *User) error - GetUser2 func(ctx context.Context, req []interface{}, rsp *User) error `dubbo:"getUser"` + GetUser2 func(ctx context.Context, req []interface{}, rsp *User) error `dubbo:"getUser"` + GetUser3 func() error Echo func(ctx context.Context, req interface{}) (interface{}, error) // Echo represent EchoFilter will be used } diff --git a/examples/dubbo/go-client/profiles/dev/client.yml b/examples/dubbo/go-client/profiles/dev/client.yml index 3fcbd2cb15cd4217a8900a148753b8a7337ac553..2eae902e50b757886cf9a377291d017f4384978d 100644 --- a/examples/dubbo/go-client/profiles/dev/client.yml +++ b/examples/dubbo/go-client/profiles/dev/client.yml @@ -32,9 +32,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" +# version: "2.0" +# group: "as" # url: "dubbo://127.0.0.1:20000" interface : "com.ikurento.user.UserProvider" cluster: "failover" diff --git a/examples/dubbo/go-client/profiles/release/client.yml b/examples/dubbo/go-client/profiles/release/client.yml index 998f232fcac87da0b793ca2c062cf6cf6de8f6f3..d30a9907f460e5bd61d0f4f2df1c1e6f576030d1 100644 --- a/examples/dubbo/go-client/profiles/release/client.yml +++ b/examples/dubbo/go-client/profiles/release/client.yml @@ -32,9 +32,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" +# version: "2.0" +# group: "as" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : diff --git a/examples/dubbo/go-client/profiles/test/client.yml b/examples/dubbo/go-client/profiles/test/client.yml index 998f232fcac87da0b793ca2c062cf6cf6de8f6f3..d30a9907f460e5bd61d0f4f2df1c1e6f576030d1 100644 --- a/examples/dubbo/go-client/profiles/test/client.yml +++ b/examples/dubbo/go-client/profiles/test/client.yml @@ -32,9 +32,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" +# version: "2.0" +# group: "as" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : diff --git a/examples/dubbo/go-server/app/server.go b/examples/dubbo/go-server/app/server.go index db0196f63e2a43fcf06d73f15d925cf8af6f1867..2a032a624ca0fd636c587a32f0a604da6f7764b6 100644 --- a/examples/dubbo/go-server/app/server.go +++ b/examples/dubbo/go-server/app/server.go @@ -26,11 +26,11 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/logger" hessian "github.com/dubbogo/hessian2" ) import ( + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" _ "github.com/apache/dubbo-go/protocol/dubbo" _ "github.com/apache/dubbo-go/registry/protocol" @@ -58,8 +58,8 @@ func main() { hessian.RegisterPOJO(&User{}) // ------------ - _, proMap := config.Load() - if proMap == nil { + _, proLen := config.Load() + if proLen == 0 { panic("proMap is nil") } diff --git a/examples/dubbo/go-server/app/user.go b/examples/dubbo/go-server/app/user.go index 9ba663104240d5ee5ead9fcda841b9beab335a49..fcd9ea7b8677add705127b817799bcb4beb6dabb 100644 --- a/examples/dubbo/go-server/app/user.go +++ b/examples/dubbo/go-server/app/user.go @@ -25,12 +25,12 @@ import ( ) import ( + "github.com/dubbogo/hessian2" perrors "github.com/pkg/errors" ) import ( "github.com/apache/dubbo-go/config" - hessian "github.com/dubbogo/hessian2" ) type Gender hessian.JavaEnum @@ -159,6 +159,22 @@ func (u *UserProvider) GetUser0(id string, name string) (User, error) { return *user, err } +func (u *UserProvider) GetUser2(ctx context.Context, req []interface{}, rsp *User) error { + var err error + + println("req:%#v", req) + rsp.Id = strconv.Itoa(int(req[0].(int32))) + return err +} + +func (u *UserProvider) GetUser3() error { + return nil +} + +func (u *UserProvider) GetErr(ctx context.Context, req []interface{}, rsp *User) error { + return hessian.NewThrowable("exception") +} + func (u *UserProvider) GetUsers(req []interface{}) ([]interface{}, error) { var err error @@ -178,6 +194,12 @@ func (u *UserProvider) GetUsers(req []interface{}) ([]interface{}, error) { return []interface{}{user, user1}, err } +func (s *UserProvider) MethodMapper() map[string]string { + return map[string]string{ + "GetUser2": "getUser", + } +} + func (u *UserProvider) Service() string { return "com.ikurento.user.UserProvider" } diff --git a/examples/dubbo/go-server/profiles/dev/server.yml b/examples/dubbo/go-server/profiles/dev/server.yml index 06f3c9f885a917b71baea178d172f8782e174218..521f916faaaf41cc14fcc5e1eaf45447a788eb14 100644 --- a/examples/dubbo/go-server/profiles/dev/server.yml +++ b/examples/dubbo/go-server/profiles/dev/server.yml @@ -28,7 +28,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" @@ -42,7 +42,7 @@ services: protocols: - name: "dubbo" - ip : "127.0.0.1" + # ip : "127.0.0.1" port : 20000 #- name: "jsonrpc" # ip: "127.0.0.1" diff --git a/examples/dubbo/go-server/profiles/release/server.yml b/examples/dubbo/go-server/profiles/release/server.yml index 64587aa1adb9afac3b32c0e74779566ba88cbc7c..5431df1172f4230a112a70b9410258a46dafda9c 100644 --- a/examples/dubbo/go-server/profiles/release/server.yml +++ b/examples/dubbo/go-server/profiles/release/server.yml @@ -28,7 +28,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" @@ -42,7 +42,7 @@ services: protocols: - name: "dubbo" - ip : "127.0.0.1" + # ip : "127.0.0.1" port : 20000 #- name: "jsonrpc" # ip: "127.0.0.1" diff --git a/examples/dubbo/go-server/profiles/test/server.yml b/examples/dubbo/go-server/profiles/test/server.yml index 64587aa1adb9afac3b32c0e74779566ba88cbc7c..5431df1172f4230a112a70b9410258a46dafda9c 100644 --- a/examples/dubbo/go-server/profiles/test/server.yml +++ b/examples/dubbo/go-server/profiles/test/server.yml @@ -28,7 +28,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "dubbo" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" @@ -42,7 +42,7 @@ services: protocols: - name: "dubbo" - ip : "127.0.0.1" + # ip : "127.0.0.1" port : 20000 #- name: "jsonrpc" # ip: "127.0.0.1" diff --git a/examples/dubbo/java-client/src/main/java/com/ikurento/user/Consumer.java b/examples/dubbo/java-client/src/main/java/com/ikurento/user/Consumer.java index b410813c74a7432cbbe1f2e7627faba9ebb17e70..edf4c0d2b20a08c17241132cd03bf16a51b2fbb8 100644 --- a/examples/dubbo/java-client/src/main/java/com/ikurento/user/Consumer.java +++ b/examples/dubbo/java-client/src/main/java/com/ikurento/user/Consumer.java @@ -51,9 +51,24 @@ public class Consumer { System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] " + " UserInfo, Id:" + user2.getId() + ", name:" + user2.getName() + ", sex:" + user2.getSex().toString() + ", age:" + user2.getAge() + ", time:" + user2.getTime().toString()); + User user3 = userProvider.getUser(1); + System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] " + + " UserInfo, Id:" + user3.getId() + ", name:" + user3.getName() + ", sex:" + user3.getSex().toString() + + ", age:" + user3.getAge() + ", time:" + user3.getTime().toString()); + userProvider.GetUser3(); + System.out.println("GetUser3 succ"); + + User user9 = userProvider.GetUser1("A003"); } catch (Exception e) { + System.out.println("*************exception***********"); e.printStackTrace(); } + try { + userProvider.GetErr("A003"); + } catch (Throwable t) { + System.out.println("*************exception***********"); + t.printStackTrace(); + } } private void testGetUsers() throws Exception { diff --git a/examples/dubbo/java-client/src/main/java/com/ikurento/user/UserProvider.java b/examples/dubbo/java-client/src/main/java/com/ikurento/user/UserProvider.java index d5bce8105673a24d78ddd3a636788d1ccf8e57a6..ed765d4c0c6b6b9b382666b29d90c9dd25206f8d 100644 --- a/examples/dubbo/java-client/src/main/java/com/ikurento/user/UserProvider.java +++ b/examples/dubbo/java-client/src/main/java/com/ikurento/user/UserProvider.java @@ -20,7 +20,10 @@ import java.util.List; public interface UserProvider { User GetUser(String userId); - + User GetErr(String userId) throws Exception; + User GetUser1(String userId); + User getUser(int usercode); + void GetUser3(); List<User> GetUsers(List<String> userIdList); User GetUser0(String userId, String name); } diff --git a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProvider.java b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProvider.java index b75740bbcd26a6438d22f7d3bf08fa5e316f7aa7..24567d23be662312a4750f07b605c685a8dfa5fe 100644 --- a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProvider.java +++ b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProvider.java @@ -12,8 +12,12 @@ public interface UserProvider { List<User> GetUsers(List<String> userIdList); + void GetUser3(); + User GetUser0(String userId, String name); + User GetErr(String userId) throws Exception; + Map<String, User> GetUserMap(List<String> userIdList); User getUser(int usercode); diff --git a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java index f0d7faf3df40c3d422407ddf4d2f2c501e1efdf7..d600545c5084a40f1318e47a6a1c20bfcd6d36bc 100644 --- a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java +++ b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java @@ -35,7 +35,11 @@ public class UserProviderAnotherImpl implements UserProvider { public User GetUser0(String userId, String name) { return new User(userId, name, 48); } - + public void GetUser3() { + } + public User GetErr(String userId) throws Exception { + throw new Exception("exception"); + } public List<User> GetUsers(ArrayList<String> userIdList) { Iterator it = userIdList.iterator(); List<User> userList = new ArrayList<User>(); diff --git a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java index 1311bd807d59d8336f4ada1296d3a05abd31a548..47a4e2d9732aa8d8d9279d47af5bb4fb3db37195 100644 --- a/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java +++ b/examples/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java @@ -32,6 +32,9 @@ public class UserProviderImpl implements UserProvider { public User GetUser(String userId) { return new User(userId, "zhangsan", 18); } + public User GetErr(String userId) throws Exception { + throw new Exception("exception"); + } public User GetUser0(String userId, String name) { return new User(userId, name, 18); } @@ -53,6 +56,9 @@ public class UserProviderImpl implements UserProvider { return userList; } + public void GetUser3() { + } + public Map<String, User> GetUserMap(List<String> userIdList) { Iterator it = userIdList.iterator(); Map<String, User> map = new HashMap<String, User>(); diff --git a/examples/dubbo/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml b/examples/dubbo/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml index b3a1b19d6764ca6db895719709412c07b348f13e..f8dd13a833e6095485d0504e21cec272a3c9a288 100644 --- a/examples/dubbo/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml +++ b/examples/dubbo/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml @@ -30,9 +30,9 @@ <dubbo:protocol id="dubbo" name="dubbo" host="127.0.0.1" port="20010" /> <dubbo:protocol id="jsonrpc" name="jsonrpc" host="127.0.0.1" port="10010" /> <!-- 声明需要暴露的服务接口 --> - <dubbo:service registry="ikurento,ikurento2" timeout="3000" interface="com.ikurento.user.UserProvider" ref="demoService"/> - <dubbo:service registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" version="2.0"/> - <dubbo:service registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" group="as" version="2.0"/> + <dubbo:service id="aaa" registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="demoService"/> + <dubbo:service id="bbb" registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" version="2.0"/> + <dubbo:service id="ccc" registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" group="as" version="2.0"/> <bean id="demoService" class="com.ikurento.user.UserProviderImpl" /> <bean id="otherService" class="com.ikurento.user.UserProviderAnotherImpl"/> diff --git a/examples/jsonrpc/go-client/app/client.go b/examples/jsonrpc/go-client/app/client.go index dcefd9762425afca996c5caf8217e5b921ac74fa..4c7b5146f9682867e50549d6dfa419f430c5c558 100644 --- a/examples/jsonrpc/go-client/app/client.go +++ b/examples/jsonrpc/go-client/app/client.go @@ -28,9 +28,6 @@ import ( import ( "github.com/apache/dubbo-go/common/logger" -) - -import ( _ "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/config" _ "github.com/apache/dubbo-go/protocol/jsonrpc" @@ -52,13 +49,13 @@ var ( // export APP_LOG_CONF_FILE="xxx" func main() { - conMap, _ := config.Load() - if conMap == nil { + conLen, _ := config.Load() + if conLen == 0 { panic("conMap is nil") } println("\n\n\necho") - res, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).Echo(context.TODO(), "OK") + res, err := userProvider.Echo(context.TODO(), "OK") if err != nil { println("echo - error: %v", err) } else { @@ -69,21 +66,21 @@ func main() { println("\n\n\nstart to test jsonrpc") user := &JsonRPCUser{} - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser(context.TODO(), []interface{}{"A003"}, user) + err = userProvider.GetUser(context.TODO(), []interface{}{"A003"}, user) if err != nil { panic(err) } println("response result: %v", user) println("\n\n\nstart to test jsonrpc - GetUser0") - ret, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser0("A003", "Moorse") + ret, err := userProvider.GetUser0("A003", "Moorse") if err != nil { panic(err) } println("response result: %v", ret) println("\n\n\nstart to test jsonrpc - GetUsers") - ret1, err := conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUsers([]interface{}{[]interface{}{"A002", "A003"}}) + ret1, err := userProvider.GetUsers([]interface{}{[]interface{}{"A002", "A003"}}) if err != nil { panic(err) } @@ -91,15 +88,21 @@ func main() { println("\n\n\nstart to test jsonrpc - getUser") user = &JsonRPCUser{} - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser2(context.TODO(), []interface{}{1}, user) + err = userProvider.GetUser2(context.TODO(), []interface{}{1}, user) if err != nil { - println("getUser - error: %v", err) - } else { - println("response result: %v", user) + panic(err) + } + println("response result: %v", user) + + println("\n\n\nstart to test jsonrpc - GetUser3") + err = userProvider.GetUser3() + if err != nil { + panic(err) } + println("succ!") println("\n\n\nstart to test jsonrpc illegal method") - err = conMap["com.ikurento.user.UserProvider"].GetRPCService().(*UserProvider).GetUser1(context.TODO(), []interface{}{"A003"}, user) + err = userProvider.GetUser1(context.TODO(), []interface{}{"A003"}, user) if err != nil { panic(err) } diff --git a/examples/jsonrpc/go-client/app/user.go b/examples/jsonrpc/go-client/app/user.go index 6e2a97081ce74b28424e9faa8a74194cf9ea141f..ca98b1af0b3c1379c73623162546db9fb4fc95d6 100644 --- a/examples/jsonrpc/go-client/app/user.go +++ b/examples/jsonrpc/go-client/app/user.go @@ -27,8 +27,10 @@ import ( "github.com/apache/dubbo-go/config" ) +var userProvider = new(UserProvider) + func init() { - config.SetConsumerService(new(UserProvider)) + config.SetConsumerService(userProvider) } type JsonRPCUser struct { @@ -52,7 +54,8 @@ type UserProvider struct { GetUser0 func(id string, name string) (JsonRPCUser, error) GetUser1 func(ctx context.Context, req []interface{}, rsp *JsonRPCUser) error GetUser2 func(ctx context.Context, req []interface{}, rsp *JsonRPCUser) error `dubbo:"getUser"` - Echo func(ctx context.Context, req interface{}) (interface{}, error) // Echo represent EchoFilter will be used + GetUser3 func() error + Echo func(ctx context.Context, req interface{}) (interface{}, error) // Echo represent EchoFilter will be used } func (u *UserProvider) Service() string { diff --git a/examples/jsonrpc/go-client/profiles/dev/client.yml b/examples/jsonrpc/go-client/profiles/dev/client.yml index 020adc5e6bd1294a8174f5c0e663f379fe03c88e..e82a200e1ce8ea85ea98773fb562f46ca250e80f 100644 --- a/examples/jsonrpc/go-client/profiles/dev/client.yml +++ b/examples/jsonrpc/go-client/profiles/dev/client.yml @@ -33,9 +33,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" +# version : "2.0" +# group: "as" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : diff --git a/examples/jsonrpc/go-client/profiles/release/client.yml b/examples/jsonrpc/go-client/profiles/release/client.yml index 3e3bd63f5b2a73312936fc6dbc07404bcbe11687..f7e7df4036ff7a0ba70587542bb61592126f6d09 100644 --- a/examples/jsonrpc/go-client/profiles/release/client.yml +++ b/examples/jsonrpc/go-client/profiles/release/client.yml @@ -34,9 +34,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" +# version : "2.0" +# group: "as" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : diff --git a/examples/jsonrpc/go-client/profiles/test/client.yml b/examples/jsonrpc/go-client/profiles/test/client.yml index e79f7f47f72f598775be0c226b2f63a3d334f8fe..e4a6c4b166feddb042ee2193e5b6569703976e6e 100644 --- a/examples/jsonrpc/go-client/profiles/test/client.yml +++ b/examples/jsonrpc/go-client/profiles/test/client.yml @@ -33,9 +33,11 @@ registries : references: - registries : - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" +# version : "2.0" +# group: "as" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : diff --git a/examples/jsonrpc/go-server/app/server.go b/examples/jsonrpc/go-server/app/server.go index 45692b4d1259ef0a8a8c4d784575488184289658..ba747497bf608deadcb4e8392e3def30bebfc6f5 100644 --- a/examples/jsonrpc/go-server/app/server.go +++ b/examples/jsonrpc/go-server/app/server.go @@ -48,8 +48,8 @@ var ( // export APP_LOG_CONF_FILE="xxx" func main() { - _, proMap := config.Load() - if proMap == nil { + _, proLen := config.Load() + if proLen == 0 { panic("proMap is nil") } diff --git a/examples/jsonrpc/go-server/app/user.go b/examples/jsonrpc/go-server/app/user.go index fbe6f3339c212d2bd42d52b6bbf7c7fcec6fb9c3..e86d915417cc54b05faa25ebaa06dae2c5fb6dd1 100644 --- a/examples/jsonrpc/go-server/app/user.go +++ b/examples/jsonrpc/go-server/app/user.go @@ -20,6 +20,7 @@ package main import ( "context" "fmt" + "strconv" "time" ) @@ -127,6 +128,19 @@ func (u *UserProvider) GetUser0(id string, name string) (User, error) { return *user, err } +func (u *UserProvider) GetUser2(ctx context.Context, req []interface{}, rsp *User) error { + var err error + + println("req:%#v", req) + rsp.Id = strconv.FormatFloat(req[0].(float64), 'f', 0, 64) + rsp.Sex = Gender(MAN).String() + return err +} + +func (u *UserProvider) GetUser3() error { + return nil +} + func (u *UserProvider) GetUsers(req []interface{}) ([]User, error) { var err error @@ -146,6 +160,12 @@ func (u *UserProvider) GetUsers(req []interface{}) ([]User, error) { return []User{*user, *user1}, err } +func (s *UserProvider) MethodMapper() map[string]string { + return map[string]string{ + "GetUser2": "getUser", + } +} + func (u *UserProvider) Service() string { return "com.ikurento.user.UserProvider" } diff --git a/examples/jsonrpc/go-server/profiles/dev/server.yml b/examples/jsonrpc/go-server/profiles/dev/server.yml index 0175847a68c19427a35d000f6a7e6d215fd8cf64..70e62b20c8f1a323486f0468463d10241cf801b4 100644 --- a/examples/jsonrpc/go-server/profiles/dev/server.yml +++ b/examples/jsonrpc/go-server/profiles/dev/server.yml @@ -28,7 +28,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" diff --git a/examples/jsonrpc/go-server/profiles/release/server.yml b/examples/jsonrpc/go-server/profiles/release/server.yml index 3c70e82483be227d60e7e9806ed19d3f371ded11..1ded448a136807bfd602fa2f0bdf93d7f4bd46d5 100644 --- a/examples/jsonrpc/go-server/profiles/release/server.yml +++ b/examples/jsonrpc/go-server/profiles/release/server.yml @@ -28,7 +28,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" diff --git a/examples/jsonrpc/go-server/profiles/test/server.yml b/examples/jsonrpc/go-server/profiles/test/server.yml index a8ad5746b35804cc0154830fa2d59be249bbc46b..5948fd78f6dbda86e63211cdbfac663570a94dbd 100644 --- a/examples/jsonrpc/go-server/profiles/test/server.yml +++ b/examples/jsonrpc/go-server/profiles/test/server.yml @@ -27,7 +27,7 @@ registries : services: - registries: - "hangzhouzk" - - "shanghaizk" +# - "shanghaizk" protocol : "jsonrpc" # 相当于dubbo.xml中的interface interface : "com.ikurento.user.UserProvider" diff --git a/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/Consumer.java b/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/Consumer.java index b2b8e95f94b5112721e12bf738b05bdd3bd9c419..ddf899aa10979d65f9c88bc0b79ccbb065812417 100644 --- a/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/Consumer.java +++ b/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/Consumer.java @@ -55,6 +55,13 @@ public class Consumer { System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] " + " UserInfo, Id:" + user2.getId() + ", name:" + user2.getName() + ", sex:" + user2.getSex().toString() + ", age:" + user2.getAge() + ", time:" + user2.getTime().toString()); + User user3 = userProvider.getUser(1); + System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] " + + " UserInfo, Id:" + user3.getId() + ", name:" + user3.getName() + ", sex:" + user3.getSex().toString() + + ", age:" + user3.getAge() + ", time:" + user3.getTime().toString()); + + userProvider.GetUser3(); + System.out.println("GetUser3 succ"); } catch (Exception e) { e.printStackTrace(); } diff --git a/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/UserProvider.java b/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/UserProvider.java index d5bce8105673a24d78ddd3a636788d1ccf8e57a6..93e7aadf02a869163f41a8f3dda84d2ebf18a475 100644 --- a/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/UserProvider.java +++ b/examples/jsonrpc/java-client/src/main/java/com/ikurento/user/UserProvider.java @@ -20,7 +20,8 @@ import java.util.List; public interface UserProvider { User GetUser(String userId); - + User getUser(int usercode); + void GetUser3(); List<User> GetUsers(List<String> userIdList); User GetUser0(String userId, String name); } diff --git a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProvider.java b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProvider.java index b75740bbcd26a6438d22f7d3bf08fa5e316f7aa7..a74b2222018988e13b29f5d3b74cee00815ec40e 100644 --- a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProvider.java +++ b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProvider.java @@ -14,6 +14,8 @@ public interface UserProvider { User GetUser0(String userId, String name); + void GetUser3(); + Map<String, User> GetUserMap(List<String> userIdList); User getUser(int usercode); diff --git a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java index 157253575b9e5e75dadaaeaffa1e256374fefa5d..753a6f89a5f60e0f4884711d4c3b79e52ed2f094 100644 --- a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java +++ b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderAnotherImpl.java @@ -32,9 +32,12 @@ public class UserProviderAnotherImpl implements UserProvider { return new User(userId, "Joe", 48); } - public User GetUser0(String userId, String name) { - return new User(userId, name, 48); - } + public User GetUser0(String userId, String name) { + return new User(userId, name, 48); + } + + public void GetUser3() { + } public List<User> GetUsers(ArrayList<String> userIdList) { Iterator it = userIdList.iterator(); diff --git a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java index 25e97dd1c4482f2ff6ae7acb1ecb01a5ed66b328..960c678cf76cf4bafb3de9d5ce2a587b61aa1bac 100644 --- a/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java +++ b/examples/jsonrpc/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java @@ -78,7 +78,8 @@ public class UserProviderImpl implements UserProvider { public Map<String, User> queryAll() { return userMap; } - + public void GetUser3() { + } public User getUser(int userCode) { return new User(String.valueOf(userCode), "userCode get", 48); diff --git a/examples/jsonrpc/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml b/examples/jsonrpc/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml index b3a1b19d6764ca6db895719709412c07b348f13e..6eb03bc94bbf34b23b6df890b7c70568466ffad8 100644 --- a/examples/jsonrpc/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml +++ b/examples/jsonrpc/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml @@ -30,7 +30,7 @@ <dubbo:protocol id="dubbo" name="dubbo" host="127.0.0.1" port="20010" /> <dubbo:protocol id="jsonrpc" name="jsonrpc" host="127.0.0.1" port="10010" /> <!-- 声明需要暴露的服务接口 --> - <dubbo:service registry="ikurento,ikurento2" timeout="3000" interface="com.ikurento.user.UserProvider" ref="demoService"/> + <dubbo:service registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="demoService"/> <dubbo:service registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" version="2.0"/> <dubbo:service registry="ikurento" timeout="3000" interface="com.ikurento.user.UserProvider" ref="otherService" group="as" version="2.0"/> diff --git a/filter/impl/active_filter.go b/filter/impl/active_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..435bfe7488c520b14d770aea01b3c3baf4950056 --- /dev/null +++ b/filter/impl/active_filter.go @@ -0,0 +1,47 @@ +// Licensed 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. + +// @author yiji@apache.org +package impl + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +const active = "active" + +func init() { + extension.SetFilter(active, GetActiveFilter) +} + +type ActiveFilter struct { +} + +func (ef *ActiveFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking active filter. %v,%v", invocation.MethodName(), len(invocation.Arguments())) + + protocol.BeginCount(invoker.GetUrl(), invocation.MethodName()) + return invoker.Invoke(invocation) +} + +func (ef *ActiveFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + + protocol.EndCount(invoker.GetUrl(), invocation.MethodName()) + return result +} + +func GetActiveFilter() filter.Filter { + return &ActiveFilter{} +} diff --git a/filter/impl/echo_filter.go b/filter/impl/echo_filter.go index d2a00108bf2c842057af23dab4d4f09fbb2b0ed7..5eb5a37fa500bd8c180d879240d1c1e367df31ce 100644 --- a/filter/impl/echo_filter.go +++ b/filter/impl/echo_filter.go @@ -17,13 +17,10 @@ package impl -import ( - "github.com/apache/dubbo-go/common/logger" -) - import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/filter" "github.com/apache/dubbo-go/protocol" ) diff --git a/go.mod b/go.mod index 6a082e3594aa200f2c513741dfe338fc1afee90b..709124241c865e6469d19c7c67a8d275e5ade2c7 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/apache/dubbo-go require ( - github.com/dubbogo/getty v0.0.0-20190523180329-bdf5e640ea53 - github.com/dubbogo/hessian2 v0.0.0-20190526221400-d5610bbd0a41 + github.com/dubbogo/getty v1.0.7 + github.com/dubbogo/hessian2 v1.0.2 github.com/pkg/errors v0.8.1 github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec diff --git a/go.sum b/go.sum index 3579cf12dce2d18ab63451712ad6651e550511cc..f39e814c86a6d7b32e699ac36198f4965937f44c 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dubbogo/getty v0.0.0-20190523180329-bdf5e640ea53 h1:bniSNoC4xnAbrx4estwc9F0qkWnh6ZDsAS0y9d7mPos= -github.com/dubbogo/getty v0.0.0-20190523180329-bdf5e640ea53/go.mod h1:cRMSuoCmwc5lULFFnYZTxyCfZhObmRTNbS7XRnPNHSo= -github.com/dubbogo/hessian2 v0.0.0-20190526221400-d5610bbd0a41 h1:lNtW7+aN8oBdCoEuny0rOqOkL5duI4Cu3+G8vqibX48= -github.com/dubbogo/hessian2 v0.0.0-20190526221400-d5610bbd0a41/go.mod h1:XFGDn4oSZX26zkcfhkM/fCJrOqwQJxk/xgWW1KMJBKM= +github.com/dubbogo/getty v1.0.7 h1:5Hg+JwXyCKm9Yr4yJkm98ahhnoa8c2h6br5QJxwQ+YU= +github.com/dubbogo/getty v1.0.7/go.mod h1:cRMSuoCmwc5lULFFnYZTxyCfZhObmRTNbS7XRnPNHSo= +github.com/dubbogo/hessian2 v1.0.2 h1:Ka9Z32ZszGAdCpgrGuZQmwkT0qe1pd3o9r7ERCDnSlQ= +github.com/dubbogo/hessian2 v1.0.2/go.mod h1:XFGDn4oSZX26zkcfhkM/fCJrOqwQJxk/xgWW1KMJBKM= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -13,8 +13,6 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= -github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -33,7 +31,5 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/protocol/RpcStatus.go b/protocol/RpcStatus.go new file mode 100644 index 0000000000000000000000000000000000000000..b9f3e6ecb18583034e310471164773bebe689cd9 --- /dev/null +++ b/protocol/RpcStatus.go @@ -0,0 +1,71 @@ +// Licensed 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. + +// @author yiji@apache.org +package protocol + +import ( + "sync" + "sync/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +var ( + methodStatistics sync.Map // url -> { methodName : RpcStatus} +) + +type RpcStatus struct { + active int32 +} + +func (rpc *RpcStatus) GetActive() int32 { + return atomic.LoadInt32(&rpc.active) +} + +func GetStatus(url common.URL, methodName string) *RpcStatus { + identifier := url.Key() + methodMap, found := methodStatistics.Load(identifier) + if !found { + methodMap = &sync.Map{} + methodStatistics.Store(identifier, methodMap) + } + + methodActive := methodMap.(*sync.Map) + rpcStatus, found := methodActive.Load(methodName) + if !found { + rpcStatus = &RpcStatus{} + methodActive.Store(methodName, rpcStatus) + } + + status := rpcStatus.(*RpcStatus) + return status +} + +func BeginCount(url common.URL, methodName string) { + beginCount0(GetStatus(url, methodName)) +} + +func EndCount(url common.URL, methodName string) { + endCount0(GetStatus(url, methodName)) +} + +// private methods +func beginCount0(rpcStatus *RpcStatus) { + atomic.AddInt32(&rpcStatus.active, 1) +} + +func endCount0(rpcStatus *RpcStatus) { + atomic.AddInt32(&rpcStatus.active, -1) +} diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index d055ddac9abc2d046e6c10dc40ecbae23af35e27..d6155b6021a07e51e4d4a779e56aff5e62bd40ce 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -25,7 +25,7 @@ import ( import ( "github.com/dubbogo/getty" - hessian "github.com/dubbogo/hessian2" + "github.com/dubbogo/hessian2" perrors "github.com/pkg/errors" "go.uber.org/atomic" "gopkg.in/yaml.v2" @@ -201,6 +201,13 @@ func (c *Client) AsyncCall(addr string, svcUrl common.URL, method string, args i return perrors.WithStack(c.call(CT_TwoWay, addr, svcUrl, method, args, reply, callback, copts)) } +func (c *Client) GetPendingResponse(seq SequenceType) *PendingResponse { + c.pendingLock.RLock() + defer c.pendingLock.RUnlock() + + return c.pendingResponses[SequenceType(seq)] +} + func (c *Client) call(ct CallType, addr string, svcUrl common.URL, method string, args, reply interface{}, callback AsyncCallback, opts CallOptions) error { @@ -215,7 +222,7 @@ func (c *Client) call(ct CallType, addr string, svcUrl common.URL, method string p.Service.Path = strings.TrimPrefix(svcUrl.Path, "/") p.Service.Target = svcUrl.GetParam(constant.INTERFACE_KEY, "") p.Service.Interface = svcUrl.GetParam(constant.INTERFACE_KEY, "") - p.Service.Version = svcUrl.GetParam(constant.VERSION_KEY, constant.DEFAULT_VERSION) + p.Service.Version = svcUrl.GetParam(constant.VERSION_KEY, "") p.Service.Method = method p.Service.Timeout = opts.RequestTimeout if opts.SerialID == 0 { @@ -257,7 +264,7 @@ func (c *Client) call(ct CallType, addr string, svcUrl common.URL, method string } select { - case <-getty.GetTimeWheel().After(opts.ResponseTimeout): + case <-time.After(opts.ResponseTimeout): err = errClientReadTimeout c.removePendingResponse(SequenceType(rsp.seq)) case <-rsp.done: diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go index 1183bd00d212f0c49ed8ec70fabaa34e6e24701c..1ea9e4fb0e02a1e1234e8026f5a291398508133c 100644 --- a/protocol/dubbo/client_test.go +++ b/protocol/dubbo/client_test.go @@ -18,28 +18,22 @@ package dubbo import ( + "bytes" "context" "sync" "testing" "time" +) - hessian "github.com/dubbogo/hessian2" - - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/protocol" +import ( + "github.com/dubbogo/hessian2" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) -type ( - User struct { - Id string `json:"id"` - Name string `json:"name"` - } - - UserProvider struct { - user map[string]User - } +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" ) func TestClient_CallOneway(t *testing.T) { @@ -69,35 +63,53 @@ func TestClient_Call(t *testing.T) { c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) user := &User{} + //err := c.Call("127.0.0.1:20000", url, "GetBigPkg", []interface{}{nil}, user) + //assert.NoError(t, err) + //assert.NotEqual(t, "", user.Id) + //assert.NotEqual(t, "", user.Name) + + user = &User{} err := c.Call("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, user) assert.NoError(t, err) assert.Equal(t, User{Id: "1", Name: "username"}, *user) user = &User{} - err = c.Call("127.0.0.1:20000", url, "GetUser0", []interface{}{"1", "username"}, user) + err = c.Call("127.0.0.1:20000", url, "GetUser0", []interface{}{"1", nil, "username"}, user) assert.NoError(t, err) assert.Equal(t, User{Id: "1", Name: "username"}, *user) - user = &User{} - err = c.Call("127.0.0.1:20000", url, "GetUser1", []interface{}{"1", "username"}, user) - assert.EqualError(t, err, "java exception:error") + err = c.Call("127.0.0.1:20000", url, "GetUser1", []interface{}{}, user) + assert.NoError(t, err) + + err = c.Call("127.0.0.1:20000", url, "GetUser2", []interface{}{}, user) + assert.EqualError(t, err, "error") user2 := []interface{}{} - err = c.Call("127.0.0.1:20000", url, "GetUser2", []interface{}{"1", "username"}, &user2) + err = c.Call("127.0.0.1:20000", url, "GetUser3", []interface{}{}, &user2) assert.NoError(t, err) assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) user2 = []interface{}{} - err = c.Call("127.0.0.1:20000", url, "GetUser3", []interface{}{[]interface{}{"1", "username"}}, &user2) + err = c.Call("127.0.0.1:20000", url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, &user2) assert.NoError(t, err) assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) user3 := map[interface{}]interface{}{} - err = c.Call("127.0.0.1:20000", url, "GetUser4", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, &user3) + err = c.Call("127.0.0.1:20000", url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, &user3) assert.NoError(t, err) assert.NotNil(t, user3) assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"]) + user = &User{} + err = c.Call("127.0.0.1:20000", url, "GetUser6", []interface{}{0}, user) + assert.NoError(t, err) + assert.Equal(t, User{Id: "", Name: ""}, *user) + + user = &User{} + err = c.Call("127.0.0.1:20000", url, "GetUser6", []interface{}{1}, user) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: ""}, *user) + // destroy proto.Destroy() } @@ -133,7 +145,7 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { methods, err := common.ServiceMap.Register("dubbo", &UserProvider{}) assert.NoError(t, err) - assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods) + assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) // config SetClientConf(ClientConfig{ @@ -152,10 +164,10 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { TcpWBufSize: 65536, PkgRQSize: 1024, PkgWQSize: 512, - TcpReadTimeout: "1s", + TcpReadTimeout: "4s", TcpWriteTimeout: "5s", WaitTimeout: "1s", - MaxMsgLen: 1024, + MaxMsgLen: 10240000000, SessionName: "client", }, }) @@ -176,7 +188,7 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { TcpReadTimeout: "1s", TcpWriteTimeout: "5s", WaitTimeout: "1s", - MaxMsgLen: 1024, + MaxMsgLen: 10240000000, SessionName: "server", }}) assert.NoError(t, srvConf.CheckValidity()) @@ -196,34 +208,72 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { return proto, url } +////////////////////////////////// +// provider +////////////////////////////////// + +type ( + User struct { + Id string `json:"id"` + Name string `json:"name"` + } + + UserProvider struct { + user map[string]User + } +) + +// size:4801228 +func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { + argBuf := new(bytes.Buffer) + for i := 0; i < 4000; i++ { + argBuf.WriteString("击鼓其镗,踊跃用兵。土国城漕,我独南行。从孙子仲,平陈与宋。不我以归,忧心有忡。爰居爰处?爰丧其马?于以求之?于林之下。死生契阔,与子成说。执子之手,与子偕老。于嗟阔兮,不我活兮。于嗟洵兮,不我信兮。") + argBuf.WriteString("击鼓其镗,踊跃用兵。土国城漕,我独南行。从孙子仲,平陈与宋。不我以归,忧心有忡。爰居爰处?爰丧其马?于以求之?于林之下。死生契阔,与子成说。执子之手,与子偕老。于嗟阔兮,不我活兮。于嗟洵兮,不我信兮。") + } + rsp.Id = argBuf.String() + rsp.Name = argBuf.String() + return nil +} + func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { rsp.Id = req[0].(string) rsp.Name = req[1].(string) return nil } -func (u *UserProvider) GetUser0(id string, name string) (User, error) { +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { return User{Id: id, Name: name}, nil } -func (u *UserProvider) GetUser1(ctx context.Context, req []interface{}, rsp *User) error { +func (u *UserProvider) GetUser1() error { + return nil +} + +func (u *UserProvider) GetUser2() error { return perrors.New("error") } -func (u *UserProvider) GetUser2(ctx context.Context, req []interface{}, rsp *[]interface{}) error { - *rsp = append(*rsp, User{Id: req[0].(string), Name: req[1].(string)}) +func (u *UserProvider) GetUser3(rsp *[]interface{}) error { + *rsp = append(*rsp, User{Id: "1", Name: "username"}) return nil } -func (u *UserProvider) GetUser3(ctx context.Context, req []interface{}) ([]interface{}, error) { +func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil } -func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { +func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil } +func (u *UserProvider) GetUser6(id int64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + func (u *UserProvider) Service() string { return "com.ikurento.user.UserProvider" } diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index a20fcfabd1ca93b6ad5073d3ff56a51a3a16bbe7..9551c2b0b7b742228ae69701a9d86975d2c85e52 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -25,7 +25,7 @@ import ( ) import ( - hessian "github.com/dubbogo/hessian2" + "github.com/dubbogo/hessian2" perrors "github.com/pkg/errors" ) @@ -74,7 +74,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { } func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { - codec := hessian.NewHessianCodec(bufio.NewReader(buf)) + codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, buf.Len())) // read header err := codec.ReadHeader(&p.Header) @@ -83,22 +83,19 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { } if len(opts) != 0 { // for client - if client, ok := opts[0].(*Client); ok { + client, ok := opts[0].(*Client) + if !ok { + return perrors.Errorf("opts[0] is not of type *Client") + } - r := client.pendingResponses[SequenceType(p.Header.ID)] - if r == nil { - return perrors.Errorf("pendingResponses[%v] = nil", p.Header.ID) - } - p.Body = client.pendingResponses[SequenceType(p.Header.ID)].reply + pendingRsp := client.GetPendingResponse(SequenceType(p.Header.ID)) + if pendingRsp == nil { + return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) } else { - return perrors.Errorf("opts[0] is not *Client") + p.Body = &hessian.Response{RspObj: pendingRsp.reply} } } - if p.Header.Type&hessian.PackageHeartbeat != 0x00 { - return nil - } - // read body err = codec.ReadBody(p.Body) return perrors.WithStack(err) diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go index 269a38cf6f9e5891bf622aa6a63820ebc5989194..a2df3d91b2fa6b1ef8907ecba8832368d0613b8e 100644 --- a/protocol/dubbo/dubbo_protocol.go +++ b/protocol/dubbo/dubbo_protocol.go @@ -22,6 +22,7 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + "sync" ) const ( @@ -38,7 +39,8 @@ var ( type DubboProtocol struct { protocol.BaseProtocol - serverMap map[string]*Server + serverMap map[string]*Server + serverLock sync.Mutex } func NewDubboProtocol() *DubboProtocol { @@ -50,7 +52,7 @@ func NewDubboProtocol() *DubboProtocol { func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() - serviceKey := url.Key() + serviceKey := url.ServiceKey() exporter := NewDubboExporter(serviceKey, invoker, dp.ExporterMap()) dp.SetExporterMap(serviceKey, exporter) logger.Infof("Export service: %s", url.String()) @@ -80,18 +82,27 @@ func (dp *DubboProtocol) Destroy() { } func (dp *DubboProtocol) openServer(url common.URL) { - exporter, ok := dp.ExporterMap().Load(url.Key()) + _, ok := dp.serverMap[url.Location] if !ok { - panic("[DubboProtocol]" + url.Key() + "is not existing") + _, ok := dp.ExporterMap().Load(url.ServiceKey()) + if !ok { + panic("[DubboProtocol]" + url.Key() + "is not existing") + } + + dp.serverLock.Lock() + _, ok = dp.serverMap[url.Location] + if !ok { + srv := NewServer() + dp.serverMap[url.Location] = srv + srv.Start(url) + } + dp.serverLock.Unlock() } - srv := NewServer(exporter.(protocol.Exporter)) - dp.serverMap[url.Location] = srv - srv.Start(url) } func GetProtocol() protocol.Protocol { - if dubboProtocol != nil { - return dubboProtocol + if dubboProtocol == nil { + dubboProtocol = NewDubboProtocol() } - return NewDubboProtocol() + return dubboProtocol } diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go index 0c4bfdcc7a7089873b66f278028f2e4358b013d3..3543d8da803b00befe9e08286bd67c09cd0afef2 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -19,6 +19,7 @@ package dubbo import ( "context" + "github.com/apache/dubbo-go/common/constant" "testing" ) @@ -34,24 +35,36 @@ import ( func TestDubboProtocol_Export(t *testing.T) { // Export proto := GetProtocol() + srvConf = &ServerConfig{} url, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) - srvConf = &ServerConfig{} exporter := proto.Export(protocol.NewBaseInvoker(url)) // make sure url eq := exporter.GetInvoker().GetUrl().URLEqual(url) assert.True(t, eq) + // second service: the same path and the diferent version + url2, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + "side=provider&timeout=3000×tamp=1556509797245", common.WithParamsValue(constant.VERSION_KEY, "v1.1")) + assert.NoError(t, err) + exporter2 := proto.Export(protocol.NewBaseInvoker(url2)) + // make sure url + eq2 := exporter2.GetInvoker().GetUrl().URLEqual(url2) + assert.True(t, eq2) + // make sure exporterMap after 'Unexport' - _, ok := proto.(*DubboProtocol).ExporterMap().Load(url.Key()) + _, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) assert.True(t, ok) exporter.Unexport() - _, ok = proto.(*DubboProtocol).ExporterMap().Load(url.Key()) + _, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) assert.False(t, ok) // make sure serverMap after 'Destroy' diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 59ffd3e1e554e2be9a9dfef25f31d1e06211d938..15e222676afe5579fc53df46a3983b55e5d3f2b4 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -19,19 +19,25 @@ package dubbo import ( "context" + "fmt" + "net/url" "reflect" "sync" "time" +) +import ( "github.com/dubbogo/getty" + "github.com/dubbogo/hessian2" + perrors "github.com/pkg/errors" +) +import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" - hessian "github.com/dubbogo/hessian2" - perrors "github.com/pkg/errors" ) // todo: WritePkg_Timeout will entry *.yml @@ -82,6 +88,9 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { if p.Header.Type&hessian.PackageHeartbeat != 0x00 { logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) + if p.Err != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) + } return } logger.Debugf("get rpc response{header: %#v, body: %#v}", p.Header, p.Body) @@ -126,16 +135,14 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { //////////////////////////////////////////// type RpcServerHandler struct { - exporter protocol.Exporter maxSessionNum int sessionTimeout time.Duration sessionMap map[getty.Session]*rpcSession rwlock sync.RWMutex } -func NewRpcServerHandler(exporter protocol.Exporter, maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { +func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { return &RpcServerHandler{ - exporter: exporter, maxSessionNum: maxSessionNum, sessionTimeout: sessionTimeout, sessionMap: make(map[getty.Session]*rpcSession), @@ -199,19 +206,35 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { // not twoway if p.Header.Type&hessian.PackageRequest_TwoWay == 0x00 { twoway = false - h.reply(session, p, hessian.PackageResponse) } - invoker := h.exporter.GetInvoker() + group := p.Body.(map[string]interface{})["attachments"].(map[interface{}]interface{})[constant.GROUP_KEY] + if group == nil { + group = "" + } + u := common.NewURLWithOptions(common.WithPath(p.Service.Path), common.WithParams(url.Values{}), + common.WithParamsValue(constant.GROUP_KEY, group.(string)), + common.WithParamsValue(constant.INTERFACE_KEY, p.Service.Interface), + common.WithParamsValue(constant.VERSION_KEY, p.Service.Version)) + exporter, _ := dubboProtocol.ExporterMap().Load(u.ServiceKey()) + if exporter == nil { + err := fmt.Errorf("don't have this exporter, key: %s", u.ServiceKey()) + logger.Errorf(err.Error()) + p.Header.ResponseStatus = hessian.Response_OK + p.Body = err + h.reply(session, p, hessian.PackageResponse) + return + } + invoker := exporter.(protocol.Exporter).GetInvoker() if invoker != nil { result := invoker.Invoke(invocation.NewRPCInvocationForProvider(p.Service.Method, p.Body.(map[string]interface{})["args"].([]interface{}), map[string]string{ - constant.PATH_KEY: p.Service.Path, - //attachments[constant.GROUP_KEY] = url.GetParam(constant.GROUP_KEY, "") + constant.PATH_KEY: p.Service.Path, + constant.GROUP_KEY: group.(string), constant.INTERFACE_KEY: p.Service.Interface, constant.VERSION_KEY: p.Service.Version, })) if err := result.Error(); err != nil { - p.Header.ResponseStatus = hessian.Response_SERVER_ERROR + p.Header.ResponseStatus = hessian.Response_OK p.Body = err h.reply(session, p, hessian.PackageResponse) return @@ -260,15 +283,15 @@ func (h *RpcServerHandler) callService(req *DubboPackage, ctx context.Context) { defer func() { if e := recover(); e != nil { - req.Header.ResponseStatus = hessian.Response_BAD_REQUEST + req.Header.ResponseStatus = hessian.Response_SERVER_ERROR if err, ok := e.(error); ok { - logger.Errorf("callService panic: %#v", err) - req.Body = e.(error) + logger.Errorf("callService panic: %+v", perrors.WithStack(err)) + req.Body = perrors.WithStack(err) } else if err, ok := e.(string); ok { - logger.Errorf("callService panic: %#v", perrors.New(err)) + logger.Errorf("callService panic: %+v", perrors.New(err)) req.Body = perrors.New(err) } else { - logger.Errorf("callService panic: %#v", e) + logger.Errorf("callService panic: %+v, this is impossible.", e) req.Body = e } } @@ -277,7 +300,7 @@ func (h *RpcServerHandler) callService(req *DubboPackage, ctx context.Context) { svcIf := req.Body.(map[string]interface{})["service"] if svcIf == nil { logger.Errorf("service not found!") - req.Header.ResponseStatus = hessian.Response_SERVICE_NOT_FOUND + req.Header.ResponseStatus = hessian.Response_BAD_REQUEST req.Body = perrors.New("service not found") return } @@ -285,7 +308,7 @@ func (h *RpcServerHandler) callService(req *DubboPackage, ctx context.Context) { method := svc.Method()[req.Service.Method] if method == nil { logger.Errorf("method not found!") - req.Header.ResponseStatus = hessian.Response_SERVICE_NOT_FOUND + req.Header.ResponseStatus = hessian.Response_BAD_REQUEST req.Body = perrors.New("method not found") return } @@ -301,13 +324,21 @@ func (h *RpcServerHandler) callService(req *DubboPackage, ctx context.Context) { in = append(in, reflect.ValueOf(argv)) } else { for i := 0; i < len(argv.([]interface{})); i++ { - in = append(in, reflect.ValueOf(argv.([]interface{})[i])) + t := reflect.ValueOf(argv.([]interface{})[i]) + if !t.IsValid() { + at := method.ArgsType()[i] + if at.Kind() == reflect.Ptr { + at = at.Elem() + } + t = reflect.New(at) + } + in = append(in, t) } } // prepare replyv var replyv reflect.Value - if method.ReplyType() == nil { + if method.ReplyType() == nil && len(method.ArgsType()) > 0 { replyv = reflect.New(method.ArgsType()[len(method.ArgsType())-1].Elem()) in = append(in, replyv) } @@ -322,10 +353,14 @@ func (h *RpcServerHandler) callService(req *DubboPackage, ctx context.Context) { retErr = returnValues[1].Interface() } if retErr != nil { - req.Header.ResponseStatus = hessian.Response_SERVER_ERROR - req.Body = retErr.(error) + req.Header.ResponseStatus = hessian.Response_OK + req.Body = retErr } else { - req.Body = replyv.Interface() + if replyv.IsValid() && (replyv.Kind() != reflect.Ptr || replyv.Kind() == reflect.Ptr && replyv.Elem().IsValid()) { + req.Body = replyv.Interface() + } else { + req.Body = nil + } } } diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index b47c146fe783aba2fc2de7fe5df956da4d6bb7d6..de205fa75f5fe8e8ef75c7c0f12cc2f78b3c397b 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -21,7 +21,6 @@ import ( "fmt" "math/rand" "net" - "strings" "sync" "time" ) @@ -39,7 +38,7 @@ type gettyRPCClient struct { once sync.Once protocol string addr string - created int64 // 为0,则说明没有被创建或者被销毁了 + created int64 // zero, not create or be destroyed pool *gettyRPCClientPool @@ -225,13 +224,13 @@ func (c *gettyRPCClient) close() error { c.once.Do(func() { // delete @c from client pool c.pool.remove(c) + c.gettyClient.Close() + c.gettyClient = nil for _, s := range c.sessions { logger.Infof("close client session{%s, last active:%s, request number:%d}", s.session.Stat(), s.session.GetActive().String(), s.reqNum) s.session.Close() } - c.gettyClient.Close() - c.gettyClient = nil c.sessions = c.sessions[:0] c.created = 0 @@ -337,13 +336,3 @@ func (p *gettyRPCClientPool) remove(conn *gettyRPCClient) { } } } - -func GenerateEndpointAddr(protocol, addr string) string { - var builder strings.Builder - - builder.WriteString(protocol) - builder.WriteString("://") - builder.WriteString(addr) - - return builder.String() -} diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index 5de96acaa1ae5a8df7fa5e8182b98be08ac137c3..529aa759a5c39a467a1f72560d56c1b48738a9a6 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -24,6 +24,7 @@ import ( import ( "github.com/dubbogo/getty" + hessian "github.com/dubbogo/hessian2" perrors "github.com/pkg/errors" ) import ( @@ -52,11 +53,20 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface buf := bytes.NewBuffer(data) err := pkg.Unmarshal(buf, p.client) if err != nil { - pkg.Err = perrors.WithStack(err) // client will get this err - return pkg, len(data), nil + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, nil + } + + logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) + + return nil, 0, perrors.WithStack(err) } - return pkg, len(data), nil + pkg.Err = pkg.Body.(*hessian.Response).Exception + pkg.Body = pkg.Body.(*hessian.Response).RspObj + + return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil } func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) error { @@ -79,12 +89,11 @@ func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) error // RpcServerPackageHandler //////////////////////////////////////////// -type RpcServerPackageHandler struct { -} +var ( + rpcServerPkgHandler = &RpcServerPackageHandler{} +) -func NewRpcServerPackageHandler() *RpcServerPackageHandler { - return &RpcServerPackageHandler{} -} +type RpcServerPackageHandler struct{} func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { pkg := &DubboPackage{ @@ -94,46 +103,56 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface buf := bytes.NewBuffer(data) err := pkg.Unmarshal(buf) if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, nil + } + + logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) + return nil, 0, perrors.WithStack(err) } - // convert params of request - req := pkg.Body.([]interface{}) // length of body should be 7 - if len(req) > 0 { - var dubboVersion, argsTypes string - var args []interface{} - var attachments map[interface{}]interface{} - if req[0] != nil { - dubboVersion = req[0].(string) - } - if req[1] != nil { - pkg.Service.Path = req[1].(string) - } - if req[2] != nil { - pkg.Service.Version = req[2].(string) - } - if req[3] != nil { - pkg.Service.Method = req[3].(string) - } - if req[4] != nil { - argsTypes = req[4].(string) - } - if req[5] != nil { - args = req[5].([]interface{}) - } - if req[6] != nil { - attachments = req[6].(map[interface{}]interface{}) - } - pkg.Service.Interface = attachments[constant.INTERFACE_KEY].(string) - pkg.Body = map[string]interface{}{ - "dubboVersion": dubboVersion, - "argsTypes": argsTypes, - "args": args, - "service": common.ServiceMap.GetService(DUBBO, pkg.Service.Interface), - "attachments": attachments, + + if pkg.Header.Type&hessian.PackageHeartbeat == 0x00 { + // convert params of request + req := pkg.Body.([]interface{}) // length of body should be 7 + if len(req) > 0 { + var dubboVersion, argsTypes string + var args []interface{} + var attachments map[interface{}]interface{} + if req[0] != nil { + dubboVersion = req[0].(string) + } + if req[1] != nil { + pkg.Service.Path = req[1].(string) + } + if req[2] != nil { + pkg.Service.Version = req[2].(string) + } + if req[3] != nil { + pkg.Service.Method = req[3].(string) + } + if req[4] != nil { + argsTypes = req[4].(string) + } + if req[5] != nil { + args = req[5].([]interface{}) + } + if req[6] != nil { + attachments = req[6].(map[interface{}]interface{}) + } + pkg.Service.Interface = attachments[constant.INTERFACE_KEY].(string) + pkg.Body = map[string]interface{}{ + "dubboVersion": dubboVersion, + "argsTypes": argsTypes, + "args": args, + "service": common.ServiceMap.GetService(DUBBO, pkg.Service.Interface), + "attachments": attachments, + } } } - return pkg, len(data), nil + return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil } func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) error { diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index 80568c61bb44b540df0d0689273f22d206411ca4..ac521bdc485c5add3745ba78acf8cafab6675158 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -31,7 +31,6 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" - "github.com/apache/dubbo-go/protocol" ) var srvConf *ServerConfig @@ -76,18 +75,19 @@ func GetServerConfig() ServerConfig { } type Server struct { - conf ServerConfig - tcpServer getty.Server - exporter protocol.Exporter + conf ServerConfig + tcpServer getty.Server + rpcHandler *RpcServerHandler } -func NewServer(exporter protocol.Exporter) *Server { +func NewServer() *Server { s := &Server{ - exporter: exporter, - conf: *srvConf, + conf: *srvConf, } + s.rpcHandler = NewRpcServerHandler(s.conf.SessionNumber, s.conf.sessionTimeout) + return s } @@ -116,8 +116,8 @@ func (s *Server) newSession(session getty.Session) error { session.SetName(conf.GettySessionParam.SessionName) session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) - session.SetPkgHandler(NewRpcServerPackageHandler()) - session.SetEventListener(NewRpcServerHandler(s.exporter, conf.SessionNumber, conf.sessionTimeout)) + session.SetPkgHandler(rpcServerPkgHandler) + session.SetEventListener(s.rpcHandler) session.SetRQLen(conf.GettySessionParam.PkgRQSize) session.SetWQLen(conf.GettySessionParam.PkgWQSize) session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index 008e883aed317a78d68fc0f0d55f49df66b2dee0..222921890df08545d8ac98e5faf27641ed47df53 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -48,7 +48,7 @@ func NewRPCInvocationForConsumer(methodName string, parameterTypes []reflect.Typ attachments[constant.PATH_KEY] = url.Path attachments[constant.GROUP_KEY] = url.GetParam(constant.GROUP_KEY, "") attachments[constant.INTERFACE_KEY] = url.GetParam(constant.INTERFACE_KEY, "") - attachments[constant.VERSION_KEY] = url.GetParam(constant.VERSION_KEY, constant.DEFAULT_VERSION) + attachments[constant.VERSION_KEY] = url.GetParam(constant.VERSION_KEY, "") return &RPCInvocation{ methodName: methodName, diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index af65db04b880a0e6d7f18fd5a96b775795be0fab..46e2da06b77c070cf15ff6ee6b4781c453022747 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -100,7 +100,7 @@ func (c *HTTPClient) NewRequest(service common.URL, method string, args interfac ID: atomic.AddInt64(&c.ID, 1), group: service.GetParam(constant.GROUP_KEY, ""), protocol: service.Protocol, - version: service.GetParam(constant.VERSION_KEY, constant.DEFAULT_VERSION), + version: service.GetParam(constant.VERSION_KEY, ""), service: service.Path, method: method, args: args, @@ -136,7 +136,7 @@ func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, return perrors.WithStack(err) } - rspBody, err := c.Do(service.Location, service.Params.Get("interface"), httpHeader, reqBody) + rspBody, err := c.Do(service.Location, service.Path, httpHeader, reqBody) if err != nil { return perrors.WithStack(err) } diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 3f6f983702360b9c3036c943b0651e5c935376e3..f05f47b8c1b571846335852503bb2c3a5a8c5a8d 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -50,7 +50,7 @@ func TestHTTPClient_Call(t *testing.T) { methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{}) assert.NoError(t, err) - assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3", methods) + assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods) // Export proto := GetProtocol() @@ -82,9 +82,9 @@ func TestHTTPClient_Call(t *testing.T) { ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ "X-Proxy-Id": "dubbogo", "X-Services": url.Path, - "X-Method": "GetUser", + "X-Method": "GetUser0", }) - req = client.NewRequest(url, "GetUser0", []interface{}{"1", "username"}) + req = client.NewRequest(url, "GetUser0", []interface{}{"1", nil, "username"}) reply = &User{} err = client.Call(ctx, url, req, reply) assert.NoError(t, err) @@ -97,7 +97,7 @@ func TestHTTPClient_Call(t *testing.T) { "X-Services": url.Path, "X-Method": "GetUser1", }) - req = client.NewRequest(url, "GetUser1", []interface{}{""}) + req = client.NewRequest(url, "GetUser1", []interface{}{}) reply = &User{} err = client.Call(ctx, url, req, reply) assert.True(t, strings.Contains(err.Error(), "500 Internal Server Error")) @@ -107,7 +107,7 @@ func TestHTTPClient_Call(t *testing.T) { ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ "X-Proxy-Id": "dubbogo", "X-Services": url.Path, - "X-Method": "GetUser", + "X-Method": "GetUser2", }) req = client.NewRequest(url, "GetUser2", []interface{}{"1", "username"}) reply1 := []User{} @@ -119,7 +119,7 @@ func TestHTTPClient_Call(t *testing.T) { ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ "X-Proxy-Id": "dubbogo", "X-Services": url.Path, - "X-Method": "GetUser", + "X-Method": "GetUser3", }) req = client.NewRequest(url, "GetUser3", []interface{}{"1", "username"}) reply1 = []User{} @@ -127,6 +127,29 @@ func TestHTTPClient_Call(t *testing.T) { assert.NoError(t, err) assert.Equal(t, User{Id: "1", Name: "username"}, reply1[0]) + // call GetUser4 + ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ + "X-Proxy-Id": "dubbogo", + "X-Services": url.Path, + "X-Method": "GetUser4", + }) + req = client.NewRequest(url, "GetUser4", []interface{}{0}) + reply = &User{} + err = client.Call(ctx, url, req, reply) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "", Name: ""}, reply) + + ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ + "X-Proxy-Id": "dubbogo", + "X-Services": url.Path, + "X-Method": "GetUser4", + }) + req = client.NewRequest(url, "GetUser4", []interface{}{1}) + reply = &User{} + err = client.Call(ctx, url, req, reply) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "1", Name: ""}, reply) + // destroy proto.Destroy() @@ -138,11 +161,11 @@ func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User return nil } -func (u *UserProvider) GetUser0(id string, name string) (User, error) { +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { return User{Id: id, Name: name}, nil } -func (u *UserProvider) GetUser1(ctx context.Context, req []interface{}, rsp *User) error { +func (u *UserProvider) GetUser1() error { return perrors.New("error") } @@ -155,6 +178,13 @@ func (u *UserProvider) GetUser3(ctx context.Context, req []interface{}) ([]User, return []User{{Id: req[0].(string), Name: req[1].(string)}}, nil } +func (u *UserProvider) GetUser4(id float64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + func (u *UserProvider) Service() string { return "com.ikurento.user.UserProvider" } diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go index c147f9add85041b3dfaf1c9463813c560f9238d0..7ee454e8ad16d2ee96ed08e7e5f55b2209a81054 100644 --- a/protocol/jsonrpc/json.go +++ b/protocol/jsonrpc/json.go @@ -192,6 +192,9 @@ func (c *jsonClientCodec) Read(streamBytes []byte, x interface{}) error { return perrors.New(c.rsp.Error.Error()) } + if c.rsp.Result == nil { + return nil + } return perrors.WithStack(json.Unmarshal(*c.rsp.Result, x)) } diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index 9641fb532148bc78562755ea4c55696142278951..0dd427eb69127317d646c599506dc476f2859a3f 100644 --- a/protocol/jsonrpc/jsonrpc_invoker_test.go +++ b/protocol/jsonrpc/jsonrpc_invoker_test.go @@ -37,7 +37,7 @@ func TestJsonrpcInvoker_Invoke(t *testing.T) { methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{}) assert.NoError(t, err) - assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3", methods) + assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods) // Export proto := GetProtocol() diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go index fa4071b4fd75ecc4f0be94f93d31c13a8cccb8f2..c18345d413edb2d263f1acaef1741514b665f042 100644 --- a/protocol/jsonrpc/jsonrpc_protocol.go +++ b/protocol/jsonrpc/jsonrpc_protocol.go @@ -17,6 +17,11 @@ package jsonrpc +import ( + "strings" + "sync" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/extension" @@ -35,10 +40,11 @@ var jsonrpcProtocol *JsonrpcProtocol type JsonrpcProtocol struct { protocol.BaseProtocol - serverMap map[string]*Server + serverMap map[string]*Server + serverLock sync.Mutex } -func NewDubboProtocol() *JsonrpcProtocol { +func NewJsonrpcProtocol() *JsonrpcProtocol { return &JsonrpcProtocol{ BaseProtocol: protocol.NewBaseProtocol(), serverMap: make(map[string]*Server), @@ -47,7 +53,8 @@ func NewDubboProtocol() *JsonrpcProtocol { func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() - serviceKey := url.Key() + serviceKey := strings.TrimPrefix(url.Path, "/") + exporter := NewJsonrpcExporter(serviceKey, invoker, jp.ExporterMap()) jp.SetExporterMap(serviceKey, exporter) logger.Infof("Export service: %s", url.String()) @@ -81,18 +88,27 @@ func (jp *JsonrpcProtocol) Destroy() { } func (jp *JsonrpcProtocol) openServer(url common.URL) { - exporter, ok := jp.ExporterMap().Load(url.Key()) + _, ok := jp.serverMap[url.Location] if !ok { - panic("[JsonrpcProtocol]" + url.Key() + "is not existing") + _, ok := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) + if !ok { + panic("[JsonrpcProtocol]" + url.Key() + "is not existing") + } + + jp.serverLock.Lock() + _, ok = jp.serverMap[url.Location] + if !ok { + srv := NewServer() + jp.serverMap[url.Location] = srv + srv.Start(url) + } + jp.serverLock.Unlock() } - srv := NewServer(exporter.(protocol.Exporter)) - jp.serverMap[url.Location] = srv - srv.Start(url) } func GetProtocol() protocol.Protocol { - if jsonrpcProtocol != nil { - return jsonrpcProtocol + if jsonrpcProtocol == nil { + jsonrpcProtocol = NewJsonrpcProtocol() } - return NewDubboProtocol() + return jsonrpcProtocol } diff --git a/protocol/jsonrpc/jsonrpc_protocol_test.go b/protocol/jsonrpc/jsonrpc_protocol_test.go index 866f3d72be7b907cd9e2a8e56528f1b76848646d..253ab830dd85e5424811b7fd4e7e7e848adad415 100644 --- a/protocol/jsonrpc/jsonrpc_protocol_test.go +++ b/protocol/jsonrpc/jsonrpc_protocol_test.go @@ -19,6 +19,8 @@ package jsonrpc import ( "context" + "fmt" + "strings" "testing" "time" ) @@ -49,10 +51,11 @@ func TestJsonrpcProtocol_Export(t *testing.T) { assert.True(t, eq) // make sure exporterMap after 'Unexport' - _, ok := proto.(*JsonrpcProtocol).ExporterMap().Load(url.Key()) + fmt.Println(url.Path) + _, ok := proto.(*JsonrpcProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) assert.True(t, ok) exporter.Unexport() - _, ok = proto.(*JsonrpcProtocol).ExporterMap().Load(url.Key()) + _, ok = proto.(*JsonrpcProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) assert.False(t, ok) // make sure serverMap after 'Destroy' diff --git a/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index a7643dc215e8ae1ef5f7e5b02236f881b0f963c9..5b5548067225bcf6d8bcbaf35cee63c829c03edc 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -40,7 +40,6 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -58,19 +57,17 @@ const ( ) type Server struct { - exporter protocol.Exporter - done chan struct{} - once sync.Once + done chan struct{} + once sync.Once sync.RWMutex wg sync.WaitGroup timeout time.Duration } -func NewServer(exporter protocol.Exporter) *Server { +func NewServer() *Server { return &Server{ - exporter: exporter, - done: make(chan struct{}), + done: make(chan struct{}), } } @@ -161,7 +158,7 @@ func (s *Server) handlePkg(conn net.Conn) { } setTimeout(conn, httpTimeout) - if err := serveRequest(ctx, reqHeader, reqBody, conn, s.exporter); err != nil { + if err := serveRequest(ctx, reqHeader, reqBody, conn); err != nil { if errRsp := sendErrorResp(r.Header, []byte(perrors.WithStack(err).Error())); errRsp != nil { logger.Warnf("sendErrorResp(header:%#v, error:%v) = error:%s", r.Header, perrors.WithStack(err), errRsp) @@ -249,8 +246,7 @@ func (s *Server) Stop() { } func serveRequest(ctx context.Context, - header map[string]string, body []byte, conn net.Conn, exporter protocol.Exporter) error { - + header map[string]string, body []byte, conn net.Conn) error { sendErrorResp := func(header map[string]string, body []byte) error { rsp := &http.Response{ Header: make(http.Header), @@ -309,27 +305,26 @@ func serveRequest(ctx context.Context, return perrors.New("server cannot decode request: " + err.Error()) } - serviceName := header["Path"] + path := header["Path"] methodName := codec.req.Method - if len(serviceName) == 0 || len(methodName) == 0 { + if len(path) == 0 || len(methodName) == 0 { codec.ReadBody(nil) - return perrors.New("service/method request ill-formed: " + serviceName + "/" + methodName) + return perrors.New("service/method request ill-formed: " + path + "/" + methodName) } // read body - var args interface{} + var args []interface{} if err = codec.ReadBody(&args); err != nil { return perrors.WithStack(err) } logger.Debugf("args: %v", args) // exporter invoke - invoker := exporter.GetInvoker() + exporter, _ := jsonrpcProtocol.ExporterMap().Load(path) + invoker := exporter.(*JsonrpcExporter).GetInvoker() if invoker != nil { - result := invoker.Invoke(invocation.NewRPCInvocationForProvider(methodName, args.([]interface{}), map[string]string{ - //attachments[constant.PATH_KEY] = url.Path - //attachments[constant.GROUP_KEY] = url.GetParam(constant.GROUP_KEY, "") - //attachments[constant.INTERFACE_KEY] = url.GetParam(constant.INTERFACE_KEY, "") + result := invoker.Invoke(invocation.NewRPCInvocationForProvider(methodName, args, map[string]string{ + constant.PATH_KEY: path, constant.VERSION_KEY: codec.req.Version, })) if err := result.Error(); err != nil { @@ -351,7 +346,7 @@ func serveRequest(ctx context.Context, } } } - + serviceName := invoker.GetUrl().Service() // get method svc := common.ServiceMap.GetService(JSONRPC, serviceName) if svc == nil { @@ -371,14 +366,22 @@ func serveRequest(ctx context.Context, if (len(method.ArgsType()) == 1 || len(method.ArgsType()) == 2 && method.ReplyType() == nil) && method.ArgsType()[0].String() == "[]interface {}" { in = append(in, reflect.ValueOf(args)) } else { - for i := 0; i < len(args.([]interface{})); i++ { - in = append(in, reflect.ValueOf(args.([]interface{})[i])) + for i := 0; i < len(args); i++ { + t := reflect.ValueOf(args[i]) + if !t.IsValid() { + at := method.ArgsType()[i] + if at.Kind() == reflect.Ptr { + at = at.Elem() + } + t = reflect.New(at) + } + in = append(in, t) } } // prepare replyv var replyv reflect.Value - if method.ReplyType() == nil { + if method.ReplyType() == nil && len(method.ArgsType()) > 0 { replyv = reflect.New(method.ArgsType()[len(method.ArgsType())-1].Elem()) in = append(in, replyv) } @@ -401,7 +404,10 @@ func serveRequest(ctx context.Context, // write response code := 200 - rspReply := replyv.Interface() + var rspReply interface{} + if replyv.IsValid() && (replyv.Kind() != reflect.Ptr || replyv.Kind() == reflect.Ptr && replyv.Elem().IsValid()) { + rspReply = replyv.Interface() + } if len(errMsg) != 0 { code = 500 rspReply = invalidRequest diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go index 8131accfd3105d101042c1c51178f2794ebc47f6..8a332490f71ead601d151fe5e27390eadcc1cbd8 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go @@ -38,7 +38,7 @@ func TestProtocolFilterWrapper_Export(t *testing.T) { filtProto := extension.GetProtocol(FILTER) filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{} - u := common.NewURLWithOptions("Service", + u := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.SERVICE_FILTER_KEY, impl.ECHO)) exporter := filtProto.Export(protocol.NewBaseInvoker(*u)) @@ -50,7 +50,7 @@ func TestProtocolFilterWrapper_Refer(t *testing.T) { filtProto := extension.GetProtocol(FILTER) filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{} - u := common.NewURLWithOptions("Service", + u := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.REFERENCE_FILTER_KEY, impl.ECHO)) invoker := filtProto.Refer(*u) diff --git a/protocol/result.go b/protocol/result.go index f6e27280216641269cfc031888ea53176cc8b225..dcdb62310d359d441067395ea92f8460df97eb22 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -18,8 +18,14 @@ package protocol type Result interface { + SetError(error) Error() error + SetResult(interface{}) Result() interface{} + SetAttachments(map[string]string) + Attachments() map[string]string + AddAttachment(string, string) + Attachment(string, string) string } ///////////////////////////// @@ -27,14 +33,43 @@ type Result interface { ///////////////////////////// type RPCResult struct { - Err error - Rest interface{} + Attrs map[string]string + Err error + Rest interface{} +} + +func (r *RPCResult) SetError(err error) { + r.Err = err } func (r *RPCResult) Error() error { return r.Err } +func (r *RPCResult) SetResult(rest interface{}) { + r.Rest = rest +} + func (r *RPCResult) Result() interface{} { return r.Rest } + +func (r *RPCResult) SetAttachments(attr map[string]string) { + r.Attrs = attr +} + +func (r *RPCResult) Attachments() map[string]string { + return r.Attrs +} + +func (r *RPCResult) AddAttachment(key, value string) { + r.Attrs[key] = value +} + +func (r *RPCResult) Attachment(key, defaultValue string) string { + v, ok := r.Attrs[key] + if !ok { + v = defaultValue + } + return v +} diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 1003baf5df7bb659200bb12e118e9113577051af..a4a9263156345a6e98c5a5189a49d4e54d65a76b 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -23,7 +23,6 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/logger" perrors "github.com/pkg/errors" ) @@ -32,9 +31,11 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/protocolwrapper" "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting" ) const ( @@ -130,10 +131,10 @@ func (dir *registryDirectory) update(res *registry.ServiceEvent) { func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { switch res.Action { - case registry.ServiceAdd: + case remoting.Add: //dir.cacheService.Add(res.Path, dir.serviceTTL) dir.cacheInvoker(res.Service) - case registry.ServiceDel: + case remoting.Del: //dir.cacheService.Del(res.Path, dir.serviceTTL) dir.uncacheInvoker(res.Service) logger.Infof("selector delete service url{%s}", res.Service) @@ -170,7 +171,9 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { } if len(groupInvokersMap) == 1 { //len is 1 it means no group setting ,so do not need cluster again - groupInvokersList = groupInvokersMap[""] + for _, invokers := range groupInvokersMap { + groupInvokersList = invokers + } } else { for _, invokers := range groupInvokersMap { staticDir := directory.NewStaticDirectory(invokers) diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index eafd3f7c17a47520bd91e99176814753f5d7ddef..a40452756c73d0b80a91e5424132d0c7bf8251f4 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -37,6 +37,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" "github.com/apache/dubbo-go/protocol/protocolwrapper" "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting" ) func TestSubscribe(t *testing.T) { @@ -50,7 +51,7 @@ func TestSubscribe_Delete(t *testing.T) { registryDirectory, mockRegistry := normalRegistryDir() time.Sleep(1e9) assert.Len(t, registryDirectory.cacheInvokers, 3) - mockRegistry.MockEvent(®istry.ServiceEvent{Action: registry.ServiceDel, Service: *common.NewURLWithOptions("TEST0", common.WithProtocol("dubbo"))}) + mockRegistry.MockEvent(®istry.ServiceEvent{Action: remoting.Del, Service: *common.NewURLWithOptions(common.WithPath("TEST0"), common.WithProtocol("dubbo"))}) time.Sleep(1e9) assert.Len(t, registryDirectory.cacheInvokers, 2) } @@ -73,14 +74,14 @@ func TestSubscribe_Group(t *testing.T) { mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) registryDirectory, _ := NewRegistryDirectory(®url, mockRegistry) - go registryDirectory.Subscribe(*common.NewURLWithOptions("testservice")) + go registryDirectory.Subscribe(*common.NewURLWithOptions(common.WithPath("testservice"))) //for group1 urlmap := url.Values{} urlmap.Set(constant.GROUP_KEY, "group1") urlmap.Set(constant.CLUSTER_KEY, "failover") //to test merge url for i := 0; i < 3; i++ { - mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: registry.ServiceAdd, Service: *common.NewURLWithOptions("TEST"+strconv.FormatInt(int64(i), 10), common.WithProtocol("dubbo"), + mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.Add, Service: *common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), common.WithParams(urlmap))}) } //for group2 @@ -88,7 +89,7 @@ func TestSubscribe_Group(t *testing.T) { urlmap2.Set(constant.GROUP_KEY, "group2") urlmap2.Set(constant.CLUSTER_KEY, "failover") //to test merge url for i := 0; i < 3; i++ { - mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: registry.ServiceAdd, Service: *common.NewURLWithOptions("TEST"+strconv.FormatInt(int64(i), 10), common.WithProtocol("dubbo"), + mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.Add, Service: *common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), common.WithParams(urlmap2))}) } @@ -126,9 +127,9 @@ func normalRegistryDir() (*registryDirectory, *registry.MockRegistry) { mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) registryDirectory, _ := NewRegistryDirectory(&url, mockRegistry) - go registryDirectory.Subscribe(*common.NewURLWithOptions("testservice")) + go registryDirectory.Subscribe(*common.NewURLWithOptions(common.WithPath("testservice"))) for i := 0; i < 3; i++ { - mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: registry.ServiceAdd, Service: *common.NewURLWithOptions("TEST"+strconv.FormatInt(int64(i), 10), common.WithProtocol("dubbo"))}) + mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.Add, Service: *common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"))}) } return registryDirectory, mockRegistry.(*registry.MockRegistry) } diff --git a/registry/event.go b/registry/event.go index ef51bbbe1710b1fdb166d34af58c630a5203b53a..24f5b72e8b27d4dc727e72d641d8bae3e00ff165 100644 --- a/registry/event.go +++ b/registry/event.go @@ -25,38 +25,19 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/remoting" ) func init() { rand.Seed(time.Now().UnixNano()) } -////////////////////////////////////////// -// service url event type -////////////////////////////////////////// - -type ServiceEventType int - -const ( - ServiceAdd = iota - ServiceDel -) - -var serviceEventTypeStrings = [...]string{ - "add service", - "delete service", -} - -func (t ServiceEventType) String() string { - return serviceEventTypeStrings[t] -} - ////////////////////////////////////////// // service event ////////////////////////////////////////// type ServiceEvent struct { - Action ServiceEventType + Action remoting.EventType Service common.URL } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index e318bd097cc0122f3e0ecf4db222da0edef64c46..ff33b5fe6e063257c12035f3262e0daae874363e 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -21,14 +21,11 @@ import ( "sync" ) -import ( - "github.com/apache/dubbo-go/common/logger" -) - import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/protocolwrapper" "github.com/apache/dubbo-go/registry" diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index d2cebde2ad7eabd9554069a969a390f193552d6c..4f3cf1e19ad28cb10be8adf85351c1f2eb4d4622 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -19,295 +19,64 @@ package zookeeper import ( "context" - "fmt" - "path" - "sync" - "time" ) - import ( - "github.com/apache/dubbo-go/common/logger" perrors "github.com/pkg/errors" - "github.com/samuel/go-zookeeper/zk" ) - import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting" + zk "github.com/apache/dubbo-go/remoting/zookeeper" ) -const ( - MaxFailTimes = 15 -) - -type zkEvent struct { - res *registry.ServiceEvent - err error +type RegistryDataListener struct { + interestedURL []*common.URL + listener *RegistryConfigurationListener } -func (e zkEvent) String() string { - return fmt.Sprintf("err:%s, res:%s", e.err, e.res) +func NewRegistryDataListener(listener *RegistryConfigurationListener) *RegistryDataListener { + return &RegistryDataListener{listener: listener, interestedURL: []*common.URL{}} } - -type zkEventListener struct { - client *zookeeperClient - events chan zkEvent - serviceMapLock sync.Mutex - serviceMap map[string]struct{} - wg sync.WaitGroup - registry *zkRegistry +func (l *RegistryDataListener) AddInterestedURL(url *common.URL) { + l.interestedURL = append(l.interestedURL, url) } -func newZkEventListener(registry *zkRegistry, client *zookeeperClient) *zkEventListener { - return &zkEventListener{ - client: client, - registry: registry, - events: make(chan zkEvent, 32), - serviceMap: make(map[string]struct{}), +func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { + serviceURL, err := common.NewURL(context.TODO(), eventType.Content) + if err != nil { + logger.Errorf("Listen NewURL(r{%s}) = error{%v}", eventType.Content, err) + return false } -} - -func (l *zkEventListener) listenServiceNodeEvent(zkPath string) bool { - l.wg.Add(1) - defer l.wg.Done() - var zkEvent zk.Event - for { - keyEventCh, err := l.client.existW(zkPath) - if err != nil { - logger.Errorf("existW{key:%s} = error{%v}", zkPath, err) - return false - } - - select { - case zkEvent = <-keyEventCh: - logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", - zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, stateToString(zkEvent.State), zkEvent.Err) - switch zkEvent.Type { - case zk.EventNodeDataChanged: - logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDataChanged}", zkPath) - case zk.EventNodeCreated: - logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeCreated}", zkPath) - case zk.EventNotWatching: - logger.Warnf("zk.ExistW(key{%s}) = event{EventNotWatching}", zkPath) - case zk.EventNodeDeleted: - logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDeleted}", zkPath) - return true - } - case <-l.client.done(): - return false + for _, v := range l.interestedURL { + if serviceURL.URLEqual(*v) { + l.listener.Process(&remoting.ConfigChangeEvent{Value: serviceURL, ConfigType: eventType.Action}) + return true } } return false } -func (l *zkEventListener) handleZkNodeEvent(zkPath string, children []string, conf common.URL) { - contains := func(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - - return false - } - - newChildren, err := l.client.getChildren(zkPath) - if err != nil { - logger.Errorf("path{%s} child nodes changed, zk.Children() = error{%v}", zkPath, perrors.WithStack(err)) - return - } - - // a node was added -- listen the new node - var ( - newNode string - serviceURL common.URL - ) - for _, n := range newChildren { - if contains(children, n) { - continue - } - - newNode = path.Join(zkPath, n) - logger.Infof("add zkNode{%s}", newNode) - //context.TODO - serviceURL, err = common.NewURL(context.TODO(), n) - if err != nil { - logger.Errorf("NewURL(%s) = error{%v}", n, perrors.WithStack(err)) - continue - } - if !conf.URLEqual(serviceURL) { - logger.Warnf("serviceURL{%s} is not compatible with SubURL{%#v}", serviceURL.Key(), conf.Key()) - continue - } - logger.Infof("add serviceURL{%s}", serviceURL) - l.events <- zkEvent{®istry.ServiceEvent{Action: registry.ServiceAdd, Service: serviceURL}, nil} - // listen l service node - go func(node string, serviceURL common.URL) { - logger.Infof("delete zkNode{%s}", node) - if l.listenServiceNodeEvent(node) { - logger.Infof("delete serviceURL{%s}", serviceURL) - l.events <- zkEvent{®istry.ServiceEvent{Action: registry.ServiceDel, Service: serviceURL}, nil} - } - logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) - }(newNode, serviceURL) - } - - // old node was deleted - var oldNode string - for _, n := range children { - if contains(newChildren, n) { - continue - } - - oldNode = path.Join(zkPath, n) - logger.Warnf("delete zkPath{%s}", oldNode) - serviceURL, err = common.NewURL(context.TODO(), n) - if !conf.URLEqual(serviceURL) { - logger.Warnf("serviceURL{%s} has been deleted is not compatible with SubURL{%#v}", serviceURL.Key(), conf.Key()) - continue - } - logger.Warnf("delete serviceURL{%s}", serviceURL) - if err != nil { - logger.Errorf("NewURL(i{%s}) = error{%v}", n, perrors.WithStack(err)) - continue - } - l.events <- zkEvent{®istry.ServiceEvent{Action: registry.ServiceDel, Service: serviceURL}, nil} - } +type RegistryConfigurationListener struct { + client *zk.ZookeeperClient + registry *zkRegistry + events chan *remoting.ConfigChangeEvent } -func (l *zkEventListener) listenDirEvent(zkPath string, conf common.URL) { - l.wg.Add(1) - defer l.wg.Done() - - var ( - failTimes int - event chan struct{} - zkEvent zk.Event - ) - event = make(chan struct{}, 4) - defer close(event) - for { - // get current children for a zkPath - children, childEventCh, err := l.client.getChildrenW(zkPath) - if err != nil { - failTimes++ - if MaxFailTimes <= failTimes { - failTimes = MaxFailTimes - } - logger.Errorf("listenDirEvent(path{%s}) = error{%v}", zkPath, err) - // clear the event channel - CLEAR: - for { - select { - case <-event: - default: - break CLEAR - } - } - l.client.registerEvent(zkPath, &event) - select { - case <-time.After(timeSecondDuration(failTimes * RegistryConnDelay)): - l.client.unregisterEvent(zkPath, &event) - continue - case <-l.client.done(): - l.client.unregisterEvent(zkPath, &event) - logger.Warnf("client.done(), listen(path{%s}, ReferenceConfig{%#v}) goroutine exit now...", zkPath, conf) - return - case <-event: - logger.Infof("get zk.EventNodeDataChange notify event") - l.client.unregisterEvent(zkPath, &event) - l.handleZkNodeEvent(zkPath, nil, conf) - continue - } - } - failTimes = 0 - - select { - case zkEvent = <-childEventCh: - logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", - zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, stateToString(zkEvent.State), zkEvent.Err) - if zkEvent.Type != zk.EventNodeChildrenChanged { - continue - } - l.handleZkNodeEvent(zkEvent.Path, children, conf) - case <-l.client.done(): - logger.Warnf("client.done(), listen(path{%s}, ReferenceConfig{%#v}) goroutine exit now...", zkPath, conf) - return - } - } +func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry) *RegistryConfigurationListener { + reg.wg.Add(1) + return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *remoting.ConfigChangeEvent, 32)} } - -// this func is invoked by ZkConsumerRegistry::Registe/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener -// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent -// | -// --------> listenServiceNodeEvent -func (l *zkEventListener) listenServiceEvent(conf common.URL) { - var ( - err error - zkPath string - dubboPath string - children []string - serviceURL common.URL - ) - - zkPath = fmt.Sprintf("/dubbo%s/providers", conf.Path) - - l.serviceMapLock.Lock() - _, ok := l.serviceMap[zkPath] - l.serviceMapLock.Unlock() - if ok { - logger.Warnf("@zkPath %s has already been listened.", zkPath) - return - } - - l.serviceMapLock.Lock() - l.serviceMap[zkPath] = struct{}{} - l.serviceMapLock.Unlock() - - logger.Infof("listen dubbo provider path{%s} event and wait to get all provider zk nodes", zkPath) - children, err = l.client.getChildren(zkPath) - if err != nil { - children = nil - logger.Errorf("fail to get children of zk path{%s}", zkPath) - } - - for _, c := range children { - serviceURL, err = common.NewURL(context.TODO(), c) - if err != nil { - logger.Errorf("NewURL(r{%s}) = error{%v}", c, err) - continue - } - if !conf.URLEqual(serviceURL) { - logger.Warnf("serviceURL %v is not compatible with SubURL %v", serviceURL.Key(), conf.Key()) - continue - } - logger.Debugf("add serviceUrl{%s}", serviceURL) - l.events <- zkEvent{®istry.ServiceEvent{Action: registry.ServiceAdd, Service: serviceURL}, nil} - - // listen l service node - dubboPath = path.Join(zkPath, c) - logger.Infof("listen dubbo service key{%s}", dubboPath) - go func(zkPath string, serviceURL common.URL) { - if l.listenServiceNodeEvent(dubboPath) { - logger.Debugf("delete serviceUrl{%s}", serviceURL) - l.events <- zkEvent{®istry.ServiceEvent{Action: registry.ServiceDel, Service: serviceURL}, nil} - } - logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) - }(dubboPath, serviceURL) - } - - logger.Infof("listen dubbo path{%s}", zkPath) - go func(zkPath string, conf common.URL) { - l.listenDirEvent(zkPath, conf) - logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) - }(zkPath, conf) +func (l *RegistryConfigurationListener) Process(configType *remoting.ConfigChangeEvent) { + l.events <- configType } -func (l *zkEventListener) Next() (*registry.ServiceEvent, error) { +func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { for { select { - case <-l.client.done(): + case <-l.client.Done(): logger.Warnf("listener's zk client connection is broken, so zk event listener exit now.") return nil, perrors.New("listener stopped") @@ -317,29 +86,21 @@ func (l *zkEventListener) Next() (*registry.ServiceEvent, error) { case e := <-l.events: logger.Debugf("got zk event %s", e) - if e.err != nil { - return nil, perrors.WithStack(e.err) - } - if e.res.Action == registry.ServiceDel && !l.valid() { - logger.Warnf("update @result{%s}. But its connection to registry is invalid", e.res) + if e.ConfigType == remoting.Del && !l.valid() { + logger.Warnf("update @result{%s}. But its connection to registry is invalid", e.Value) continue } //r.update(e.res) //write to invoker //r.outerEventCh <- e.res - return e.res, nil + return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(common.URL)}, nil } } } - -func (l *zkEventListener) valid() bool { - return l.client.zkConnValid() +func (l *RegistryConfigurationListener) Close() { + l.registry.wg.Done() } -func (l *zkEventListener) Close() { - l.registry.listenerLock.Lock() - l.client.Close() - l.registry.listenerLock.Unlock() - l.registry.wg.Done() - l.wg.Wait() +func (l *RegistryConfigurationListener) valid() bool { + return l.client.ZkConnValid() } diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index fbcc9e4546b908f5d11d3b2f8915003fa9512be2..21a156bc694f3b85033c692252c90d55106f3103 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -29,7 +29,6 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/logger" perrors "github.com/pkg/errors" "github.com/samuel/go-zookeeper/zk" ) @@ -38,15 +37,15 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/utils" "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting/zookeeper" "github.com/apache/dubbo-go/version" ) const ( - defaultTimeout = int64(10e9) - RegistryZkClient = "zk registry" - RegistryConnDelay = 3 + RegistryZkClient = "zk registry" ) var ( @@ -73,14 +72,16 @@ type zkRegistry struct { done chan struct{} cltLock sync.Mutex - client *zookeeperClient + client *zookeeper.ZookeeperClient services map[string]common.URL // service name + protocol -> service config - listenerLock sync.Mutex - listener *zkEventListener - + listenerLock sync.Mutex + listener *zookeeper.ZkEventListener + dataListener *RegistryDataListener + configListener *RegistryConfigurationListener //for provider zkPath map[string]int // key = protocol://ip:port/interface + } func newZkRegistry(url *common.URL) (registry.Registry, error) { @@ -97,30 +98,28 @@ func newZkRegistry(url *common.URL) (registry.Registry, error) { zkPath: make(map[string]int), } - //if r.SubURL.Name == "" { - // r.SubURL.Name = RegistryZkClient - //} - //if r.Version == "" { - // r.Version = version.Version - //} - - err = r.validateZookeeperClient() + err = zookeeper.ValidateZookeeperClient(r, zookeeper.WithZkName(RegistryZkClient)) if err != nil { return nil, err } r.wg.Add(1) - go r.handleZkRestart() + go zookeeper.HandleClientRestart(r) - //if r.RoleType == registry.CONSUMER { - // r.wg.Add(1) - // go r.listen() - //} + r.listener = zookeeper.NewZkEventListener(r.client) + r.configListener = NewRegistryConfigurationListener(r.client, r) + r.dataListener = NewRegistryDataListener(r.configListener) return r, nil } -func newMockZkRegistry(url *common.URL) (*zk.TestCluster, *zkRegistry, error) { +type Options struct { + client *zookeeper.ZookeeperClient +} + +type Option func(*Options) + +func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestCluster, *zkRegistry, error) { var ( err error r *zkRegistry @@ -136,139 +135,78 @@ func newMockZkRegistry(url *common.URL) (*zk.TestCluster, *zkRegistry, error) { zkPath: make(map[string]int), } - c, r.client, _, err = newMockZookeeperClient("test", 15*time.Second) + c, r.client, _, err = zookeeper.NewMockZookeeperClient("test", 15*time.Second, opts...) if err != nil { return nil, nil, err } - r.wg.Add(1) - go r.handleZkRestart() + go zookeeper.HandleClientRestart(r) - //if r.RoleType == registry.CONSUMER { - // r.wg.Add(1) - // go r.listen() - //} + r.listener = zookeeper.NewZkEventListener(r.client) + r.configListener = NewRegistryConfigurationListener(r.client, r) + r.dataListener = NewRegistryDataListener(r.configListener) return c, r, nil } +func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient { + return r.client +} + +func (r *zkRegistry) SetZkClient(client *zookeeper.ZookeeperClient) { + r.client = client +} + +func (r *zkRegistry) ZkClientLock() *sync.Mutex { + return &r.cltLock +} + +func (r *zkRegistry) WaitGroup() *sync.WaitGroup { + return &r.wg +} + +func (r *zkRegistry) GetDone() chan struct{} { + return r.done +} + func (r *zkRegistry) GetUrl() common.URL { return *r.URL } func (r *zkRegistry) Destroy() { - if r.listener != nil { - r.listener.Close() + if r.configListener != nil { + r.configListener.Close() } close(r.done) r.wg.Wait() r.closeRegisters() } -func (r *zkRegistry) validateZookeeperClient() error { - var ( - err error - ) +func (r *zkRegistry) RestartCallBack() bool { - err = nil - r.cltLock.Lock() - defer r.cltLock.Unlock() - if r.client == nil { - //in dubbp ,every registry only connect one node ,so this is []string{r.Address} - timeout, err := time.ParseDuration(r.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) - if err != nil { - logger.Errorf("timeout config %v is invalid ,err is %v", - r.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) - return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", r.Location) - } - r.client, err = newZookeeperClient(RegistryZkClient, []string{r.Location}, timeout) - if err != nil { - logger.Warnf("newZookeeperClient(name{%s}, zk addresss{%v}, timeout{%d}) = error{%v}", - RegistryZkClient, r.Location, timeout.String(), err) - return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", r.Location) - } + // copy r.services + services := []common.URL{} + for _, confIf := range r.services { + services = append(services, confIf) } - if r.client.conn == nil { - var event <-chan zk.Event - r.client.conn, event, err = zk.Connect(r.client.zkAddrs, r.client.timeout) - if err == nil { - r.client.wait.Add(1) - go r.client.handleZkEvent(event) - } - } - - return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", r.PrimitiveURL) -} - -func (r *zkRegistry) handleZkRestart() { - var ( - err error - flag bool - failTimes int - confIf common.URL - ) - defer r.wg.Done() -LOOP: - for { - select { - case <-r.done: - logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") - break LOOP - // re-register all services - case <-r.client.done(): - r.cltLock.Lock() - r.client.Close() - r.client = nil - r.cltLock.Unlock() - - // 接zk,直至成功 - failTimes = 0 - for { - select { - case <-r.done: - logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") - break LOOP - case <-time.After(time.Duration(1e9 * failTimes * RegistryConnDelay)): // 防止疯狂重连zk - } - err = r.validateZookeeperClient() - logger.Infof("ZkProviderRegistry.validateZookeeperClient(zkAddr{%s}) = error{%#v}", - r.client.zkAddrs, perrors.WithStack(err)) - if err == nil { - // copy r.services - services := []common.URL{} - for _, confIf = range r.services { - services = append(services, confIf) - } - - flag = true - for _, confIf = range services { - err = r.register(confIf) - if err != nil { - logger.Errorf("(ZkProviderRegistry)register(conf{%#v}) = error{%#v}", - confIf, perrors.WithStack(err)) - flag = false - break - } - logger.Infof("success to re-register service :%v", confIf.Key()) - } - if flag { - break - } - } - failTimes++ - if MaxFailTimes <= failTimes { - failTimes = MaxFailTimes - } - } + flag := true + for _, confIf := range services { + err := r.register(confIf) + if err != nil { + logger.Errorf("(ZkProviderRegistry)register(conf{%#v}) = error{%#v}", + confIf, perrors.WithStack(err)) + flag = false + break } + logger.Infof("success to re-register service :%v", confIf.Key()) } + return flag } func (r *zkRegistry) Register(conf common.URL) error { var ( - ok bool - err error - listener *zkEventListener + ok bool + err error ) role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) switch role { @@ -291,12 +229,6 @@ func (r *zkRegistry) Register(conf common.URL) error { r.cltLock.Unlock() logger.Debugf("(consumerZkConsumerRegistry)Register(conf{%#v})", conf) - r.listenerLock.Lock() - listener = r.listener - r.listenerLock.Unlock() - if listener != nil { - go listener.listenServiceEvent(conf) - } case common.PROVIDER: // 检验服务是否已经注册过 @@ -337,7 +269,7 @@ func (r *zkRegistry) register(c common.URL) error { //conf config.URL ) - err = r.validateZookeeperClient() + err = zookeeper.ValidateZookeeperClient(r, zookeeper.WithZkName(RegistryZkClient)) if err != nil { return perrors.WithStack(err) } @@ -359,7 +291,7 @@ func (r *zkRegistry) register(c common.URL) error { return perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods) } // 先创建服务下面的provider node - dubboPath = fmt.Sprintf("/dubbo%s/%s", c.Path, common.DubboNodes[common.PROVIDER]) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.PROVIDER]) r.cltLock.Lock() err = r.client.Create(dubboPath) r.cltLock.Unlock() @@ -397,11 +329,11 @@ func (r *zkRegistry) register(c common.URL) error { encodedURL = url.QueryEscape(rawURL) // 把自己注册service providers - dubboPath = fmt.Sprintf("/dubbo%s/%s", c.Path, (common.RoleType(common.PROVIDER)).String()) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), (common.RoleType(common.PROVIDER)).String()) logger.Debugf("provider path:%s, url:%s", dubboPath, rawURL) case common.CONSUMER: - dubboPath = fmt.Sprintf("/dubbo%s/%s", c.Path, common.DubboNodes[common.CONSUMER]) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.CONSUMER]) r.cltLock.Lock() err = r.client.Create(dubboPath) r.cltLock.Unlock() @@ -409,7 +341,7 @@ func (r *zkRegistry) register(c common.URL) error { logger.Errorf("zkClient.create(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) return perrors.WithStack(err) } - dubboPath = fmt.Sprintf("/dubbo%s/%s", c.Path, common.DubboNodes[common.PROVIDER]) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.PROVIDER]) r.cltLock.Lock() err = r.client.Create(dubboPath) r.cltLock.Unlock() @@ -426,8 +358,9 @@ func (r *zkRegistry) register(c common.URL) error { rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode()) encodedURL = url.QueryEscape(rawURL) - dubboPath = fmt.Sprintf("/dubbo%s/%s", c.Path, (common.RoleType(common.CONSUMER)).String()) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), (common.RoleType(common.CONSUMER)).String()) logger.Debugf("consumer path:%s, url:%s", dubboPath, rawURL) + default: return perrors.Errorf("@c{%v} type is not referencer or provider", c) } @@ -464,44 +397,37 @@ func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error { } func (r *zkRegistry) Subscribe(conf common.URL) (registry.Listener, error) { - r.wg.Add(1) return r.getListener(conf) } -func (r *zkRegistry) getListener(conf common.URL) (*zkEventListener, error) { +func (r *zkRegistry) getListener(conf common.URL) (*RegistryConfigurationListener, error) { var ( - zkListener *zkEventListener + zkListener *RegistryConfigurationListener ) r.listenerLock.Lock() - zkListener = r.listener + zkListener = r.configListener r.listenerLock.Unlock() - if zkListener != nil { - return zkListener, nil - } + if r.listener == nil { + r.cltLock.Lock() + client := r.client + r.cltLock.Unlock() + if client == nil { + return nil, perrors.New("zk connection broken") + } - r.cltLock.Lock() - client := r.client - r.cltLock.Unlock() - if client == nil { - return nil, perrors.New("zk connection broken") - } + // new client & listener + listener := zookeeper.NewZkEventListener(r.client) - // new client & listener - zkListener = newZkEventListener(r, client) + r.listenerLock.Lock() + r.listener = listener + r.listenerLock.Unlock() + } - r.listenerLock.Lock() - r.listener = zkListener - r.listenerLock.Unlock() + //注册到dataconfig的interested + r.dataListener.AddInterestedURL(&conf) - // listen - r.cltLock.Lock() - for _, svs := range r.services { - if svs.URLEqual(conf) { - go zkListener.listenServiceEvent(svs) - } - } - r.cltLock.Unlock() + go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/providers", conf.Service()), r.dataListener) return zkListener, nil } diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go index 2e85e12f3a87df880b78dc70760efaa7e6dd8203..ba2755fa9923d9e6c11a1908594a176ace458691 100644 --- a/registry/zookeeper/registry_test.go +++ b/registry/zookeeper/registry_test.go @@ -31,6 +31,7 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/remoting/zookeeper" ) func Test_Register(t *testing.T) { @@ -40,7 +41,7 @@ func Test_Register(t *testing.T) { ts, reg, err := newMockZkRegistry(®url) defer ts.Stop() err = reg.Register(url) - children, _ := reg.client.getChildren("/dubbo/com.ikurento.user.UserProvider/providers") + children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-2.6.0%26.*provider", children) assert.NoError(t, err) } @@ -49,7 +50,6 @@ func Test_Subscribe(t *testing.T) { regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) ts, reg, err := newMockZkRegistry(®url) - defer ts.Stop() //provider register err = reg.Register(url) @@ -61,8 +61,8 @@ func Test_Subscribe(t *testing.T) { //consumer register regurl.Params.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) - _, reg2, err := newMockZkRegistry(®url) - reg2.client = reg.client + _, reg2, err := newMockZkRegistry(®url, zookeeper.WithTestCluster(ts)) + err = reg2.Register(url) listener, err := reg2.Subscribe(url) @@ -71,8 +71,8 @@ func Test_Subscribe(t *testing.T) { if err != nil { return } - assert.Regexp(t, ".*ServiceEvent{Action{add service}.*", serviceEvent.String()) - + assert.Regexp(t, ".*ServiceEvent{Action{add}.*", serviceEvent.String()) + defer ts.Stop() } func Test_ConsumerDestory(t *testing.T) { diff --git a/remoting/listener.go b/remoting/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..37f75d46522c8b2405029725ba2178167ee19668 --- /dev/null +++ b/remoting/listener.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 remoting + +import "fmt" + +type ConfigurationListener interface { + Process(*ConfigChangeEvent) +} + +type DataListener interface { + DataChange(eventType Event) bool //bool is return for interface implement is interesting +} + +type ConfigChangeEvent struct { + Key string + Value interface{} + ConfigType EventType +} + +func (c ConfigChangeEvent) String() string { + return fmt.Sprintf("ConfigChangeEvent{key = %v , value = %v , changeType = %v}", c.Key, c.Value, c.ConfigType) +} + +////////////////////////////////////////// +// event type +////////////////////////////////////////// + +type EventType int + +const ( + Add = iota + Del +) + +var serviceEventTypeStrings = [...]string{ + "add", + "delete", +} + +func (t EventType) String() string { + return serviceEventTypeStrings[t] +} + +////////////////////////////////////////// +// service event +////////////////////////////////////////// + +type Event struct { + Path string + Action EventType + Content string +} + +func (e Event) String() string { + return fmt.Sprintf("Event{Action{%s}, Content{%s}}", e.Action, e.Content) +} diff --git a/registry/zookeeper/zk_client.go b/remoting/zookeeper/client.go similarity index 66% rename from registry/zookeeper/zk_client.go rename to remoting/zookeeper/client.go index a8b14bb2bd04938e84cf3c7f482f6ae650c3c98b..93382ed265ef2189cd0ad41bbe72adb8de606844 100644 --- a/registry/zookeeper/zk_client.go +++ b/remoting/zookeeper/client.go @@ -25,27 +25,36 @@ import ( ) import ( - "github.com/apache/dubbo-go/common/logger" perrors "github.com/pkg/errors" "github.com/samuel/go-zookeeper/zk" ) +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" +) + +const ( + ConnDelay = 3 + MaxFailTimes = 15 +) + var ( errNilZkClientConn = perrors.New("zookeeperclient{conn} is nil") ) -type zookeeperClient struct { +type ZookeeperClient struct { name string - zkAddrs []string + ZkAddrs []string sync.Mutex // for conn - conn *zk.Conn - timeout time.Duration + Conn *zk.Conn + Timeout time.Duration exit chan struct{} - wait sync.WaitGroup + Wait sync.WaitGroup eventRegistry map[string][]*chan struct{} } -func stateToString(state zk.State) string { +func StateToString(state zk.State) string { switch state { case zk.StateDisconnected: return "zookeeper disconnected" @@ -76,55 +85,128 @@ func stateToString(state zk.State) string { return "zookeeper unknown state" } -func timeSecondDuration(sec int) time.Duration { - return time.Duration(sec) * time.Second +type Options struct { + zkName string + client *ZookeeperClient + + ts *zk.TestCluster +} + +type Option func(*Options) + +func WithZkName(name string) Option { + return func(opt *Options) { + opt.zkName = name + } +} + +func ValidateZookeeperClient(container ZkClientContainer, opts ...Option) error { + var ( + err error + ) + opions := &Options{} + for _, opt := range opts { + opt(opions) + } + + err = nil + + lock := container.ZkClientLock() + url := container.GetUrl() + + lock.Lock() + defer lock.Unlock() + + if container.ZkClient() == nil { + //in dubbp ,every registry only connect one node ,so this is []string{r.Address} + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + logger.Errorf("timeout config %v is invalid ,err is %v", + url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) + return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location) + } + newClient, err := newZookeeperClient(opions.zkName, []string{url.Location}, timeout) + if err != nil { + logger.Warnf("newZookeeperClient(name{%s}, zk addresss{%v}, timeout{%d}) = error{%v}", + opions.zkName, url.Location, timeout.String(), err) + return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location) + } + container.SetZkClient(newClient) + } + + if container.ZkClient().Conn == nil { + var event <-chan zk.Event + container.ZkClient().Conn, event, err = zk.Connect(container.ZkClient().ZkAddrs, container.ZkClient().Timeout) + if err == nil { + container.ZkClient().Wait.Add(1) + go container.ZkClient().HandleZkEvent(event) + } + } + + return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.PrimitiveURL) } -func newZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*zookeeperClient, error) { +func newZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*ZookeeperClient, error) { var ( err error event <-chan zk.Event - z *zookeeperClient + z *ZookeeperClient ) - z = &zookeeperClient{ + z = &ZookeeperClient{ name: name, - zkAddrs: zkAddrs, - timeout: timeout, + ZkAddrs: zkAddrs, + Timeout: timeout, exit: make(chan struct{}), eventRegistry: make(map[string][]*chan struct{}), } // connect to zookeeper - z.conn, event, err = zk.Connect(zkAddrs, timeout) + z.Conn, event, err = zk.Connect(zkAddrs, timeout) if err != nil { return nil, perrors.WithMessagef(err, "zk.Connect(zkAddrs:%+v)", zkAddrs) } - z.wait.Add(1) - go z.handleZkEvent(event) + z.Wait.Add(1) + go z.HandleZkEvent(event) return z, nil } -func newMockZookeeperClient(name string, timeout time.Duration) (*zk.TestCluster, *zookeeperClient, <-chan zk.Event, error) { +func WithTestCluster(ts *zk.TestCluster) Option { + return func(opt *Options) { + opt.ts = ts + } +} + +func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Event, error) { var ( err error event <-chan zk.Event - z *zookeeperClient + z *ZookeeperClient + ts *zk.TestCluster ) - z = &zookeeperClient{ + z = &ZookeeperClient{ name: name, - zkAddrs: []string{}, - timeout: timeout, + ZkAddrs: []string{}, + Timeout: timeout, exit: make(chan struct{}), eventRegistry: make(map[string][]*chan struct{}), } - // connect to zookeeper - ts, err := zk.StartTestCluster(1, nil, nil) - if err != nil { - return nil, nil, nil, perrors.WithMessagef(err, "zk.Connect") + opions := &Options{} + for _, opt := range opts { + opt(opions) + } + + // connect to zookeeper + if opions.ts != nil { + ts = opions.ts + } else { + ts, err = zk.StartTestCluster(1, nil, nil) + if err != nil { + return nil, nil, nil, perrors.WithMessagef(err, "zk.Connect") + } } //callbackChan := make(chan zk.Event) @@ -132,7 +214,7 @@ func newMockZookeeperClient(name string, timeout time.Duration) (*zk.TestCluster // callbackChan <- event //} - z.conn, event, err = ts.ConnectWithOptions(timeout) + z.Conn, event, err = ts.ConnectWithOptions(timeout) if err != nil { return nil, nil, nil, perrors.WithMessagef(err, "zk.Connect") } @@ -141,15 +223,15 @@ func newMockZookeeperClient(name string, timeout time.Duration) (*zk.TestCluster return ts, z, event, nil } -func (z *zookeeperClient) handleZkEvent(session <-chan zk.Event) { +func (z *ZookeeperClient) HandleZkEvent(session <-chan zk.Event) { var ( state int event zk.Event ) defer func() { - z.wait.Done() - logger.Infof("zk{path:%v, name:%s} connection goroutine game over.", z.zkAddrs, z.name) + z.Wait.Done() + logger.Infof("zk{path:%v, name:%s} connection goroutine game over.", z.ZkAddrs, z.name) }() LOOP: @@ -159,15 +241,15 @@ LOOP: break LOOP case event = <-session: logger.Warnf("client{%s} get a zookeeper event{type:%s, server:%s, path:%s, state:%d-%s, err:%v}", - z.name, event.Type, event.Server, event.Path, event.State, stateToString(event.State), event.Err) + z.name, event.Type, event.Server, event.Path, event.State, StateToString(event.State), event.Err) switch (int)(event.State) { case (int)(zk.StateDisconnected): - logger.Warnf("zk{addr:%s} state is StateDisconnected, so close the zk client{name:%s}.", z.zkAddrs, z.name) + logger.Warnf("zk{addr:%s} state is StateDisconnected, so close the zk client{name:%s}.", z.ZkAddrs, z.name) z.stop() z.Lock() - if z.conn != nil { - z.conn.Close() - z.conn = nil + if z.Conn != nil { + z.Conn.Close() + z.Conn = nil } z.Unlock() break LOOP @@ -199,7 +281,7 @@ LOOP: } } -func (z *zookeeperClient) registerEvent(zkPath string, event *chan struct{}) { +func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) { if zkPath == "" || event == nil { return } @@ -212,7 +294,7 @@ func (z *zookeeperClient) registerEvent(zkPath string, event *chan struct{}) { z.Unlock() } -func (z *zookeeperClient) unregisterEvent(zkPath string, event *chan struct{}) { +func (z *ZookeeperClient) UnregisterEvent(zkPath string, event *chan struct{}) { if zkPath == "" { return } @@ -241,11 +323,11 @@ func (z *zookeeperClient) unregisterEvent(zkPath string, event *chan struct{}) { z.Unlock() } -func (z *zookeeperClient) done() <-chan struct{} { +func (z *ZookeeperClient) Done() <-chan struct{} { return z.exit } -func (z *zookeeperClient) stop() bool { +func (z *ZookeeperClient) stop() bool { select { case <-z.exit: return true @@ -256,7 +338,7 @@ func (z *zookeeperClient) stop() bool { return false } -func (z *zookeeperClient) zkConnValid() bool { +func (z *ZookeeperClient) ZkConnValid() bool { select { case <-z.exit: return false @@ -265,7 +347,7 @@ func (z *zookeeperClient) zkConnValid() bool { valid := true z.Lock() - if z.conn == nil { + if z.Conn == nil { valid = false } z.Unlock() @@ -273,23 +355,23 @@ func (z *zookeeperClient) zkConnValid() bool { return valid } -func (z *zookeeperClient) Close() { +func (z *ZookeeperClient) Close() { if z == nil { return } z.stop() - z.wait.Wait() + z.Wait.Wait() z.Lock() - if z.conn != nil { - z.conn.Close() - z.conn = nil + if z.Conn != nil { + z.Conn.Close() + z.Conn = nil } z.Unlock() - logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.zkAddrs) + logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs) } -func (z *zookeeperClient) Create(basePath string) error { +func (z *ZookeeperClient) Create(basePath string) error { var ( err error tmpPath string @@ -300,8 +382,8 @@ func (z *zookeeperClient) Create(basePath string) error { tmpPath = path.Join(tmpPath, "/", str) err = errNilZkClientConn z.Lock() - if z.conn != nil { - _, err = z.conn.Create(tmpPath, []byte(""), 0, zk.WorldACL(zk.PermAll)) + if z.Conn != nil { + _, err = z.Conn.Create(tmpPath, []byte(""), 0, zk.WorldACL(zk.PermAll)) } z.Unlock() if err != nil { @@ -317,22 +399,22 @@ func (z *zookeeperClient) Create(basePath string) error { return nil } -func (z *zookeeperClient) Delete(basePath string) error { +func (z *ZookeeperClient) Delete(basePath string) error { var ( err error ) err = errNilZkClientConn z.Lock() - if z.conn != nil { - err = z.conn.Delete(basePath, -1) + if z.Conn != nil { + err = z.Conn.Delete(basePath, -1) } z.Unlock() return perrors.WithMessagef(err, "Delete(basePath:%s)", basePath) } -func (z *zookeeperClient) RegisterTemp(basePath string, node string) (string, error) { +func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, error) { var ( err error data []byte @@ -344,8 +426,8 @@ func (z *zookeeperClient) RegisterTemp(basePath string, node string) (string, er data = []byte("") zkPath = path.Join(basePath) + "/" + node z.Lock() - if z.conn != nil { - tmpPath, err = z.conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) + if z.Conn != nil { + tmpPath, err = z.Conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) } z.Unlock() //if err != nil && err != zk.ErrNodeExists { @@ -358,7 +440,7 @@ func (z *zookeeperClient) RegisterTemp(basePath string, node string) (string, er return tmpPath, nil } -func (z *zookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, error) { +func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, error) { var ( err error tmpPath string @@ -366,8 +448,8 @@ func (z *zookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, err = errNilZkClientConn z.Lock() - if z.conn != nil { - tmpPath, err = z.conn.Create( + if z.Conn != nil { + tmpPath, err = z.Conn.Create( path.Join(basePath)+"/", data, zk.FlagEphemeral|zk.FlagSequence, @@ -386,7 +468,7 @@ func (z *zookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, return tmpPath, nil } -func (z *zookeeperClient) getChildrenW(path string) ([]string, <-chan zk.Event, error) { +func (z *ZookeeperClient) GetChildrenW(path string) ([]string, <-chan zk.Event, error) { var ( err error children []string @@ -396,8 +478,8 @@ func (z *zookeeperClient) getChildrenW(path string) ([]string, <-chan zk.Event, err = errNilZkClientConn z.Lock() - if z.conn != nil { - children, stat, event, err = z.conn.ChildrenW(path) + if z.Conn != nil { + children, stat, event, err = z.Conn.ChildrenW(path) } z.Unlock() if err != nil { @@ -417,7 +499,7 @@ func (z *zookeeperClient) getChildrenW(path string) ([]string, <-chan zk.Event, return children, event, nil } -func (z *zookeeperClient) getChildren(path string) ([]string, error) { +func (z *ZookeeperClient) GetChildren(path string) ([]string, error) { var ( err error children []string @@ -426,8 +508,8 @@ func (z *zookeeperClient) getChildren(path string) ([]string, error) { err = errNilZkClientConn z.Lock() - if z.conn != nil { - children, stat, err = z.conn.Children(path) + if z.Conn != nil { + children, stat, err = z.Conn.Children(path) } z.Unlock() if err != nil { @@ -447,7 +529,7 @@ func (z *zookeeperClient) getChildren(path string) ([]string, error) { return children, nil } -func (z *zookeeperClient) existW(zkPath string) (<-chan zk.Event, error) { +func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) { var ( exist bool err error @@ -456,8 +538,8 @@ func (z *zookeeperClient) existW(zkPath string) (<-chan zk.Event, error) { err = errNilZkClientConn z.Lock() - if z.conn != nil { - exist, _, event, err = z.conn.ExistsW(zkPath) + if z.Conn != nil { + exist, _, event, err = z.Conn.ExistsW(zkPath) } z.Unlock() if err != nil { diff --git a/registry/zookeeper/zk_client_test.go b/remoting/zookeeper/client_test.go similarity index 92% rename from registry/zookeeper/zk_client_test.go rename to remoting/zookeeper/client_test.go index ff98bb5bc783da0908c5f3cbe28c94385b0bd1db..4a71ebd6107c499bafe7baa7112e31dd53dfdfd4 100644 --- a/registry/zookeeper/zk_client_test.go +++ b/remoting/zookeeper/client_test.go @@ -93,7 +93,8 @@ func verifyEventOrder(t *testing.T, c <-chan zk.Event, expectedEvent []zk.EventT //} func Test_newMockZookeeperClient(t *testing.T) { - ts, z, event, _ := newMockZookeeperClient("test", 15*time.Second) + ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) defer ts.Stop() states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} verifyEventStateOrder(t, event, states, "event channel") @@ -103,7 +104,7 @@ func Test_newMockZookeeperClient(t *testing.T) { } func TestCreate(t *testing.T) { - ts, z, event, _ := newMockZookeeperClient("test", 15*time.Second) + ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() err := z.Create("test1/test2/test3/test4") assert.NoError(t, err) @@ -113,7 +114,7 @@ func TestCreate(t *testing.T) { } func TestCreateDelete(t *testing.T) { - ts, z, event, _ := newMockZookeeperClient("test", 15*time.Second) + ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} @@ -126,7 +127,7 @@ func TestCreateDelete(t *testing.T) { } func TestRegisterTemp(t *testing.T) { - ts, z, event, _ := newMockZookeeperClient("test", 15*time.Second) + ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() err := z.Create("/test1/test2/test3") assert.NoError(t, err) @@ -139,7 +140,7 @@ func TestRegisterTemp(t *testing.T) { } func TestRegisterTempSeq(t *testing.T) { - ts, z, event, _ := newMockZookeeperClient("test", 15*time.Second) + ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() err := z.Create("/test1/test2/test3") assert.NoError(t, err) diff --git a/remoting/zookeeper/container.go b/remoting/zookeeper/container.go new file mode 100644 index 0000000000000000000000000000000000000000..f869b32444a58c3396ec5520b7b8f859f4accd6f --- /dev/null +++ b/remoting/zookeeper/container.go @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zookeeper + +import ( + "sync" + "time" +) +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" +) + +type ZkClientContainer interface { + ZkClient() *ZookeeperClient + SetZkClient(*ZookeeperClient) + ZkClientLock() *sync.Mutex + WaitGroup() *sync.WaitGroup //for wait group control, zk client listener & zk client container + GetDone() chan struct{} //for zk client control + RestartCallBack() bool + common.Node +} + +func HandleClientRestart(r ZkClientContainer) { + var ( + err error + + failTimes int + ) + + defer r.WaitGroup().Done() +LOOP: + for { + select { + case <-r.GetDone(): + logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") + break LOOP + // re-register all services + case <-r.ZkClient().Done(): + r.ZkClientLock().Lock() + r.ZkClient().Close() + zkName := r.ZkClient().name + zkAddress := r.ZkClient().ZkAddrs + r.SetZkClient(nil) + r.ZkClientLock().Unlock() + + // 接zk,直至成功 + failTimes = 0 + for { + select { + case <-r.GetDone(): + logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") + break LOOP + case <-time.After(time.Duration(1e9 * failTimes * ConnDelay)): // 防止疯狂重连zk + } + err = ValidateZookeeperClient(r, WithZkName(zkName)) + logger.Infof("ZkProviderRegistry.validateZookeeperClient(zkAddr{%s}) = error{%#v}", + zkAddress, perrors.WithStack(err)) + if err == nil { + if r.RestartCallBack() { + break + } + } + failTimes++ + if MaxFailTimes <= failTimes { + failTimes = MaxFailTimes + } + } + } + } +} diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..703d06f84eacef8f830a75846c6130bb36239a1e --- /dev/null +++ b/remoting/zookeeper/listener.go @@ -0,0 +1,277 @@ +/* + * 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 zookeeper + +import ( + "path" + "sync" + "time" +) + +import ( + perrors "github.com/pkg/errors" + "github.com/samuel/go-zookeeper/zk" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/remoting" +) + +type ZkEventListener struct { + client *ZookeeperClient + pathMapLock sync.Mutex + pathMap map[string]struct{} + wg sync.WaitGroup +} + +func NewZkEventListener(client *ZookeeperClient) *ZkEventListener { + return &ZkEventListener{ + client: client, + pathMap: make(map[string]struct{}), + } +} +func (l *ZkEventListener) SetClient(client *ZookeeperClient) { + l.client = client +} +func (l *ZkEventListener) listenServiceNodeEvent(zkPath string) bool { + l.wg.Add(1) + defer l.wg.Done() + var zkEvent zk.Event + for { + keyEventCh, err := l.client.ExistW(zkPath) + if err != nil { + logger.Errorf("existW{key:%s} = error{%v}", zkPath, err) + return false + } + + select { + case zkEvent = <-keyEventCh: + logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", + zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) + switch zkEvent.Type { + case zk.EventNodeDataChanged: + logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDataChanged}", zkPath) + case zk.EventNodeCreated: + logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeCreated}", zkPath) + case zk.EventNotWatching: + logger.Warnf("zk.ExistW(key{%s}) = event{EventNotWatching}", zkPath) + case zk.EventNodeDeleted: + logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDeleted}", zkPath) + return true + } + case <-l.client.Done(): + return false + } + } + + return false +} + +func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, listener remoting.DataListener) { + contains := func(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + + return false + } + + newChildren, err := l.client.GetChildren(zkPath) + if err != nil { + logger.Errorf("path{%s} child nodes changed, zk.Children() = error{%v}", zkPath, perrors.WithStack(err)) + return + } + + // a node was added -- listen the new node + var ( + newNode string + ) + for _, n := range newChildren { + if contains(children, n) { + continue + } + + newNode = path.Join(zkPath, n) + logger.Infof("add zkNode{%s}", newNode) + if !listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Add, Content: n}) { + continue + } + // listen l service node + go func(node string) { + logger.Infof("delete zkNode{%s}", node) + if l.listenServiceNodeEvent(node) { + logger.Infof("delete content{%s}", n) + listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Del, Content: n}) + } + logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) + }(newNode) + } + + // old node was deleted + var oldNode string + for _, n := range children { + if contains(newChildren, n) { + continue + } + + oldNode = path.Join(zkPath, n) + logger.Warnf("delete zkPath{%s}", oldNode) + if !listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Add, Content: n}) { + continue + } + logger.Warnf("delete content{%s}", n) + if err != nil { + logger.Errorf("NewURL(i{%s}) = error{%v}", n, perrors.WithStack(err)) + continue + } + listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Del, Content: n}) + } +} + +func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataListener) { + l.wg.Add(1) + defer l.wg.Done() + + var ( + failTimes int + event chan struct{} + zkEvent zk.Event + ) + event = make(chan struct{}, 4) + defer close(event) + for { + // get current children for a zkPath + children, childEventCh, err := l.client.GetChildrenW(zkPath) + if err != nil { + failTimes++ + if MaxFailTimes <= failTimes { + failTimes = MaxFailTimes + } + logger.Errorf("listenDirEvent(path{%s}) = error{%v}", zkPath, err) + // clear the event channel + CLEAR: + for { + select { + case <-event: + default: + break CLEAR + } + } + l.client.RegisterEvent(zkPath, &event) + select { + case <-time.After(timeSecondDuration(failTimes * ConnDelay)): + l.client.UnregisterEvent(zkPath, &event) + continue + case <-l.client.Done(): + l.client.UnregisterEvent(zkPath, &event) + logger.Warnf("client.done(), listen(path{%s}) goroutine exit now...", zkPath) + return + case <-event: + logger.Infof("get zk.EventNodeDataChange notify event") + l.client.UnregisterEvent(zkPath, &event) + l.handleZkNodeEvent(zkPath, nil, listener) + continue + } + } + failTimes = 0 + + select { + case zkEvent = <-childEventCh: + logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", + zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) + if zkEvent.Type != zk.EventNodeChildrenChanged { + continue + } + l.handleZkNodeEvent(zkEvent.Path, children, listener) + case <-l.client.Done(): + logger.Warnf("client.done(), listen(path{%s}) goroutine exit now...", zkPath) + return + } + } +} + +func timeSecondDuration(sec int) time.Duration { + return time.Duration(sec) * time.Second +} + +// this func is invoked by ZkConsumerRegistry::Registe/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener +// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent +// | +// --------> listenServiceNodeEvent +func (l *ZkEventListener) ListenServiceEvent(zkPath string, listener remoting.DataListener) { + var ( + err error + dubboPath string + children []string + serviceURL common.URL + ) + + l.pathMapLock.Lock() + _, ok := l.pathMap[zkPath] + l.pathMapLock.Unlock() + if ok { + logger.Warnf("@zkPath %s has already been listened.", zkPath) + return + } + + l.pathMapLock.Lock() + l.pathMap[zkPath] = struct{}{} + l.pathMapLock.Unlock() + + logger.Infof("listen dubbo provider path{%s} event and wait to get all provider zk nodes", zkPath) + children, err = l.client.GetChildren(zkPath) + if err != nil { + children = nil + logger.Errorf("fail to get children of zk path{%s}", zkPath) + } + + for _, c := range children { + if !listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Add, Content: c}) { + continue + } + + // listen l service node + dubboPath = path.Join(zkPath, c) + logger.Infof("listen dubbo service key{%s}", dubboPath) + go func(zkPath string, serviceURL common.URL) { + if l.listenServiceNodeEvent(dubboPath) { + logger.Debugf("delete serviceUrl{%s}", serviceURL) + listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.Del, Content: c}) + } + logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) + }(dubboPath, serviceURL) + } + + logger.Infof("listen dubbo path{%s}", zkPath) + go func(zkPath string, listener remoting.DataListener) { + l.listenDirEvent(zkPath, listener) + logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) + }(zkPath, listener) +} + +func (l *ZkEventListener) valid() bool { + return l.client.ZkConnValid() +} + +func (l *ZkEventListener) Close() { + l.wg.Wait() +}