diff --git a/README.md b/README.md
index 5c4de0559d534e03b23d136da5f5f23ff6768d55..9bade617c8b05ec52c2018cf231ae036a7ae91d3 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Apache License, Version 2.0
## Release note ##
-[v1.4.0-rc1 - Mar 12, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.4.0-rc1)
+[v1.4.0 - Mar 17, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.4.0)
[v1.3.0 - Mar 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.3.0)
diff --git a/README_CN.md b/README_CN.md
index 5dec68fd6134fc58bf265ea382473d724332ecc6..180759f36663a587ee02232e229ae7c3ebbb06c1 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -15,7 +15,7 @@ Apache License, Version 2.0
## 发布日志 ##
-[v1.4.0-rc1 - 2020年3月12日](https://github.com/apache/dubbo-go/releases/tag/v1.4.0-rc1)
+[v1.4.0 - 2020年3月17日](https://github.com/apache/dubbo-go/releases/tag/v1.4.0)
[v1.3.0 - 2020年3月1日](https://github.com/apache/dubbo-go/releases/tag/v1.3.0)
diff --git a/common/constant/key.go b/common/constant/key.go
index 8c84a2a8884d909bdd743f738f3d69dde399cc22..9af387193e2ad47f5f6adb6f7c88eec6244467b2 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -131,6 +131,7 @@ const (
ProviderConfigPrefix = "dubbo.provider."
ConsumerConfigPrefix = "dubbo.consumer."
ShutdownConfigPrefix = "dubbo.shutdown."
+ MetadataReportPrefix = "dubbo.metadata-report."
RouterConfigPrefix = "dubbo.router."
)
@@ -179,6 +180,9 @@ const (
// ForceUseTag is the tag in attachment
ForceUseTag = "dubbo.force.tag"
Tagkey = "dubbo.tag"
+
+ // Attachment key in context in invoker
+ AttachmentKey = "attachment"
)
const (
@@ -209,9 +213,23 @@ const (
// consumer
CONSUMER = "consumer"
// key of access key id
- ACCESS_KEY_ID_KEY = "accessKeyId"
+ ACCESS_KEY_ID_KEY = ".accessKeyId"
// key of secret access key
- SECRET_ACCESS_KEY_KEY = "secretAccessKey"
+ SECRET_ACCESS_KEY_KEY = ".secretAccessKey"
+)
+
+// metadata report
+
+const (
+ METACONFIG_REMOTE = "remote"
+ METACONFIG_LOCAL = "local"
+ KEY_SEPARATOR = ":"
+ DEFAULT_PATH_TAG = "metadata"
+ KEY_REVISON_PREFIX = "revision"
+ PATH_SEPARATOR = "/"
+
+ // metadata service
+ METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService"
)
// HealthCheck Router
@@ -235,3 +253,9 @@ const (
// The default time window of circuit-tripped in millisecond if not specfied
MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000
)
+
+// service discovery
+
+const (
+ NACOS_GROUP = "nacos.group"
+)
diff --git a/common/extension/metadata_report_factory.go b/common/extension/metadata_report_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ae0793bb4459767cb42fb1860fc484388aae1a3
--- /dev/null
+++ b/common/extension/metadata_report_factory.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/metadata"
+)
+
+var (
+ metaDataReportFactories = make(map[string]func() metadata.MetadataReportFactory, 8)
+)
+
+// SetMetadataReportFactory ...
+func SetMetadataReportFactory(name string, v func() metadata.MetadataReportFactory) {
+ metaDataReportFactories[name] = v
+}
+
+// GetMetadataReportFactory ...
+func GetMetadataReportFactory(name string) metadata.MetadataReportFactory {
+ if metaDataReportFactories[name] == nil {
+ panic("metadata report for " + name + " is not existing, make sure you have import the package.")
+ }
+ return metaDataReportFactories[name]()
+}
diff --git a/common/extension/registry_directory.go b/common/extension/registry_directory.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b92189c4e98b391a90e6e71a68d51a252eede2a
--- /dev/null
+++ b/common/extension/registry_directory.go
@@ -0,0 +1,41 @@
+/*
+ * 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/cluster"
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/registry"
+)
+
+type registryDirectory func(url *common.URL, registry registry.Registry) (cluster.Directory, error)
+
+var defaultRegistry registryDirectory
+
+// SetDefaultRegistryDirectory ...
+func SetDefaultRegistryDirectory(v registryDirectory) {
+ defaultRegistry = v
+}
+
+// GetDefaultRegistryDirectory ...
+func GetDefaultRegistryDirectory(config *common.URL, registry registry.Registry) (cluster.Directory, error) {
+ if defaultRegistry == nil {
+ panic("registry directory is not existing, make sure you have import the package.")
+ }
+ return defaultRegistry(config, registry)
+}
diff --git a/common/extension/service_discovery.go b/common/extension/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..25b80cf3353505c058bea40cc4c80712ad923d2d
--- /dev/null
+++ b/common/extension/service_discovery.go
@@ -0,0 +1,45 @@
+/*
+ * 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 (
+ perrors "github.com/pkg/errors"
+)
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/registry"
+)
+
+var (
+ discoveryCreatorMap = make(map[string]func(url *common.URL) (registry.ServiceDiscovery, error), 4)
+)
+
+// SetServiceDiscovery will store the creator and name
+func SetServiceDiscovery(name string, creator func(url *common.URL) (registry.ServiceDiscovery, error)) {
+ discoveryCreatorMap[name] = creator
+}
+
+// GetServiceDiscovery will return the registry.ServiceDiscovery
+// if not found, or initialize instance failed, it will return error.
+func GetServiceDiscovery(name string, url *common.URL) (registry.ServiceDiscovery, error) {
+ creator, ok := discoveryCreatorMap[name]
+ if !ok {
+ return nil, perrors.New("Could not find the service discovery with name: " + name)
+ }
+ return creator(url)
+}
diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go
index 68ba3ff7882837a9419c5e47228461af11fd79ba..a77d26d1103e605f5d0b38ac14c8bb3e20fc27b8 100644
--- a/common/proxy/proxy.go
+++ b/common/proxy/proxy.go
@@ -25,6 +25,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/protocol"
invocation_impl "github.com/apache/dubbo-go/protocol/invocation"
@@ -44,7 +45,7 @@ var (
typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type()
)
-// NewProxy ...
+// NewProxy create service proxy.
func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[string]string) *Proxy {
return &Proxy{
invoke: invoke,
@@ -59,7 +60,6 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str
// type XxxProvider struct {
// Yyy func(ctx context.Context, args []interface{}, rsp *Zzz) error
// }
-
func (p *Proxy) Implement(v common.RPCService) {
// check parameters, incoming interface must be a elem's pointer.
@@ -141,7 +141,7 @@ func (p *Proxy) Implement(v common.RPCService) {
}
// add user setAttachment
- atm := invCtx.Value("attachment")
+ atm := invCtx.Value(constant.AttachmentKey)
if m, ok := atm.(map[string]string); ok {
for k, value := range m {
inv.SetAttachments(k, value)
@@ -149,6 +149,9 @@ func (p *Proxy) Implement(v common.RPCService) {
}
result := p.invoke.Invoke(invCtx, inv)
+ if len(result.Attachments()) > 0 {
+ invCtx = context.WithValue(invCtx, constant.AttachmentKey, result.Attachments())
+ }
err = result.Error()
logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
@@ -202,12 +205,12 @@ func (p *Proxy) Implement(v common.RPCService) {
}
-// Get ...
+// Get get rpc service instance.
func (p *Proxy) Get() common.RPCService {
return p.rpc
}
-// GetCallback ...
+// GetCallback get callback.
func (p *Proxy) GetCallback() interface{} {
return p.callBack
}
diff --git a/common/proxy/proxy_factory.go b/common/proxy/proxy_factory.go
index 7b249a3e9754b097130a80bf3819d282dad6b6e8..34fa3fd07eacae95351f302158d7da68165ea5cf 100644
--- a/common/proxy/proxy_factory.go
+++ b/common/proxy/proxy_factory.go
@@ -22,7 +22,7 @@ import (
"github.com/apache/dubbo-go/protocol"
)
-// ProxyFactory ...
+// ProxyFactory interface.
type ProxyFactory interface {
GetProxy(invoker protocol.Invoker, url *common.URL) *Proxy
GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *Proxy
diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go
index 114cfee2363022da5f7957a825a16fc42b8c928f..013b39911054ef5bd89d38cd50116a204b117872 100644
--- a/common/proxy/proxy_factory/default.go
+++ b/common/proxy/proxy_factory/default.go
@@ -113,6 +113,7 @@ func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocati
in := []reflect.Value{svc.Rcvr()}
if method.CtxType() != nil {
+ ctx = context.WithValue(ctx, constant.AttachmentKey, invocation.Attachments())
in = append(in, method.SuiteContext(ctx))
}
diff --git a/common/rpc_service.go b/common/rpc_service.go
index b138b4f300387a512932ee8869db493c24f4cb27..d7d900718e911fe12915225c04338d52e5f084f5 100644
--- a/common/rpc_service.go
+++ b/common/rpc_service.go
@@ -59,7 +59,6 @@ type AsyncCallback func(response CallbackResponse)
// return map[string][string]{}
// }
const (
- // METHOD_MAPPER ...
METHOD_MAPPER = "MethodMapper"
)
@@ -68,7 +67,7 @@ var (
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
- // ServiceMap ...
+ // ServiceMap store description of service.
// todo: lowerecas?
ServiceMap = &serviceMap{
serviceMap: make(map[string]map[string]*Service),
@@ -80,7 +79,7 @@ var (
// info of method
//////////////////////////
-// MethodType ...
+// MethodType is description of service method.
type MethodType struct {
method reflect.Method
ctxType reflect.Type // request context
@@ -88,27 +87,27 @@ type MethodType struct {
replyType reflect.Type // return value, otherwise it is nil
}
-// Method ...
+// Method get @m.method.
func (m *MethodType) Method() reflect.Method {
return m.method
}
-// CtxType ...
+// CtxType get @m.ctxType.
func (m *MethodType) CtxType() reflect.Type {
return m.ctxType
}
-// ArgsType ...
+// ArgsType get @m.argsType.
func (m *MethodType) ArgsType() []reflect.Type {
return m.argsType
}
-// ReplyType ...
+// ReplyType get @m.replyType.
func (m *MethodType) ReplyType() reflect.Type {
return m.replyType
}
-// SuiteContext ...
+// SuiteContext tranfer @ctx to reflect.Value type or get it from @m.ctxType.
func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value {
if contextv := reflect.ValueOf(ctx); contextv.IsValid() {
return contextv
@@ -120,7 +119,7 @@ func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value {
// info of service interface
//////////////////////////
-// Service ...
+// Service is description of service
type Service struct {
name string
rcvr reflect.Value
@@ -128,17 +127,17 @@ type Service struct {
methods map[string]*MethodType
}
-// Method ...
+// Method get @s.methods.
func (s *Service) Method() map[string]*MethodType {
return s.methods
}
-// RcvrType ...
+// RcvrType get @s.rcvrType.
func (s *Service) RcvrType() reflect.Type {
return s.rcvrType
}
-// Rcvr ...
+// Rcvr get @s.rcvr.
func (s *Service) Rcvr() reflect.Value {
return s.rcvr
}
@@ -274,7 +273,9 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro
}
}
delete(svcs, serviceId)
- delete(sm.serviceMap, protocol)
+ if len(sm.serviceMap) == 0 {
+ delete(sm.serviceMap, protocol)
+ }
return nil
}
diff --git a/common/url.go b/common/url.go
index ebb648db27c3efff534f0d0a545f2211f335aa89..a70ac7dc9dd6f415aa458689864604a793c8e256 100644
--- a/common/url.go
+++ b/common/url.go
@@ -195,7 +195,6 @@ func NewURLWithOptions(opts ...option) *URL {
// NewURL will create a new url
// the urlString should not be empty
func NewURL(urlString string, opts ...option) (URL, error) {
-
var (
err error
rawUrlString string
@@ -249,7 +248,7 @@ func NewURL(urlString string, opts ...option) (URL, error) {
return s, nil
}
-// URLEqual ...
+// URLEqual judge @url and @c is equal or not.
func (c URL) URLEqual(url URL) bool {
c.Ip = ""
c.Port = ""
@@ -265,17 +264,19 @@ func (c URL) URLEqual(url URL) bool {
} else if urlGroup == constant.ANY_VALUE {
urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1)
}
+
+ // 1. protocol, username, password, ip, port, service name, group, version should be equal
if cKey != urlKey {
return false
}
+
+ // 2. if url contains enabled key, should be true, or *
if url.GetParam(constant.ENABLED_KEY, "true") != "true" && url.GetParam(constant.ENABLED_KEY, "") != constant.ANY_VALUE {
return false
}
+
//TODO :may need add interface key any value condition
- if !isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) {
- return false
- }
- return true
+ return isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY))
}
func isMatchCategory(category1 string, category2 string) bool {
@@ -313,10 +314,9 @@ func (c URL) Key() string {
"%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()
}
-// ServiceKey ...
+// ServiceKey get a unique key of a service.
func (c URL) ServiceKey() string {
intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
if intf == "" {
@@ -409,12 +409,12 @@ func (c *URL) RangeParams(f func(key, value string) bool) {
// GetParam ...
func (c URL) GetParam(s string, d string) string {
- var r string
c.paramsLock.RLock()
- if r = c.params.Get(s); len(r) == 0 {
+ defer c.paramsLock.RUnlock()
+ r := c.params.Get(s)
+ if len(r) == 0 {
r = d
}
- c.paramsLock.RUnlock()
return r
}
@@ -454,10 +454,8 @@ func (c URL) GetRawParam(key string) string {
// GetParamBool ...
func (c URL) GetParamBool(s string, d bool) bool {
-
- var r bool
- var err error
- if r, err = strconv.ParseBool(c.GetParam(s, "")); err != nil {
+ r, err := strconv.ParseBool(c.GetParam(s, ""))
+ if err != nil {
return d
}
return r
@@ -465,10 +463,8 @@ func (c URL) GetParamBool(s string, d bool) bool {
// GetParamInt ...
func (c URL) GetParamInt(s string, d int64) int64 {
- var r int
- var err error
-
- if r, err = strconv.Atoi(c.GetParam(s, "")); r == 0 || err != nil {
+ r, err := strconv.Atoi(c.GetParam(s, ""))
+ if r == 0 || err != nil {
return d
}
return int64(r)
@@ -476,11 +472,8 @@ func (c URL) GetParamInt(s string, d int64) int64 {
// GetMethodParamInt ...
func (c URL) GetMethodParamInt(method string, key string, d int64) int64 {
- var r int
- var err error
- c.paramsLock.RLock()
- defer c.paramsLock.RUnlock()
- if r, err = strconv.Atoi(c.GetParam("methods."+method+"."+key, "")); r == 0 || err != nil {
+ r, err := strconv.Atoi(c.GetParam("methods."+method+"."+key, ""))
+ if r == 0 || err != nil {
return d
}
return int64(r)
@@ -492,14 +485,13 @@ func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 {
if r == math.MinInt64 {
return c.GetParamInt(key, d)
}
-
return r
}
// GetMethodParam ...
func (c URL) GetMethodParam(method string, key string, d string) string {
- var r string
- if r = c.GetParam("methods."+method+"."+key, ""); r == "" {
+ r := c.GetParam("methods."+method+"."+key, "")
+ if r == "" {
r = d
}
return r
@@ -530,7 +522,6 @@ func (c *URL) SetParams(m url.Values) {
// ToMap transfer URL to Map
func (c URL) ToMap() map[string]string {
-
paramsMap := make(map[string]string)
c.RangeParams(func(key, value string) bool {
@@ -615,8 +606,30 @@ func (c *URL) Clone() *URL {
return newUrl
}
+// Copy url based on the reserved parameters' keys.
+func (c *URL) CloneWithParams(reserveParams []string) *URL {
+ params := url.Values{}
+ for _, reserveParam := range reserveParams {
+ v := c.GetParam(reserveParam, "")
+ if len(v) != 0 {
+ params.Set(reserveParam, v)
+ }
+ }
+
+ return NewURLWithOptions(
+ WithProtocol(c.Protocol),
+ WithUsername(c.Username),
+ WithPassword(c.Password),
+ WithIp(c.Ip),
+ WithPort(c.Port),
+ WithPath(c.Path),
+ WithMethods(c.Methods),
+ WithParams(params),
+ )
+}
+
func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) {
- var methodConfigMergeFcn = []func(method string){}
+ methodConfigMergeFcn := make([]func(method string), 0, len(paramKeys))
for _, paramKey := range paramKeys {
if v := referenceUrl.GetParam(paramKey, ""); len(v) > 0 {
mergedUrl.SetParam(paramKey, v)
diff --git a/config/application_config.go b/config/application_config.go
index 23ab7d34aceaba02d7f592906d6f4e3d6cf36dae..33b47c81dd0da9959984cd1f53648167863cb713 100644
--- a/config/application_config.go
+++ b/config/application_config.go
@@ -33,6 +33,7 @@ type ApplicationConfig struct {
Version string `yaml:"version" json:"version,omitempty" property:"version"`
Owner string `yaml:"owner" json:"owner,omitempty" property:"owner"`
Environment string `yaml:"environment" json:"environment,omitempty" property:"environment"`
+ MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"` //field for metadata report
}
// Prefix ...
diff --git a/config/config_loader.go b/config/config_loader.go
index 61cb49457b7a2435cb22f02a9df0a02a71ae68cb..c84817eaea9d2ba825c96bdb7b2945018b523114 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -81,128 +81,142 @@ func checkApplicationName(config *ApplicationConfig) {
}
}
-// Load Dubbo Init
-func Load() {
-
- // init router
- if confRouterFile != "" {
- if errPro := RouterInit(confRouterFile); errPro != nil {
- log.Printf("[routerConfig init] %#v", errPro)
- }
- }
-
- // reference config
+func loadConsumerConfig() {
if consumerConfig == nil {
logger.Warnf("consumerConfig is nil!")
- } else {
- // init other consumer config
- conConfigType := consumerConfig.ConfigType
- for key, value := range extension.GetDefaultConfigReader() {
- if conConfigType == nil {
- if v, ok := conConfigType[key]; ok {
- value = v
- }
- }
- if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil {
- logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value)
+ return
+ }
+ // init other consumer config
+ conConfigType := consumerConfig.ConfigType
+ for key, value := range extension.GetDefaultConfigReader() {
+ if conConfigType == nil {
+ if v, ok := conConfigType[key]; ok {
+ value = v
}
}
+ if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil {
+ logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value)
+ }
+ }
- metricConfig = consumerConfig.MetricConfig
- applicationConfig = consumerConfig.ApplicationConfig
+ metricConfig = consumerConfig.MetricConfig
+ applicationConfig = consumerConfig.ApplicationConfig
- checkApplicationName(consumerConfig.ApplicationConfig)
- if err := configCenterRefreshConsumer(); err != nil {
- logger.Errorf("[consumer config center refresh] %#v", err)
+ checkApplicationName(consumerConfig.ApplicationConfig)
+ if err := configCenterRefreshConsumer(); err != nil {
+ logger.Errorf("[consumer config center refresh] %#v", err)
+ }
+ checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
+ for key, ref := range consumerConfig.References {
+ if ref.Generic {
+ genericService := NewGenericService(key)
+ SetConsumerService(genericService)
}
- checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
- for key, ref := range consumerConfig.References {
- if ref.Generic {
- genericService := NewGenericService(key)
- SetConsumerService(genericService)
- }
- rpcService := GetConsumerService(key)
- if rpcService == nil {
- logger.Warnf("%s does not exist!", key)
- continue
- }
- ref.id = key
- ref.Refer(rpcService)
- ref.Implement(rpcService)
+ rpcService := GetConsumerService(key)
+ if rpcService == nil {
+ logger.Warnf("%s does not exist!", key)
+ continue
}
+ ref.id = key
+ ref.Refer(rpcService)
+ ref.Implement(rpcService)
+ }
- //wait for invoker is available, if wait over default 3s, then panic
- var count int
- checkok := true
- for {
- for _, refconfig := range consumerConfig.References {
- if (refconfig.Check != nil && *refconfig.Check) ||
- (refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) ||
- (refconfig.Check == nil && consumerConfig.Check == nil) { //default to true
-
- if refconfig.invoker != nil &&
- !refconfig.invoker.IsAvailable() {
- checkok = false
- count++
- if count > maxWait {
- errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version)
- logger.Error(errMsg)
- panic(errMsg)
- }
- time.Sleep(time.Second * 1)
- break
- }
- if refconfig.invoker == nil {
- logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName)
+ //wait for invoker is available, if wait over default 3s, then panic
+ var count int
+ checkok := true
+ for {
+ for _, refconfig := range consumerConfig.References {
+ if (refconfig.Check != nil && *refconfig.Check) ||
+ (refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) ||
+ (refconfig.Check == nil && consumerConfig.Check == nil) { //default to true
+
+ if refconfig.invoker != nil &&
+ !refconfig.invoker.IsAvailable() {
+ checkok = false
+ count++
+ if count > maxWait {
+ errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version)
+ logger.Error(errMsg)
+ panic(errMsg)
}
+ time.Sleep(time.Second * 1)
+ break
+ }
+ if refconfig.invoker == nil {
+ logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName)
}
}
- if checkok {
- break
- }
- checkok = true
}
+ if checkok {
+ break
+ }
+ checkok = true
}
+}
- // service config
+func loadProviderConfig() {
if providerConfig == nil {
logger.Warnf("providerConfig is nil!")
- } else {
- // init other provider config
- proConfigType := providerConfig.ConfigType
- for key, value := range extension.GetDefaultConfigReader() {
- if proConfigType != nil {
- if v, ok := proConfigType[key]; ok {
- value = v
- }
- }
- if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil {
- logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value)
+ return
+ }
+
+ // init other provider config
+ proConfigType := providerConfig.ConfigType
+ for key, value := range extension.GetDefaultConfigReader() {
+ if proConfigType != nil {
+ if v, ok := proConfigType[key]; ok {
+ value = v
}
}
+ if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil {
+ logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value)
+ }
+ }
- // so, you should know that the consumer's config will be override
- metricConfig = providerConfig.MetricConfig
- applicationConfig = providerConfig.ApplicationConfig
+ // so, you should know that the consumer's config will be override
+ metricConfig = providerConfig.MetricConfig
+ applicationConfig = providerConfig.ApplicationConfig
- checkApplicationName(providerConfig.ApplicationConfig)
- if err := configCenterRefreshProvider(); err != nil {
- logger.Errorf("[provider config center refresh] %#v", err)
+ checkApplicationName(providerConfig.ApplicationConfig)
+ if err := configCenterRefreshProvider(); err != nil {
+ logger.Errorf("[provider config center refresh] %#v", err)
+ }
+ checkRegistries(providerConfig.Registries, providerConfig.Registry)
+ for key, svs := range providerConfig.Services {
+ rpcService := GetProviderService(key)
+ if rpcService == nil {
+ logger.Warnf("%s does not exist!", key)
+ continue
}
- checkRegistries(providerConfig.Registries, providerConfig.Registry)
- for key, svs := range providerConfig.Services {
- rpcService := GetProviderService(key)
- if rpcService == nil {
- logger.Warnf("%s does not exist!", key)
- continue
- }
- svs.id = key
- svs.Implement(rpcService)
- if err := svs.Export(); err != nil {
- panic(fmt.Sprintf("service %s export failed! err: %#v", key, err))
- }
+ svs.id = key
+ svs.Implement(rpcService)
+ if err := svs.Export(); err != nil {
+ panic(fmt.Sprintf("service %s export failed! err: %#v", key, err))
}
}
+}
+
+func initRouter() {
+ if confRouterFile != "" {
+ if err := RouterInit(confRouterFile); err != nil {
+ log.Printf("[routerConfig init] %#v", err)
+ }
+ }
+}
+
+// Load Dubbo Init
+func Load() {
+
+ // init router
+ initRouter()
+
+ // reference config
+ loadConsumerConfig()
+
+ // service config
+ loadProviderConfig()
+
// init the shutdown callback
GracefulShutdownInit()
}
diff --git a/config/consumer_config.go b/config/consumer_config.go
index 1fa68415bfc3c7e622c0b455e9945c926fed4df2..debcd79fa281c40e5526f60f5c5cdb66688688f4 100644
--- a/config/consumer_config.go
+++ b/config/consumer_config.go
@@ -44,6 +44,7 @@ type ConsumerConfig struct {
Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"`
// application
ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"`
+
// client
Connect_Timeout string `default:"100ms" yaml:"connect_timeout" json:"connect_timeout,omitempty" property:"connect_timeout"`
ConnectTimeout time.Duration
@@ -117,6 +118,7 @@ func ConsumerInit(confConFile string) error {
return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout)
}
}
+
logger.Debugf("consumer config{%#v}\n", consumerConfig)
return nil
diff --git a/config/instance/metedata_report.go b/config/instance/metedata_report.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd54b0a7940df166c88f02234ab1a4e3bf384163
--- /dev/null
+++ b/config/instance/metedata_report.go
@@ -0,0 +1,41 @@
+/*
+ * 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 instance
+
+import (
+ "sync"
+)
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/common/extension"
+ "github.com/apache/dubbo-go/metadata"
+)
+
+var (
+ instance metadata.MetadataReport
+ once sync.Once
+)
+
+// GetMetadataReportInstance ...
+func GetMetadataReportInstance(url *common.URL) metadata.MetadataReport {
+ once.Do(func() {
+ instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url)
+ })
+ return instance
+}
diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..41fb6b4769e59784d8d18c3f82b956fd029d4ff7
--- /dev/null
+++ b/config/metadata_report_config.go
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+import (
+ "net/url"
+)
+
+import (
+ "github.com/creasty/defaults"
+ perrors "github.com/pkg/errors"
+)
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/config/instance"
+)
+
+// MethodConfig ...
+type MetadataReportConfig struct {
+ Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"`
+ Address string `yaml:"address" json:"address,omitempty" property:"address"`
+ Username string `yaml:"username" json:"username,omitempty" property:"username"`
+ Password string `yaml:"password" json:"password,omitempty" property:"password"`
+ Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
+ TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second
+ Group string `yaml:"group" json:"group,omitempty" property:"group"`
+}
+
+// Prefix ...
+func (c *MetadataReportConfig) Prefix() string {
+ return constant.MetadataReportPrefix
+}
+
+// UnmarshalYAML ...
+func (c *MetadataReportConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ if err := defaults.Set(c); err != nil {
+ return perrors.WithStack(err)
+ }
+ type plain MetadataReportConfig
+ if err := unmarshal((*plain)(c)); err != nil {
+ return perrors.WithStack(err)
+ }
+ return nil
+}
+
+// ToUrl ...
+func (c *MetadataReportConfig) ToUrl() (*common.URL, error) {
+ urlMap := make(url.Values)
+
+ if c.Params != nil {
+ for k, v := range c.Params {
+ urlMap.Set(k, v)
+ }
+ }
+
+ url, err := common.NewURL(c.Address,
+ common.WithParams(urlMap),
+ common.WithUsername(c.Username),
+ common.WithPassword(c.Password),
+ common.WithLocation(c.Address),
+ common.WithProtocol(c.Protocol),
+ )
+ if err != nil || len(url.Protocol) == 0 {
+ return nil, perrors.New("Invalid MetadataReportConfig.")
+ }
+ url.SetParam("metadata", url.Protocol)
+ return &url, nil
+}
+
+func (c *MetadataReportConfig) IsValid() bool {
+ return len(c.Protocol) != 0
+}
+
+// StartMetadataReport: The entry of metadata report start
+func startMetadataReport(metadataType string, metadataReportConfig *MetadataReportConfig) error {
+ if metadataReportConfig == nil || metadataReportConfig.IsValid() {
+ return nil
+ }
+
+ if metadataType == constant.METACONFIG_REMOTE {
+ return perrors.New("No MetadataConfig found, you must specify the remote Metadata Center address when 'metadata=remote' is enabled.")
+ } else if metadataType == constant.METACONFIG_REMOTE && len(metadataReportConfig.Address) == 0 {
+ return perrors.New("MetadataConfig address can not be empty.")
+ }
+
+ if url, err := metadataReportConfig.ToUrl(); err == nil {
+ instance.GetMetadataReportInstance(url)
+ } else {
+ return perrors.New("MetadataConfig is invalid!")
+ }
+
+ return nil
+}
diff --git a/config/metadata_report_config_test.go b/config/metadata_report_config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..635feecc2d433366534566d184e058eb54a881ed
--- /dev/null
+++ b/config/metadata_report_config_test.go
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import "testing"
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMetadataReportConfig_ToUrl(t *testing.T) {
+ metadataReportConfig := MetadataReportConfig{
+ Protocol: "mock",
+ Address: "127.0.0.1:2181",
+ Username: "test",
+ Password: "test",
+ TimeoutStr: "3s",
+ Params: map[string]string{
+ "k": "v",
+ },
+ }
+ url, error := metadataReportConfig.ToUrl()
+ assert.NoError(t, error)
+ assert.Equal(t, "mock", url.Protocol)
+ assert.Equal(t, "127.0.0.1:2181", url.Location)
+ assert.Equal(t, "127.0.0.1", url.Ip)
+ assert.Equal(t, "2181", url.Port)
+ assert.Equal(t, "test", url.Username)
+ assert.Equal(t, "test", url.Password)
+ assert.Equal(t, "v", url.GetParam("k", ""))
+ assert.Equal(t, "mock", url.GetParam("metadata", ""))
+}
diff --git a/config/provider_config.go b/config/provider_config.go
index 14b77cafb3487754b9583d3b4e64ff605394b7db..79569917455773653750d1d5921a722daf079b0a 100644
--- a/config/provider_config.go
+++ b/config/provider_config.go
@@ -41,6 +41,8 @@ type ProviderConfig struct {
BaseConfig `yaml:",inline"`
Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"`
ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
+ // metadata-report
+ MetadataReportConfig *MetadataReportConfig `yaml:"metadata_report" json:"metadata_report,omitempty" property:"metadata_report"`
ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"`
Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"`
@@ -95,7 +97,10 @@ func ProviderInit(confProFile string) error {
n.InterfaceId = k
}
}
-
+ //start the metadata report if config set
+ if err := startMetadataReport(providerConfig.ApplicationConfig.MetadataType, providerConfig.MetadataReportConfig); err != nil {
+ return perrors.WithMessagef(err, "Provider starts metadata report error, and the error is {%#v}", err)
+ }
logger.Debugf("provider config{%#v}\n", providerConfig)
return nil
diff --git a/config/reference_config.go b/config/reference_config.go
index dcdba958058edb7c1f59f49f20a4cb738ea102f8..3710cbc4bc62a01a014e91bcb978742c4a93c5cb 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -188,6 +188,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values {
urlMap.Set(constant.VERSION_KEY, c.Version)
urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic))
urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
+
urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version)
urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.CONSUMER)).Role())
diff --git a/config/registry_config.go b/config/registry_config.go
index 4e4b6e97d79a9402616b6cac954f7a09b2973dcc..e877a2c19dd0c4dabdce9f7ee65c2404b82d615e 100644
--- a/config/registry_config.go
+++ b/config/registry_config.go
@@ -36,14 +36,15 @@ import (
// RegistryConfig ...
type RegistryConfig struct {
Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty" property:"protocol"`
- //I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig
+ // I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig
TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second
Group string `yaml:"group" json:"group,omitempty" property:"group"`
- //for registry
- Address string `yaml:"address" json:"address,omitempty" property:"address"`
- Username string `yaml:"username" json:"username,omitempty" property:"username"`
- Password string `yaml:"password" json:"password,omitempty" property:"password"`
- Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
+ // for registry
+ Address string `yaml:"address" json:"address,omitempty" property:"address"`
+ Username string `yaml:"username" json:"username,omitempty" property:"username"`
+ Password string `yaml:"password" json:"password,omitempty" property:"password"`
+ Simplified bool `yaml:"simplified" json:"simplified,omitempty" property:"simplified"`
+ Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
}
// UnmarshalYAML ...
@@ -70,9 +71,11 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf
for k, registryConf := range registries {
target := false
- // if user not config targetRegistries,default load all
- // Notice:in func "func Split(s, sep string) []string" comment : if s does not contain sep and sep is not empty, SplitAfter returns a slice of length 1 whose only element is s.
- // So we have to add the condition when targetRegistries string is not set (it will be "" when not set)
+ // if user not config targetRegistries, default load all
+ // Notice: in func "func Split(s, sep string) []string" comment:
+ // if s does not contain sep and sep is not empty, SplitAfter returns
+ // a slice of length 1 whose only element is s. So we have to add the
+ // condition when targetRegistries string is not set (it will be "" when not set)
if len(trSlice) == 0 || (len(trSlice) == 1 && trSlice[0] == "") {
target = true
} else {
@@ -86,29 +89,24 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf
}
if target {
- var (
- url common.URL
- err error
- )
-
addresses := strings.Split(registryConf.Address, ",")
address := addresses[0]
- address = traslateRegistryConf(address, registryConf)
- url, err = common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address,
+ address = translateRegistryConf(address, registryConf)
+ url, err := common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address,
common.WithParams(registryConf.getUrlMap(roleType)),
+ common.WithParamsValue("simplified", strconv.FormatBool(registryConf.Simplified)),
common.WithUsername(registryConf.Username),
common.WithPassword(registryConf.Password),
common.WithLocation(registryConf.Address),
)
if err != nil {
- logger.Errorf("The registry id:%s url is invalid , error: %#v", k, err)
+ logger.Errorf("The registry id: %s url is invalid, error: %#v", k, err)
panic(err)
} else {
urls = append(urls, &url)
}
}
-
}
return urls
@@ -123,15 +121,14 @@ func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values {
for k, v := range c.Params {
urlMap.Set(k, v)
}
-
return urlMap
}
-func traslateRegistryConf(address string, registryConf *RegistryConfig) string {
+func translateRegistryConf(address string, registryConf *RegistryConfig) string {
if strings.Contains(address, "://") {
translatedUrl, err := url.Parse(address)
if err != nil {
- logger.Errorf("The registry url is invalid , error: %#v", err)
+ logger.Errorf("The registry url is invalid, error: %#v", err)
panic(err)
}
address = translatedUrl.Host
diff --git a/config/service_config.go b/config/service_config.go
index 50bf5e12c3247340f177a84c72446383ec5c3450..45e7df6306fc016f014497868eb45ec3be768a11 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -18,6 +18,7 @@
package config
import (
+ "container/list"
"context"
"fmt"
"net/url"
@@ -29,6 +30,7 @@ import (
import (
"github.com/creasty/defaults"
+ gxnet "github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors"
"go.uber.org/atomic"
)
@@ -105,6 +107,24 @@ func NewServiceConfig(id string, context context.Context) *ServiceConfig {
}
}
+// Get Random Port
+func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List {
+ ports := list.New()
+ for _, proto := range protocolConfigs {
+ if len(proto.Port) > 0 {
+ continue
+ }
+
+ tcp, err := gxnet.ListenOnTCPRandomPort(proto.Ip)
+ if err != nil {
+ panic(perrors.New(fmt.Sprintf("Get tcp port error,err is {%v}", err)))
+ }
+ defer tcp.Close()
+ ports.PushBack(strings.Split(tcp.Addr().String(), ":")[1])
+ }
+ return ports
+}
+
// Export ...
func (c *ServiceConfig) Export() error {
// TODO: config center start here
@@ -127,6 +147,9 @@ func (c *ServiceConfig) Export() error {
logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs ", c.InterfaceName, c.Protocol)
return nil
}
+
+ ports := getRandomPort(protocolConfigs)
+ nextPort := ports.Front()
for _, proto := range protocolConfigs {
// registry the service reflect
methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService)
@@ -135,11 +158,18 @@ func (c *ServiceConfig) Export() error {
logger.Errorf(err.Error())
return err
}
+
+ port := proto.Port
+
+ if len(proto.Port) == 0 {
+ port = nextPort.Value.(string)
+ nextPort = nextPort.Next()
+ }
ivkURL := common.NewURLWithOptions(
common.WithPath(c.id),
common.WithProtocol(proto.Name),
common.WithIp(proto.Ip),
- common.WithPort(proto.Port),
+ common.WithPort(port),
common.WithParams(urlMap),
common.WithParamsValue(constant.BEAN_NAME_KEY, c.id),
common.WithMethods(strings.Split(methods, ",")),
diff --git a/config/service_config_test.go b/config/service_config_test.go
index 6f3230890348e77ea26c9c0eaf9165090c8cd09f..e39d32b7f7976cbdc668153bcd901e40dd635556 100644
--- a/config/service_config_test.go
+++ b/config/service_config_test.go
@@ -21,6 +21,11 @@ import (
"testing"
)
+import (
+ gxnet "github.com/dubbogo/gost/net"
+ "github.com/stretchr/testify/assert"
+)
+
import (
"github.com/apache/dubbo-go/common/extension"
)
@@ -189,3 +194,35 @@ func Test_Export(t *testing.T) {
}
providerConfig = nil
}
+
+func Test_getRandomPort(t *testing.T) {
+ protocolConfigs := make([]*ProtocolConfig, 0, 3)
+
+ ip, err := gxnet.GetLocalIP()
+ protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+ Ip: ip,
+ })
+ protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+ Ip: ip,
+ })
+ protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+ Ip: ip,
+ })
+ assert.NoError(t, err)
+ ports := getRandomPort(protocolConfigs)
+
+ assert.Equal(t, ports.Len(), len(protocolConfigs))
+
+ front := ports.Front()
+ for {
+ if front == nil {
+ break
+ }
+ t.Logf("port:%v", front.Value)
+ front = front.Next()
+ }
+
+ protocolConfigs = make([]*ProtocolConfig, 0, 3)
+ ports = getRandomPort(protocolConfigs)
+ assert.Equal(t, ports.Len(), len(protocolConfigs))
+}
diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go
index 3b5d1f4ebe8e728eb4d21e78ab675036c1ca9f63..b049d334bca7e5191caaf9674734e731bc709ba2 100644
--- a/config_center/apollo/impl.go
+++ b/config_center/apollo/impl.go
@@ -25,7 +25,8 @@ import (
)
import (
- "github.com/pkg/errors"
+ gxset "github.com/dubbogo/gost/container/set"
+ perrors "github.com/pkg/errors"
"github.com/zouyx/agollo"
)
@@ -119,7 +120,7 @@ func getNamespaceName(namespace string, configFileFormat agollo.ConfigFileFormat
func (c *apolloConfiguration) GetInternalProperty(key string, opts ...cc.Option) (string, error) {
config := agollo.GetConfig(c.appConf.NamespaceName)
if config == nil {
- return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key))
+ return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key))
}
return config.GetStringValue(key, ""), nil
}
@@ -128,6 +129,16 @@ func (c *apolloConfiguration) GetRule(key string, opts ...cc.Option) (string, er
return c.GetInternalProperty(key, opts...)
}
+// PublishConfig will publish the config with the (key, group, value) pair
+func (c *apolloConfiguration) PublishConfig(string, string, string) error {
+ return perrors.New("unsupport operation")
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *apolloConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+ return nil, perrors.New("unsupport operation")
+}
+
func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (string, error) {
/**
* when group is not null, we are getting startup configs(config file) from Config Center, for example:
@@ -135,7 +146,7 @@ func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (stri
*/
config := agollo.GetConfig(key)
if config == nil {
- return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key))
+ return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key))
}
return config.GetContent(agollo.Properties), nil
}
diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go
index d6c3b06b327f16c709b09121e589db6694d3663e..9013d7140e757520f2e8f048ce53a5ac2a13f982 100644
--- a/config_center/dynamic_configuration.go
+++ b/config_center/dynamic_configuration.go
@@ -21,14 +21,18 @@ import (
"time"
)
+import (
+ gxset "github.com/dubbogo/gost/container/set"
+)
+
import (
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/config_center/parser"
)
-//////////////////////////////////////////
+// ////////////////////////////////////////
// DynamicConfiguration
-//////////////////////////////////////////
+// ////////////////////////////////////////
const (
// DEFAULT_GROUP: default group
DEFAULT_GROUP = "dubbo"
@@ -42,14 +46,20 @@ type DynamicConfiguration interface {
SetParser(parser.ConfigurationParser)
AddListener(string, ConfigurationListener, ...Option)
RemoveListener(string, ConfigurationListener, ...Option)
- //GetProperties get properties file
+ // GetProperties get properties file
GetProperties(string, ...Option) (string, error)
- //GetRule get Router rule properties file
+ // GetRule get Router rule properties file
GetRule(string, ...Option) (string, error)
- //GetInternalProperty get value by key in Default properties file(dubbo.properties)
+ // GetInternalProperty get value by key in Default properties file(dubbo.properties)
GetInternalProperty(string, ...Option) (string, error)
+
+ // PublishConfig will publish the config with the (key, group, value) pair
+ PublishConfig(string, string, string) error
+
+ // GetConfigKeysByGroup will return all keys with the group
+ GetConfigKeysByGroup(group string) (*gxset.HashSet, error)
}
// Options ...
@@ -75,7 +85,7 @@ func WithTimeout(time time.Duration) Option {
}
}
-//GetRuleKey The format is '{interfaceName}:[version]:[group]'
+// GetRuleKey The format is '{interfaceName}:[version]:[group]'
func GetRuleKey(url common.URL) string {
return url.ColonSeparatedKey()
}
diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go
index 4d972b629abb7abd7cc0d0018026e4ccc04a1e4f..59c788b65bce2a4773975ea1a96a314649781832 100644
--- a/config_center/mock_dynamic_config.go
+++ b/config_center/mock_dynamic_config.go
@@ -22,6 +22,7 @@ import (
)
import (
+ gxset "github.com/dubbogo/gost/container/set"
"gopkg.in/yaml.v2"
)
@@ -81,6 +82,16 @@ func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(_ *common.URL)
}
+// PublishConfig will publish the config with the (key, group, value) pair
+func (c *MockDynamicConfiguration) PublishConfig(string, string, string) error {
+ return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+ return gxset.NewSet(c.content), nil
+}
+
// MockDynamicConfiguration ...
type MockDynamicConfiguration struct {
parser parser.ConfigurationParser
diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go
index 60ab89b003ff62016b9137223425c1051356975f..007b8be142274b63ceb56dd00399cdaf29c3746d 100644
--- a/config_center/nacos/impl.go
+++ b/config_center/nacos/impl.go
@@ -18,10 +18,12 @@
package nacos
import (
+ "strings"
"sync"
)
import (
+ gxset "github.com/dubbogo/gost/container/set"
"github.com/nacos-group/nacos-sdk-go/vo"
perrors "github.com/pkg/errors"
)
@@ -74,7 +76,7 @@ func (n *nacosDynamicConfiguration) RemoveListener(key string, listener config_c
n.removeListener(key, listener)
}
-//nacos distinguishes configuration files based on group and dataId. defalut group = "dubbo" and dataId = key
+// GetProperties nacos distinguishes configuration files based on group and dataId. defalut group = "dubbo" and dataId = key
func (n *nacosDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) {
return n.GetRule(key, opts...)
}
@@ -84,6 +86,33 @@ func (n *nacosDynamicConfiguration) GetInternalProperty(key string, opts ...conf
return n.GetProperties(key, opts...)
}
+// PublishConfig will publish the config with the (key, group, value) pair
+func (n *nacosDynamicConfiguration) PublishConfig(key string, group string, value string) error {
+
+ group = n.resolvedGroup(group)
+
+ ok, err := (*n.client.Client()).PublishConfig(vo.ConfigParam{
+ DataId: key,
+ Group: group,
+ Content: value,
+ })
+
+ if err != nil {
+ return perrors.WithStack(err)
+ }
+ if !ok {
+ return perrors.New("publish config to Nocos failed")
+ }
+ return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (n *nacosDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+ // TODO (the golang client of nacos does not support batch API)
+ // we should build a issue and then think about how to resolve this problem
+ return nil, perrors.New("unsupport operation, wait for implement")
+}
+
// GetRule Get router rule
func (n *nacosDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
tmpOpts := &config_center.Options{}
@@ -92,12 +121,12 @@ func (n *nacosDynamicConfiguration) GetRule(key string, opts ...config_center.Op
}
content, err := (*n.client.Client()).GetConfig(vo.ConfigParam{
DataId: key,
- Group: tmpOpts.Group,
+ Group: n.resolvedGroup(tmpOpts.Group),
})
if err != nil {
return "", perrors.WithStack(err)
} else {
- return string(content), nil
+ return content, nil
}
}
@@ -145,6 +174,15 @@ func (n *nacosDynamicConfiguration) Destroy() {
n.closeConfigs()
}
+// resolvedGroup will regular the group. Now, it will replace the '/' with '-'.
+// '/' is a special character for nacos
+func (n *nacosDynamicConfiguration) resolvedGroup(group string) string {
+ if len(group) <= 0 {
+ return group
+ }
+ return strings.ReplaceAll(group, "/", "-")
+}
+
// IsAvailable Get available status
func (n *nacosDynamicConfiguration) IsAvailable() bool {
select {
@@ -155,12 +193,12 @@ func (n *nacosDynamicConfiguration) IsAvailable() bool {
}
}
-func (r *nacosDynamicConfiguration) closeConfigs() {
- r.cltLock.Lock()
- client := r.client
- r.client = nil
- r.cltLock.Unlock()
+func (n *nacosDynamicConfiguration) closeConfigs() {
+ n.cltLock.Lock()
+ client := n.client
+ n.client = nil
+ n.cltLock.Unlock()
// Close the old client first to close the tmp node
client.Close()
- logger.Infof("begin to close provider nacos client")
+ logger.Infof("begin to close provider n client")
}
diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go
index b4e6f1d0259979eba28dd81e8f480ab4ae03a39f..4032c91cda512b649140db6eea3dc11eeb482f27 100644
--- a/config_center/nacos/impl_test.go
+++ b/config_center/nacos/impl_test.go
@@ -60,12 +60,7 @@ func runMockConfigServer(configHandler func(http.ResponseWriter, *http.Request),
func mockCommonNacosServer() *httptest.Server {
return runMockConfigServer(func(writer http.ResponseWriter, request *http.Request) {
- data := `
- dubbo.service.com.ikurento.user.UserProvider.cluster=failback
- dubbo.service.com.ikurento.user.UserProvider.protocol=myDubbo1
- dubbo.protocols.myDubbo.port=20000
- dubbo.protocols.myDubbo.name=dubbo
-`
+ data := "true"
fmt.Fprintf(writer, "%s", data)
}, func(writer http.ResponseWriter, request *http.Request) {
data := `dubbo.properties%02dubbo%02dubbo.service.com.ikurento.user.UserProvider.cluster=failback`
@@ -93,6 +88,16 @@ func Test_GetConfig(t *testing.T) {
assert.NoError(t, err)
}
+func TestNacosDynamicConfiguration_PublishConfig(t *testing.T) {
+ nacos, err := initNacosData(t)
+ assert.Nil(t, err)
+ key := "myKey"
+ group := "/custom/a/b"
+ value := "MyValue"
+ err = nacos.PublishConfig(key, group, value)
+ assert.Nil(t, err)
+}
+
func Test_AddListener(t *testing.T) {
nacos, err := initNacosData(t)
assert.NoError(t, err)
diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go
index 25c586586c7202e42ff44d6104e8132961add25a..de74cff8f64683a47278825b670352a04b69b791 100644
--- a/config_center/nacos/listener.go
+++ b/config_center/nacos/listener.go
@@ -35,11 +35,11 @@ func callback(listener config_center.ConfigurationListener, namespace, group, da
listener.Process(&config_center.ConfigChangeEvent{Key: dataId, Value: data, ConfigType: remoting.EventTypeUpdate})
}
-func (l *nacosDynamicConfiguration) addListener(key string, listener config_center.ConfigurationListener) {
- _, loaded := l.keyListeners.Load(key)
+func (n *nacosDynamicConfiguration) addListener(key string, listener config_center.ConfigurationListener) {
+ _, loaded := n.keyListeners.Load(key)
if !loaded {
_, cancel := context.WithCancel(context.Background())
- err := (*l.client.Client()).ListenConfig(vo.ConfigParam{
+ err := (*n.client.Client()).ListenConfig(vo.ConfigParam{
DataId: key,
Group: "dubbo",
OnChange: func(namespace, group, dataId, data string) {
@@ -49,14 +49,14 @@ func (l *nacosDynamicConfiguration) addListener(key string, listener config_cent
logger.Errorf("nacos : listen config fail, error:%v ", err)
newListener := make(map[config_center.ConfigurationListener]context.CancelFunc)
newListener[listener] = cancel
- l.keyListeners.Store(key, newListener)
+ n.keyListeners.Store(key, newListener)
} else {
// TODO check goroutine alive, but this version of go_nacos_sdk is not support.
logger.Infof("profile:%s. this profile is already listening", key)
}
}
-func (l *nacosDynamicConfiguration) removeListener(key string, listener config_center.ConfigurationListener) {
+func (n *nacosDynamicConfiguration) removeListener(key string, listener config_center.ConfigurationListener) {
// TODO: not supported in current go_nacos_sdk version
logger.Warn("not supported in current go_nacos_sdk version")
}
diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go
index 404243d4751146d1edc9a61d51cbb81d73c2ffb1..0a1ce35306dab98363ca475cd5d1b0648e924b90 100644
--- a/config_center/zookeeper/impl.go
+++ b/config_center/zookeeper/impl.go
@@ -25,6 +25,7 @@ import (
import (
"github.com/dubbogo/go-zookeeper/zk"
+ gxset "github.com/dubbogo/gost/container/set"
perrors "github.com/pkg/errors"
)
@@ -39,8 +40,9 @@ import (
const (
// ZkClient
- //zookeeper client name
- ZkClient = "zk config_center"
+ // zookeeper client name
+ ZkClient = "zk config_center"
+ pathSeparator = "/"
)
type zookeeperDynamicConfiguration struct {
@@ -74,7 +76,7 @@ func newZookeeperDynamicConfiguration(url *common.URL) (*zookeeperDynamicConfigu
c.cacheListener = NewCacheListener(c.rootPath)
err = c.client.Create(c.rootPath)
- c.listener.ListenServiceEvent(c.rootPath, c.cacheListener)
+ c.listener.ListenServiceEvent(url, c.rootPath, c.cacheListener)
return c, err
}
@@ -100,7 +102,7 @@ func newMockZookeeperDynamicConfiguration(url *common.URL, opts ...zookeeper.Opt
c.cacheListener = NewCacheListener(c.rootPath)
err = c.client.Create(c.rootPath)
- go c.listener.ListenServiceEvent(c.rootPath, c.cacheListener)
+ go c.listener.ListenServiceEvent(url, c.rootPath, c.cacheListener)
return tc, c, err
}
@@ -143,11 +145,39 @@ func (c *zookeeperDynamicConfiguration) GetProperties(key string, opts ...config
return string(content), nil
}
-//For zookeeper, getConfig and getConfigs have the same meaning.
+// GetInternalProperty For zookeeper, getConfig and getConfigs have the same meaning.
func (c *zookeeperDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) {
return c.GetProperties(key, opts...)
}
+// PublishConfig will put the value into Zk with specific path
+func (c *zookeeperDynamicConfiguration) PublishConfig(key string, group string, value string) error {
+ path := c.getPath(key, group)
+ err := c.client.CreateWithValue(path, []byte(value))
+ if err != nil {
+ return perrors.WithStack(err)
+ }
+ return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *zookeeperDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+ path := c.getPath("", group)
+ result, err := c.client.GetChildren(path)
+ if err != nil {
+ return nil, perrors.WithStack(err)
+ }
+
+ if len(result) == 0 {
+ return nil, perrors.New("could not find keys with group: " + group)
+ }
+ set := gxset.NewSet()
+ for _, e := range result {
+ set.Add(e)
+ }
+ return set, nil
+}
+
func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
return c.GetProperties(key, opts...)
}
@@ -214,3 +244,17 @@ func (c *zookeeperDynamicConfiguration) closeConfigs() {
func (c *zookeeperDynamicConfiguration) RestartCallBack() bool {
return true
}
+
+func (c *zookeeperDynamicConfiguration) getPath(key string, group string) string {
+ if len(key) == 0 {
+ return c.buildPath(group)
+ }
+ return c.buildPath(group) + pathSeparator + key
+}
+
+func (c *zookeeperDynamicConfiguration) buildPath(group string) string {
+ if len(group) == 0 {
+ group = config_center.DEFAULT_GROUP
+ }
+ return c.rootPath + pathSeparator + group
+}
diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go
index 22e15193cba1b533a2b1b965a44bf9665a6a4e5e..30389122a3a06ee260f2ed8b21057523137995d5 100644
--- a/config_center/zookeeper/impl_test.go
+++ b/config_center/zookeeper/impl_test.go
@@ -24,6 +24,7 @@ import (
import (
"github.com/dubbogo/go-zookeeper/zk"
+ gxset "github.com/dubbogo/gost/container/set"
"github.com/stretchr/testify/assert"
)
@@ -156,6 +157,26 @@ func Test_RemoveListener(t *testing.T) {
assert.Equal(t, "", listener.event)
}
+func TestZookeeperDynamicConfiguration_PublishConfig(t *testing.T) {
+ value := "Test Data"
+ customGroup := "Custom Group"
+ key := "myKey"
+ ts, zk := initZkData(config_center.DEFAULT_GROUP, t)
+ defer ts.Stop()
+ err := zk.PublishConfig(key, customGroup, value)
+ assert.Nil(t, err)
+ result, err := zk.GetInternalProperty("myKey", config_center.WithGroup(customGroup))
+ assert.Nil(t, err)
+ assert.Equal(t, value, result)
+
+ var keys *gxset.HashSet
+ keys, err = zk.GetConfigKeysByGroup(customGroup)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, keys.Size())
+ assert.True(t, keys.Contains(key))
+
+}
+
type mockDataListener struct {
wg sync.WaitGroup
event string
diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go
index fbfe7565170c7df468f755a4bd1aadde166a79c1..49cdc2287c28ae0cbbd0fcab3700536595bb0f5e 100644
--- a/filter/filter_impl/access_log_filter.go
+++ b/filter/filter_impl/access_log_filter.go
@@ -71,14 +71,18 @@ func init() {
*
* the value of "accesslog" can be "true" or "default" too.
* If the value is one of them, the access log will be record in log file which defined in log.yml
+ * AccessLogFilter is designed to be singleton
*/
type AccessLogFilter struct {
logChan chan AccessLogData
}
-// Invoke ...
+// Invoke will check whether user wants to use this filter.
+// If we find the value of key constant.ACCESS_LOG_KEY, we will log the invocation info
func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
accessLog := invoker.GetUrl().GetParam(constant.ACCESS_LOG_KEY, "")
+
+ // the user do not
if len(accessLog) > 0 {
accessLogData := AccessLogData{data: ef.buildAccessLogData(invoker, invocation), accessLog: accessLog}
ef.logIntoChannel(accessLogData)
@@ -86,7 +90,7 @@ func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker,
return invoker.Invoke(ctx, invocation)
}
-// it won't block the invocation
+// logIntoChannel won't block the invocation
func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) {
select {
case ef.logChan <- accessLogData:
@@ -97,6 +101,7 @@ func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) {
}
}
+// buildAccessLogData builds the access log data
func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string {
dataMap := make(map[string]string, 16)
attachments := invocation.Attachments()
@@ -130,11 +135,12 @@ func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation pro
return dataMap
}
-// OnResponse ...
+// OnResponse do nothing
func (ef *AccessLogFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result {
return result
}
+// writeLogToFile actually write the logs into file
func (ef *AccessLogFilter) writeLogToFile(data AccessLogData) {
accessLog := data.accessLog
if isDefault(accessLog) {
@@ -156,6 +162,12 @@ func (ef *AccessLogFilter) writeLogToFile(data AccessLogData) {
}
}
+// openLogFile will open the log file with append mode.
+// If the file is not found, it will create the file.
+// Actually, the accessLog is the filename
+// You may find out that, once we want to write access log into log file,
+// we open the file again and again.
+// It needs to be optimized.
func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) {
logFile, err := os.OpenFile(accessLog, os.O_CREATE|os.O_APPEND|os.O_RDWR, LogFileMode)
if err != nil {
@@ -169,6 +181,12 @@ func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) {
return nil, err
}
last := fileInfo.ModTime().Format(FileDateFormat)
+
+ // this is confused.
+ // for example, if the last = '2020-03-04'
+ // and today is '2020-03-05'
+ // we will create one new file to log access data
+ // By this way, we can split the access log based on days.
if now != last {
err = os.Rename(fileInfo.Name(), fileInfo.Name()+"."+now)
if err != nil {
@@ -180,11 +198,12 @@ func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) {
return logFile, err
}
+// isDefault check whether accessLog == true or accessLog == default
func isDefault(accessLog string) bool {
return strings.EqualFold("true", accessLog) || strings.EqualFold("default", accessLog)
}
-// GetAccessLogFilter ...
+// GetAccessLogFilter return the instance of AccessLogFilter
func GetAccessLogFilter() filter.Filter {
accessLogFilter := &AccessLogFilter{logChan: make(chan AccessLogData, LogMaxBuffer)}
go func() {
@@ -195,12 +214,13 @@ func GetAccessLogFilter() filter.Filter {
return accessLogFilter
}
-// AccessLogData ...
+// AccessLogData defines the data that will be log into file
type AccessLogData struct {
accessLog string
data map[string]string
}
+// toLogMessage convert the AccessLogData to String
func (ef *AccessLogData) toLogMessage() string {
builder := strings.Builder{}
builder.WriteString("[")
diff --git a/filter/filter_impl/tps/tps_limiter_method_service.go b/filter/filter_impl/tps/tps_limiter_method_service.go
index 7fe8de9237b82415a09083c2be59df5e232ecaf0..2d44c688ebd460f60a49da0f148fd7c6e2b79f50 100644
--- a/filter/filter_impl/tps/tps_limiter_method_service.go
+++ b/filter/filter_impl/tps/tps_limiter_method_service.go
@@ -115,7 +115,12 @@ type MethodServiceTpsLimiterImpl struct {
tpsState *concurrent.Map
}
-// IsAllowable ...
+// IsAllowable based on method-level and service-level.
+// The method-level has high priority which means that if there is any rate limit configuration for the method,
+// the service-level rate limit strategy will be ignored.
+// The key point is how to keep thread-safe
+// This implementation use concurrent map + loadOrStore to make implementation thread-safe
+// You can image that even multiple threads create limiter, but only one could store the limiter into tpsState
func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocation protocol.Invocation) bool {
methodConfigPrefix := "methods." + invocation.MethodName() + "."
@@ -123,23 +128,30 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio
methodLimitRateConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_RATE_KEY, "")
methodIntervalConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_INTERVAL_KEY, "")
+ // service-level tps limit
limitTarget := url.ServiceKey()
// method-level tps limit
if len(methodIntervalConfig) > 0 || len(methodLimitRateConfig) > 0 {
+ // it means that if the method-level rate limit exist, we will use method-level rate limit strategy
limitTarget = limitTarget + "#" + invocation.MethodName()
}
+ // looking up the limiter from 'cache'
limitState, found := limiter.tpsState.Load(limitTarget)
if found {
+ // the limiter has been cached, we return its result
return limitState.(filter.TpsLimitStrategy).IsAllowable()
}
+ // we could not find the limiter, and try to create one.
+
limitRate := getLimitConfig(methodLimitRateConfig, url, invocation,
constant.TPS_LIMIT_RATE_KEY,
constant.DEFAULT_TPS_LIMIT_RATE)
if limitRate < 0 {
+ // the limitTarget is not necessary to be limited.
return true
}
@@ -150,13 +162,20 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio
panic(fmt.Sprintf("The interval must be positive, please check your configuration! url: %s", url.String()))
}
+ // find the strategy config and then create one
limitStrategyConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_STRATEGY_KEY,
url.GetParam(constant.TPS_LIMIT_STRATEGY_KEY, constant.DEFAULT_KEY))
limitStateCreator := extension.GetTpsLimitStrategyCreator(limitStrategyConfig)
+
+ // we using loadOrStore to ensure thread-safe
limitState, _ = limiter.tpsState.LoadOrStore(limitTarget, limitStateCreator.Create(int(limitRate), int(limitInterval)))
+
return limitState.(filter.TpsLimitStrategy).IsAllowable()
}
+// getLimitConfig will try to fetch the configuration from url.
+// If we can convert the methodLevelConfig to int64, return;
+// Or, we will try to look up server-level configuration and then convert it to int64
func getLimitConfig(methodLevelConfig string,
url common.URL,
invocation protocol.Invocation,
@@ -172,6 +191,8 @@ func getLimitConfig(methodLevelConfig string,
return result
}
+ // actually there is no method-level configuration, so we use the service-level configuration
+
result, err := strconv.ParseInt(url.GetParam(configKey, defaultVal), 0, 0)
if err != nil {
@@ -183,7 +204,7 @@ func getLimitConfig(methodLevelConfig string,
var methodServiceTpsLimiterInstance *MethodServiceTpsLimiterImpl
var methodServiceTpsLimiterOnce sync.Once
-// GetMethodServiceTpsLimiter ...
+// GetMethodServiceTpsLimiter will return an MethodServiceTpsLimiterImpl instance.
func GetMethodServiceTpsLimiter() filter.TpsLimiter {
methodServiceTpsLimiterOnce.Do(func() {
methodServiceTpsLimiterInstance = &MethodServiceTpsLimiterImpl{
diff --git a/filter/handler/rejected_execution_handler_only_log.go b/filter/handler/rejected_execution_handler_only_log.go
index 0f9003c7df2165a2f3a364a5afc47f578db1d243..fe9cf4869f16e1d7c136e7f48e4138d046fcb057 100644
--- a/filter/handler/rejected_execution_handler_only_log.go
+++ b/filter/handler/rejected_execution_handler_only_log.go
@@ -36,6 +36,7 @@ const (
)
func init() {
+ // this implementation is the the default implementation of RejectedExecutionHandler
extension.SetRejectedExecutionHandler(HandlerName, GetOnlyLogRejectedExecutionHandler)
extension.SetRejectedExecutionHandler(constant.DEFAULT_KEY, GetOnlyLogRejectedExecutionHandler)
}
@@ -56,11 +57,12 @@ var onlyLogHandlerOnce sync.Once
* tps.limit.rejected.handler: "default" or "log"
* methods:
* - name: "GetUser"
+ * OnlyLogRejectedExecutionHandler is designed to be singleton
*/
type OnlyLogRejectedExecutionHandler struct {
}
-// RejectedExecution ...
+// RejectedExecution will do nothing, it only log the invocation.
func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL,
_ protocol.Invocation) protocol.Result {
@@ -68,7 +70,7 @@ func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL
return &protocol.RPCResult{}
}
-// GetOnlyLogRejectedExecutionHandler ...
+// GetOnlyLogRejectedExecutionHandler will return the instance of OnlyLogRejectedExecutionHandler
func GetOnlyLogRejectedExecutionHandler() filter.RejectedExecutionHandler {
onlyLogHandlerOnce.Do(func() {
onlyLogHandlerInstance = &OnlyLogRejectedExecutionHandler{}
diff --git a/filter/rejected_execution_handler.go b/filter/rejected_execution_handler.go
index caeea1db6631d0968fd58f59f9577ee9272f3ca0..d02481b98d2937ac58d277becdb1240b8a4e9b0f 100644
--- a/filter/rejected_execution_handler.go
+++ b/filter/rejected_execution_handler.go
@@ -31,5 +31,7 @@ import (
* In such situation, implement this interface and register it by invoking extension.SetRejectedExecutionHandler.
*/
type RejectedExecutionHandler interface {
+
+ // RejectedExecution will be called if the invocation was rejected by some component.
RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result
}
diff --git a/filter/tps_limit_strategy.go b/filter/tps_limit_strategy.go
index 5edf32ce1912642c7ad0ea0b3f6144b45c267eb4..e194f1da06b0599464f0c66f6b7747fc78fbedce 100644
--- a/filter/tps_limit_strategy.go
+++ b/filter/tps_limit_strategy.go
@@ -33,10 +33,16 @@ package filter
* tps.limit.strategy: "name of implementation" # method-level
*/
type TpsLimitStrategy interface {
+ // IsAllowable will return true if this invocation is not over limitation
IsAllowable() bool
}
-// TpsLimitStrategyCreator ...
+// TpsLimitStrategyCreator, the creator abstraction for TpsLimitStrategy
type TpsLimitStrategyCreator interface {
- Create(rate int, interval int) TpsLimitStrategy
+ // Create will create an instance of TpsLimitStrategy
+ // It will be a little hard to understand this method.
+ // The unit of interval is ms
+ // for example, if the limit = 100, interval = 1000
+ // which means that the tps limitation is 100 times per 1000ms (100/1000ms)
+ Create(limit int, interval int) TpsLimitStrategy
}
diff --git a/filter/tps_limiter.go b/filter/tps_limiter.go
index dbc9f76838a4406b4788e7757453098613253d58..531eb098232cd34a467d71882b29858c07f88aef 100644
--- a/filter/tps_limiter.go
+++ b/filter/tps_limiter.go
@@ -34,5 +34,6 @@ import (
* tps.limiter: "the name of limiter",
*/
type TpsLimiter interface {
+ // IsAllowable will check whether this invocation should be enabled for further process
IsAllowable(common.URL, protocol.Invocation) bool
}
diff --git a/go.mod b/go.mod
index 67ed1eb402c34eecc5214411531b0a4ab68fda0c..b238173cb904ec394d9dabb21c41f81e8b745ade 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ require (
github.com/Workiva/go-datastructures v1.0.50
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect
- github.com/apache/dubbo-go-hessian2 v1.4.0
+ github.com/apache/dubbo-go-hessian2 v1.5.0
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
github.com/coreos/bbolt v1.3.3 // indirect
github.com/coreos/etcd v3.3.13+incompatible
@@ -12,9 +12,9 @@ require (
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/creasty/defaults v1.3.0
- github.com/dubbogo/getty v1.3.3
+ github.com/dubbogo/getty v1.3.5
github.com/dubbogo/go-zookeeper v1.0.0
- github.com/dubbogo/gost v1.8.0
+ github.com/dubbogo/gost v1.9.0
github.com/emicklei/go-restful/v3 v3.0.0
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/go-errors/errors v1.0.1 // indirect
@@ -32,13 +32,15 @@ require (
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
github.com/jonboulle/clockwork v0.1.0 // indirect
+ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
+ github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/magiconair/properties v1.8.1
github.com/mitchellh/mapstructure v1.1.2
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
- github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb
+ github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.1.0
diff --git a/go.sum b/go.sum
index bbbaf34640b63d6b7c423d8c7f9c4419025842cf..62e51170c79815d6ba8498a60ec1a5af797819e4 100644
--- a/go.sum
+++ b/go.sum
@@ -38,8 +38,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
-github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA=
-github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
+github.com/apache/dubbo-go-hessian2 v1.5.0 h1:fzulDG5G7nX0ccgKdiN9XipJ7tZ4WXKgmk4stdlDS6s=
+github.com/apache/dubbo-go-hessian2 v1.5.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -106,15 +106,13 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs=
-github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0=
+github.com/dubbogo/getty v1.3.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw=
+github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY=
github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM=
github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
-github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8=
-github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
-github.com/dubbogo/gost v1.8.0 h1:9ACbQe5OwMjqtinQcNJC5xp16kky27OsfSGw5L9A6vw=
-github.com/dubbogo/gost v1.8.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
+github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk=
+github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M=
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo=
github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y=
@@ -324,6 +322,12 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg=
+github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
+github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0=
+github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b h1:VE6r2OwP5gj+Z9aCkSKl3MlmnZbfMAjhvR5T7abKHEo=
github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
@@ -383,8 +387,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb h1:lbmvw8r9W55w+aQgWn35W1nuleRIECMoqUrmwAOAvoI=
-github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo=
+github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c h1:WoCa3AvgQMVKNs+RIFlWPRgY9QVJwUxJDrGxHs0fcRo=
+github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo=
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s=
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk=
github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0=
diff --git a/metadata/definition/definition.go b/metadata/definition/definition.go
new file mode 100644
index 0000000000000000000000000000000000000000..ead984345efde1ddd1d54b7599fd9d5584947ea2
--- /dev/null
+++ b/metadata/definition/definition.go
@@ -0,0 +1,41 @@
+/*
+ * 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 definition
+
+type ServiceDefinition struct {
+ CanonicalName string
+ CodeSource string
+ Methods []MethodDefinition
+ Types []TypeDefinition
+}
+
+type MethodDefinition struct {
+ Name string
+ ParameterTypes []string
+ ReturnType string
+ Parameters []TypeDefinition
+}
+
+type TypeDefinition struct {
+ Id string
+ Type string
+ Items []TypeDefinition
+ Enums []string
+ Properties map[string]TypeDefinition
+ TypeBuilderName string
+}
diff --git a/metadata/exporter.go b/metadata/exporter.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d47f8bd808ec802ba73c7db73d22c78c675d12a
--- /dev/null
+++ b/metadata/exporter.go
@@ -0,0 +1,29 @@
+/*
+ * 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 metadata
+
+import (
+ "github.com/apache/dubbo-go/common"
+)
+
+type MetadataExporter interface {
+ Export() MetadataExporter
+ Unexport() MetadataExporter
+ GetExportedURLs() []*common.URL
+ IsExported() bool
+}
diff --git a/metadata/identifier/base_metadata_identifier.go b/metadata/identifier/base_metadata_identifier.go
new file mode 100644
index 0000000000000000000000000000000000000000..a314671055be523844fd7d8f9589b8b6031632bc
--- /dev/null
+++ b/metadata/identifier/base_metadata_identifier.go
@@ -0,0 +1,92 @@
+/*
+ * 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 identifier
+
+import (
+ "encoding/base64"
+)
+
+import (
+ "github.com/apache/dubbo-go/common/constant"
+)
+
+type BaseMetadataIdentifier interface {
+ getFilePathKey(params ...string) string
+ getIdentifierKey(params ...string) string
+}
+
+type BaseServiceMetadataIdentifier struct {
+ serviceInterface string
+ version string
+ group string
+ side string
+}
+
+// joinParams...
+func joinParams(joinChar string, params []string) string {
+ var joinedStr string
+ for _, param := range params {
+ joinedStr += joinChar
+ joinedStr += param
+ }
+ return joinedStr
+}
+
+// getIdentifierKey...
+func (mdi *BaseServiceMetadataIdentifier) getIdentifierKey(params ...string) string {
+ return mdi.serviceInterface +
+ constant.KEY_SEPARATOR + mdi.version +
+ constant.KEY_SEPARATOR + mdi.group +
+ constant.KEY_SEPARATOR + mdi.side +
+ joinParams(constant.KEY_SEPARATOR, params)
+}
+
+// getFilePathKey...
+func (mdi *BaseServiceMetadataIdentifier) getFilePathKey(params ...string) string {
+ path := serviceToPath(mdi.serviceInterface)
+
+ return constant.DEFAULT_PATH_TAG +
+ withPathSeparator(path) +
+ withPathSeparator(mdi.version) +
+ withPathSeparator(mdi.group) +
+ withPathSeparator(mdi.side) +
+ joinParams(constant.PATH_SEPARATOR, params)
+
+}
+
+// serviceToPath...
+func serviceToPath(serviceInterface string) string {
+ if serviceInterface == constant.ANY_VALUE {
+ return ""
+ } else {
+ decoded, err := base64.URLEncoding.DecodeString(serviceInterface)
+ if err != nil {
+ return ""
+ }
+ return string(decoded)
+ }
+
+}
+
+//withPathSeparator...
+func withPathSeparator(path string) string {
+ if len(path) != 0 {
+ path = constant.PATH_SEPARATOR + path
+ }
+ return path
+}
diff --git a/metadata/identifier/metadata_identifier.go b/metadata/identifier/metadata_identifier.go
new file mode 100644
index 0000000000000000000000000000000000000000..f3df8f36546093a826279c4e9ec1546f78d444bd
--- /dev/null
+++ b/metadata/identifier/metadata_identifier.go
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+type MetadataIdentifier struct {
+ application string
+ BaseMetadataIdentifier
+}
+
+// getIdentifierKey...
+func (mdi *MetadataIdentifier) getIdentifierKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.application)
+}
+
+// getIdentifierKey...
+func (mdi *MetadataIdentifier) getFilePathKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.application)
+}
diff --git a/metadata/identifier/service_metadata_identifier.go b/metadata/identifier/service_metadata_identifier.go
new file mode 100644
index 0000000000000000000000000000000000000000..373df0130dd1f87e3175918bde50060c4be89616
--- /dev/null
+++ b/metadata/identifier/service_metadata_identifier.go
@@ -0,0 +1,38 @@
+/*
+ * 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 identifier
+
+import (
+ "github.com/apache/dubbo-go/common/constant"
+)
+
+type ServiceMetadataIdentifier struct {
+ revision string
+ protocol string
+ BaseMetadataIdentifier
+}
+
+// getIdentifierKey...
+func (mdi *ServiceMetadataIdentifier) getIdentifierKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision)
+}
+
+// getIdentifierKey...
+func (mdi *ServiceMetadataIdentifier) getFilePathKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision)
+}
diff --git a/metadata/identifier/subscribe_metadata_identifier.go b/metadata/identifier/subscribe_metadata_identifier.go
new file mode 100644
index 0000000000000000000000000000000000000000..321a216a3e3ad3f2390ab832782924a81e226160
--- /dev/null
+++ b/metadata/identifier/subscribe_metadata_identifier.go
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+type SubscriberMetadataIdentifier struct {
+ revision string
+ BaseMetadataIdentifier
+}
+
+// getIdentifierKey...
+func (mdi *SubscriberMetadataIdentifier) getIdentifierKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.revision)
+}
+
+// getIdentifierKey...
+func (mdi *SubscriberMetadataIdentifier) getFilePathKey(params ...string) string {
+ return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.revision)
+}
diff --git a/metadata/namemapping/dynamic/service_name_mapping.go b/metadata/namemapping/dynamic/service_name_mapping.go
new file mode 100644
index 0000000000000000000000000000000000000000..e93c256fe093b4a3e3c431e1d012038b2bb7976b
--- /dev/null
+++ b/metadata/namemapping/dynamic/service_name_mapping.go
@@ -0,0 +1,82 @@
+/*
+ * 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 dynamic
+
+import (
+ "strconv"
+ "time"
+)
+
+import (
+ "github.com/dubbogo/gost/container/set"
+ perrors "github.com/pkg/errors"
+)
+
+import (
+ "github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/config"
+ "github.com/apache/dubbo-go/config_center"
+ "github.com/apache/dubbo-go/metadata"
+)
+
+const (
+ defaultGroup = config_center.DEFAULT_GROUP
+ slash = "/"
+)
+
+// DynamicConfigurationServiceNameMapping is the implementation based on config center
+type DynamicConfigurationServiceNameMapping struct {
+ dc config_center.DynamicConfiguration
+}
+
+// Map will map the service to this application-level service
+func (d *DynamicConfigurationServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
+ // metadata service is admin service, should not be mapped
+ if constant.METADATA_SERVICE_NAME == serviceInterface {
+ return perrors.New("try to map the metadata service, will be ignored")
+ }
+
+ appName := config.GetApplicationConfig().Name
+ value := time.Now().UnixNano()
+
+ err := d.dc.PublishConfig(appName,
+ d.buildGroup(serviceInterface),
+ strconv.FormatInt(value, 10))
+ if err != nil {
+ return perrors.WithStack(err)
+ }
+ return nil
+}
+
+// Get will return the application-level services. If not found, the empty set will be returned.
+// if the dynamic configuration got error, the error will return
+func (d *DynamicConfigurationServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+ return d.dc.GetConfigKeysByGroup(d.buildGroup(serviceInterface))
+}
+
+// buildGroup will return group, now it looks like defaultGroup/serviceInterface
+func (d *DynamicConfigurationServiceNameMapping) buildGroup(serviceInterface string) string {
+ // the issue : https://github.com/apache/dubbo/issues/4671
+ // so other params are ignored and remove, including group string, version string, protocol string
+ return defaultGroup + slash + serviceInterface
+}
+
+// NewServiceNameMapping will create an instance of DynamicConfigurationServiceNameMapping
+func NewServiceNameMapping(dc config_center.DynamicConfiguration) metadata.ServiceNameMapping {
+ return &DynamicConfigurationServiceNameMapping{dc: dc}
+}
diff --git a/metadata/namemapping/dynamic/service_name_mapping_test.go b/metadata/namemapping/dynamic/service_name_mapping_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e3d620cd738421c256d8fd232b1afcfd425ca989
--- /dev/null
+++ b/metadata/namemapping/dynamic/service_name_mapping_test.go
@@ -0,0 +1,61 @@
+/*
+ * 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 dynamic
+
+import (
+ "testing"
+)
+
+import (
+ gxset "github.com/dubbogo/gost/container/set"
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/config"
+ "github.com/apache/dubbo-go/config_center"
+)
+
+func TestDynamicConfigurationServiceNameMapping(t *testing.T) {
+
+ // mock data
+ appName := "myApp"
+ dc, err := (&config_center.MockDynamicConfigurationFactory{
+ Content: appName,
+ }).GetDynamicConfiguration(nil)
+ config.GetApplicationConfig().Name = appName
+
+ mapping := NewServiceNameMapping(dc)
+ intf := constant.METADATA_SERVICE_NAME
+ group := "myGroup"
+ version := "myVersion"
+ protocol := "myProtocol"
+
+ err = mapping.Map(intf, group, version, protocol)
+ assert.NotNil(t, err)
+ intf = "MyService"
+ err = mapping.Map(intf, group, version, protocol)
+ assert.Nil(t, err)
+
+ var result *gxset.HashSet
+ result, err = mapping.Get(intf, group, version, protocol)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, result.Size())
+ assert.True(t, result.Contains(appName))
+}
diff --git a/metadata/namemapping/memory/service_name_mapping.go b/metadata/namemapping/memory/service_name_mapping.go
new file mode 100644
index 0000000000000000000000000000000000000000..8a891491bdb97808b77422092a1043c1c0ffafbf
--- /dev/null
+++ b/metadata/namemapping/memory/service_name_mapping.go
@@ -0,0 +1,36 @@
+/*
+ * 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 memory
+
+import (
+ gxset "github.com/dubbogo/gost/container/set"
+)
+
+import (
+ "github.com/apache/dubbo-go/config"
+)
+
+type InMemoryServiceNameMapping struct{}
+
+func (i InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
+ return nil
+}
+
+func (i InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+ return gxset.NewSet(config.GetApplicationConfig().Name), nil
+}
diff --git a/metadata/report.go b/metadata/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..3fcc71241411d4a8f9577bb5fb3233e67942cd52
--- /dev/null
+++ b/metadata/report.go
@@ -0,0 +1,35 @@
+/*
+ * 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 metadata
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/metadata/definition"
+ "github.com/apache/dubbo-go/metadata/identifier"
+)
+
+type MetadataReport interface {
+ StoreProviderMetadata(*identifier.MetadataIdentifier, *definition.ServiceDefinition)
+ StoreConsumeretadata(*identifier.MetadataIdentifier, map[string]string)
+ SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL)
+ RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier)
+ GetExportedURLs(*identifier.ServiceMetadataIdentifier) []string
+ SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []*common.URL)
+ GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string
+ GetServiceDefinition(*identifier.MetadataIdentifier)
+}
diff --git a/metadata/report_factory.go b/metadata/report_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..19b1004eee57073acec13c7f114179c47c73f145
--- /dev/null
+++ b/metadata/report_factory.go
@@ -0,0 +1,30 @@
+/*
+ * 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 metadata
+
+import (
+ "github.com/apache/dubbo-go/common"
+)
+
+var (
+ MetadataReportInstance MetadataReport
+)
+
+type MetadataReportFactory interface {
+ CreateMetadataReport(*common.URL) MetadataReport
+}
diff --git a/metadata/service.go b/metadata/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d85703c95a57183d5c0a5b2445839e946dc6a59b
--- /dev/null
+++ b/metadata/service.go
@@ -0,0 +1,37 @@
+/*
+ * 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 metadata
+
+import (
+ "github.com/apache/dubbo-go/common"
+ gxset "github.com/dubbogo/gost/container/set"
+)
+
+type MetadataService interface {
+ ServiceName() string
+ ExportURL(url *common.URL) bool
+ UnexportURL(url *common.URL) bool
+ RefreshMetadata(exportedRevision string, subscribedRevision string) bool
+ SubscribeURL(url *common.URL) bool
+ UnsubscribeURL(url *common.URL) bool
+ PublishServiceDefinition(url *common.URL)
+
+ GetExportedURLs(serviceInterface string, group string, version string, protocol string) gxset.HashSet
+ GetServiceDefinition(interfaceName string, version string, group string) string
+ GetServiceDefinitionByServiceKey(serviceKey string) string
+}
diff --git a/metadata/service_name_mapping.go b/metadata/service_name_mapping.go
new file mode 100644
index 0000000000000000000000000000000000000000..c14e8ce2e7c40d1573897dfd6ba64c16e18acac7
--- /dev/null
+++ b/metadata/service_name_mapping.go
@@ -0,0 +1,32 @@
+/*
+ * 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 metadata
+
+import (
+ gxset "github.com/dubbogo/gost/container/set"
+)
+
+// ServiceNameMapping try to build the mapping between application-level service and interface-level service.
+type ServiceNameMapping interface {
+
+ // Map will map the service to this application-level service
+ Map(serviceInterface string, group string, version string, protocol string) error
+
+ // Get will return the application-level services
+ Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error)
+}
diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go
index 5ec7db51af0ddfa6e49d3c65910355f0bf2de414..e6ffa64d80a327517bcd5b6fb9ff2efdc9aed337 100644
--- a/protocol/dubbo/client.go
+++ b/protocol/dubbo/client.go
@@ -88,7 +88,7 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
-// SetClientConf ...
+// SetClientConf set dubbo client config.
func SetClientConf(c ClientConfig) {
clientConf = &c
err := clientConf.CheckValidity()
@@ -99,7 +99,7 @@ func SetClientConf(c ClientConfig) {
setClientGrpool()
}
-// GetClientConf ...
+// GetClientConf get dubbo client config.
func GetClientConf() ClientConfig {
return *clientConf
}
@@ -129,7 +129,7 @@ type AsyncCallbackResponse struct {
Reply interface{}
}
-// Client ...
+// Client is dubbo protocol client.
type Client struct {
opts Options
conf ClientConfig
@@ -139,7 +139,7 @@ type Client struct {
pendingResponses *sync.Map
}
-// NewClient ...
+// NewClient create a new Client.
func NewClient(opt Options) *Client {
switch {
@@ -167,7 +167,7 @@ func NewClient(opt Options) *Client {
return c
}
-// Request ...
+// Request is dubbo protocol request.
type Request struct {
addr string
svcUrl common.URL
@@ -176,7 +176,7 @@ type Request struct {
atta map[string]string
}
-// NewRequest ...
+// NewRequest create a new Request.
func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request {
return &Request{
addr: addr,
@@ -187,13 +187,13 @@ func NewRequest(addr string, svcUrl common.URL, method string, args interface{},
}
}
-// Response ...
+// Response is dubbo protocol response.
type Response struct {
reply interface{}
atta map[string]string
}
-// NewResponse ...
+// NewResponse create a new Response.
func NewResponse(reply interface{}, atta map[string]string) *Response {
return &Response{
reply: reply,
@@ -201,15 +201,14 @@ func NewResponse(reply interface{}, atta map[string]string) *Response {
}
}
-// CallOneway call one way
+// CallOneway call by one way
func (c *Client) CallOneway(request *Request) error {
return perrors.WithStack(c.call(CT_OneWay, request, NewResponse(nil, nil), nil))
}
-// Call if @response is nil, the transport layer will get the response without notify the invoker.
+// Call call remoting by two way or one way, if @response.reply is nil, the way of call is one way.
func (c *Client) Call(request *Request, response *Response) error {
-
ct := CT_TwoWay
if response.reply == nil {
ct = CT_OneWay
@@ -218,14 +217,12 @@ func (c *Client) Call(request *Request, response *Response) error {
return perrors.WithStack(c.call(ct, request, response, nil))
}
-// AsyncCall ...
+// AsyncCall call remoting by async with callback.
func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error {
-
return perrors.WithStack(c.call(CT_TwoWay, request, response, callback))
}
func (c *Client) call(ct CallType, request *Request, response *Response, callback common.AsyncCallback) error {
-
p := &DubboPackage{}
p.Service.Path = strings.TrimPrefix(request.svcUrl.Path, "/")
p.Service.Interface = request.svcUrl.GetParam(constant.INTERFACE_KEY, "")
@@ -293,7 +290,7 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac
return perrors.WithStack(err)
}
-// Close ...
+// Close close the client pool.
func (c *Client) Close() {
if c.pool != nil {
c.pool.close()
diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go
index 76416b2baf1e1db516c00d92ecb8ad618bf186bd..620a57d4a7f09da33f752b4890692d6102eb95df 100644
--- a/protocol/dubbo/codec.go
+++ b/protocol/dubbo/codec.go
@@ -69,7 +69,7 @@ func (p DubboPackage) String() string {
return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body)
}
-// Marshal ...
+// Marshal encode hessian package.
func (p *DubboPackage) Marshal() (*bytes.Buffer, error) {
codec := hessian.NewHessianCodec(nil)
@@ -81,7 +81,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) {
return bytes.NewBuffer(pkg), nil
}
-// Unmarshal ...
+// Unmarshal dncode hessian package.
func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error {
// fix issue https://github.com/apache/dubbo-go/issues/380
bufLen := buf.Len()
@@ -125,7 +125,7 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error {
// PendingResponse
////////////////////////////////////////////
-// PendingResponse ...
+// PendingResponse is a pending response.
type PendingResponse struct {
seq uint64
err error
@@ -136,7 +136,7 @@ type PendingResponse struct {
done chan struct{}
}
-// NewPendingResponse ...
+// NewPendingResponse create a PendingResponses.
func NewPendingResponse() *PendingResponse {
return &PendingResponse{
start: time.Now(),
@@ -145,7 +145,7 @@ func NewPendingResponse() *PendingResponse {
}
}
-// GetCallResponse ...
+// GetCallResponse get AsyncCallbackResponse.
func (r PendingResponse) GetCallResponse() common.CallbackResponse {
return AsyncCallbackResponse{
Cause: r.err,
diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go
index dbc6989c54780afacef717f1d110833d92967f9f..6a1daf857a7c54ae2c37a1c85ab17481f6fe6068 100644
--- a/protocol/dubbo/config.go
+++ b/protocol/dubbo/config.go
@@ -147,7 +147,7 @@ func GetDefaultServerConfig() ServerConfig {
}
}
-// CheckValidity ...
+// CheckValidity confirm getty sessian params.
func (c *GettySessionParam) CheckValidity() error {
var err error
@@ -170,7 +170,7 @@ func (c *GettySessionParam) CheckValidity() error {
return nil
}
-// CheckValidity ...
+// CheckValidity confirm client params.
func (c *ClientConfig) CheckValidity() error {
var err error
@@ -192,7 +192,7 @@ func (c *ClientConfig) CheckValidity() error {
return perrors.WithStack(c.GettySessionParam.CheckValidity())
}
-// CheckValidity ...
+// CheckValidity confirm server params.
func (c *ServerConfig) CheckValidity() error {
var err error
diff --git a/protocol/dubbo/dubbo_exporter.go b/protocol/dubbo/dubbo_exporter.go
index 1c45c40056f7690ba64838f641fe0b13a1554727..dd80937c5bdf6718c2047b102115d8c08afcd899 100644
--- a/protocol/dubbo/dubbo_exporter.go
+++ b/protocol/dubbo/dubbo_exporter.go
@@ -28,19 +28,19 @@ import (
"github.com/apache/dubbo-go/protocol"
)
-// DubboExporter ...
+// DubboExporter is dubbo service exporter.
type DubboExporter struct {
protocol.BaseExporter
}
-// NewDubboExporter ...
+// NewDubboExporter get a DubboExporter.
func NewDubboExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *DubboExporter {
return &DubboExporter{
BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap),
}
}
-// Unexport ...
+// Unexport unexport dubbo service exporter.
func (de *DubboExporter) Unexport() {
serviceId := de.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
interfaceName := de.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go
index 09c3725710d2a0b821d8e641b0cb7b367189c244..59202d5f49f30acb9e75c4e2f135601285f08e13 100644
--- a/protocol/dubbo/dubbo_invoker.go
+++ b/protocol/dubbo/dubbo_invoker.go
@@ -39,8 +39,9 @@ import (
)
var (
- // ErrNoReply ...
- ErrNoReply = perrors.New("request need @response")
+ // ErrNoReply
+ ErrNoReply = perrors.New("request need @response")
+ // ErrDestroyedInvoker
ErrDestroyedInvoker = perrors.New("request Destroyed invoker")
)
@@ -48,7 +49,7 @@ var (
attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY}
)
-// DubboInvoker ...
+// DubboInvoker is dubbo client invoker.
type DubboInvoker struct {
protocol.BaseInvoker
client *Client
@@ -57,7 +58,7 @@ type DubboInvoker struct {
reqNum int64
}
-// NewDubboInvoker ...
+// NewDubboInvoker create dubbo client invoker.
func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker {
return &DubboInvoker{
BaseInvoker: *protocol.NewBaseInvoker(url),
@@ -66,7 +67,7 @@ func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker {
}
}
-// Invoke ...
+// Invoke call remoting.
func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
var (
err error
@@ -122,7 +123,7 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati
return &result
}
-// Destroy ...
+// Destroy destroy dubbo client invoker.
func (di *DubboInvoker) Destroy() {
di.quitOnce.Do(func() {
for {
diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go
index 355dbc802488338ef4dbdd7290166038b312f183..b7d0a8a26818bd318a2724d3310d7da23b046fae 100644
--- a/protocol/dubbo/dubbo_protocol.go
+++ b/protocol/dubbo/dubbo_protocol.go
@@ -45,14 +45,14 @@ var (
dubboProtocol *DubboProtocol
)
-// DubboProtocol ...
+// DubboProtocol is a dubbo protocol implement.
type DubboProtocol struct {
protocol.BaseProtocol
serverMap map[string]*Server
serverLock sync.Mutex
}
-// NewDubboProtocol ...
+// NewDubboProtocol create a dubbo protocol.
func NewDubboProtocol() *DubboProtocol {
return &DubboProtocol{
BaseProtocol: protocol.NewBaseProtocol(),
@@ -60,7 +60,7 @@ func NewDubboProtocol() *DubboProtocol {
}
}
-// Export ...
+// Export export dubbo service.
func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
url := invoker.GetUrl()
serviceKey := url.ServiceKey()
@@ -73,7 +73,7 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
return exporter
}
-// Refer ...
+// Refer create dubbo service reference.
func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker {
//default requestTimeout
var requestTimeout = config.GetConsumerConfig().RequestTimeout
@@ -92,7 +92,7 @@ func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker {
return invoker
}
-// Destroy ...
+// Destroy destroy dubbo service.
func (dp *DubboProtocol) Destroy() {
logger.Infof("DubboProtocol destroy.")
@@ -124,7 +124,7 @@ func (dp *DubboProtocol) openServer(url common.URL) {
}
}
-// GetProtocol ...
+// GetProtocol get a single dubbo protocol.
func GetProtocol() protocol.Protocol {
if dubboProtocol == nil {
dubboProtocol = NewDubboProtocol()
diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go
index 0251b78a2b0d27a68461c16c284b1af53bcb08aa..1f4cc0068efb5688b545fa35b784b4fb2e923dc7 100644
--- a/protocol/dubbo/listener.go
+++ b/protocol/dubbo/listener.go
@@ -86,7 +86,7 @@ func (h *RpcClientHandler) OnOpen(session getty.Session) error {
// OnError ...
func (h *RpcClientHandler) OnError(session getty.Session, err error) {
- logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err)
+ logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err)
h.conn.removeSession(session)
}
@@ -201,7 +201,7 @@ func (h *RpcServerHandler) OnOpen(session getty.Session) error {
// OnError ...
func (h *RpcServerHandler) OnError(session getty.Session, err error) {
- logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err)
+ logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err)
h.rwlock.Lock()
delete(h.sessionMap, session)
h.rwlock.Unlock()
diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go
index b5c4f509190dbdc85825ad424656240b234786df..9cc7ea25cdfa6152d632f278b33285d7d38f47c9 100644
--- a/protocol/dubbo/readwriter.go
+++ b/protocol/dubbo/readwriter.go
@@ -38,16 +38,17 @@ import (
// RpcClientPackageHandler
////////////////////////////////////////////
-// RpcClientPackageHandler ...
+// RpcClientPackageHandler handle package for client in getty.
type RpcClientPackageHandler struct {
client *Client
}
-// NewRpcClientPackageHandler ...
+// NewRpcClientPackageHandler create a RpcClientPackageHandler.
func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler {
return &RpcClientPackageHandler{client: client}
}
+// Read decode @data to DubboPackage.
func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
pkg := &DubboPackage{}
@@ -72,6 +73,7 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface
return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil
}
+// Write encode @pkg.
func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) {
req, ok := pkg.(*DubboPackage)
if !ok {
@@ -96,9 +98,10 @@ var (
rpcServerPkgHandler = &RpcServerPackageHandler{}
)
-// RpcServerPackageHandler ...
+// RpcServerPackageHandler handle package for server in getty.
type RpcServerPackageHandler struct{}
+// Read decode @data to DubboPackage.
func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
pkg := &DubboPackage{
Body: make([]interface{}, 7),
@@ -169,6 +172,7 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface
return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil
}
+// Write encode @pkg.
func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) {
res, ok := pkg.(*DubboPackage)
if !ok {
diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go
index bd2b37b7a9f055745e183524d19a442af03360f4..8de353a0b372785e89585f4bf2b00b2c42540a2b 100644
--- a/protocol/dubbo/server.go
+++ b/protocol/dubbo/server.go
@@ -71,10 +71,10 @@ func init() {
if err := srvConf.CheckValidity(); err != nil {
panic(err)
}
- SetServerGrpool()
+ setServerGrpool()
}
-// SetServerConfig ...
+// SetServerConfig set dubbo server config.
func SetServerConfig(s ServerConfig) {
srvConf = &s
err := srvConf.CheckValidity()
@@ -82,30 +82,29 @@ func SetServerConfig(s ServerConfig) {
logger.Warnf("[ServerConfig CheckValidity] error: %v", err)
return
}
- SetServerGrpool()
+ setServerGrpool()
}
-// GetServerConfig ...
+// GetServerConfig get dubbo server config.
func GetServerConfig() ServerConfig {
return *srvConf
}
-// SetServerGrpool ...
-func SetServerGrpool() {
+func setServerGrpool() {
if srvConf.GrPoolSize > 1 {
srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen),
gxsync.WithTaskPoolTaskQueueNumber(srvConf.QueueNumber))
}
}
-// Server ...
+// Server is dubbo protocol server.
type Server struct {
conf ServerConfig
tcpServer getty.Server
rpcHandler *RpcServerHandler
}
-// NewServer ...
+// NewServer create a new Server.
func NewServer() *Server {
s := &Server{
@@ -156,7 +155,7 @@ func (s *Server) newSession(session getty.Session) error {
return nil
}
-// Start ...
+// Start start dubbo server.
func (s *Server) Start(url common.URL) {
var (
addr string
@@ -173,7 +172,7 @@ func (s *Server) Start(url common.URL) {
}
-// Stop ...
+// Stop stop dubbo server.
func (s *Server) Stop() {
s.tcpServer.Close()
}
diff --git a/registry/base_registry.go b/registry/base_registry.go
index 04694da25a38899cfc6f56a8092a15547f142e9c..3e1bddf233310871182544b6415c10c8df27e622 100644
--- a/registry/base_registry.go
+++ b/registry/base_registry.go
@@ -121,6 +121,7 @@ func (r *BaseRegistry) Destroy() {
close(r.done)
// wait waitgroup done (wait listeners outside close over)
r.wg.Wait()
+
//close registry client
r.closeRegisters()
}
@@ -178,7 +179,10 @@ func (r *BaseRegistry) RestartCallBack() bool {
}
logger.Infof("success to re-register service :%v", confIf.Key())
}
- r.facadeBasedRegistry.InitListeners()
+
+ if flag {
+ r.facadeBasedRegistry.InitListeners()
+ }
return flag
}
diff --git a/registry/directory/directory.go b/registry/directory/directory.go
index 20be268d7401976ef1b7884f2a7bd40eeacb8158..552aa57061c99bf92ff986b6e672743ebb375e76 100644
--- a/registry/directory/directory.go
+++ b/registry/directory/directory.go
@@ -19,7 +19,6 @@ package directory
import (
"sync"
- "time"
)
import (
@@ -28,6 +27,7 @@ import (
)
import (
+ "github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/cluster/directory"
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/constant"
@@ -42,15 +42,11 @@ import (
"github.com/apache/dubbo-go/remoting"
)
-// Options ...
-type Options struct {
- serviceTTL time.Duration
+func init() {
+ extension.SetDefaultRegistryDirectory(NewRegistryDirectory)
}
-// Option ...
-type Option func(*Options)
-
-type registryDirectory struct {
+type RegistryDirectory struct {
directory.BaseDirectory
cacheInvokers []protocol.Invoker
listenerLock sync.Mutex
@@ -61,48 +57,41 @@ type registryDirectory struct {
configurators []config_center.Configurator
consumerConfigurationListener *consumerConfigurationListener
referenceConfigurationListener *referenceConfigurationListener
- Options
- serviceKey string
- forbidden atomic.Bool
+ serviceKey string
+ forbidden atomic.Bool
}
// NewRegistryDirectory ...
-func NewRegistryDirectory(url *common.URL, registry registry.Registry, opts ...Option) (*registryDirectory, error) {
- options := Options{
- //default 300s
- serviceTTL: time.Duration(300e9),
- }
- for _, opt := range opts {
- opt(&options)
- }
+func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster.Directory, error) {
if url.SubURL == nil {
return nil, perrors.Errorf("url is invalid, suburl can not be nil")
}
- dir := ®istryDirectory{
+ dir := &RegistryDirectory{
BaseDirectory: directory.NewBaseDirectory(url),
cacheInvokers: []protocol.Invoker{},
cacheInvokersMap: &sync.Map{},
serviceType: url.SubURL.Service(),
registry: registry,
- Options: options,
}
dir.consumerConfigurationListener = newConsumerConfigurationListener(dir)
+
+ go dir.subscribe(url.SubURL)
return dir, nil
}
//subscribe from registry
-func (dir *registryDirectory) Subscribe(url *common.URL) {
+func (dir *RegistryDirectory) subscribe(url *common.URL) {
dir.consumerConfigurationListener.addNotifyListener(dir)
dir.referenceConfigurationListener = newReferenceConfigurationListener(dir, url)
dir.registry.Subscribe(url, dir)
}
-func (dir *registryDirectory) Notify(event *registry.ServiceEvent) {
+func (dir *RegistryDirectory) Notify(event *registry.ServiceEvent) {
go dir.update(event)
}
-//subscribe service from registry, and update the cacheServices
-func (dir *registryDirectory) update(res *registry.ServiceEvent) {
+// update the cacheServices and subscribe service from registry
+func (dir *RegistryDirectory) update(res *registry.ServiceEvent) {
if res == nil {
return
}
@@ -111,7 +100,7 @@ func (dir *registryDirectory) update(res *registry.ServiceEvent) {
dir.refreshInvokers(res)
}
-func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) {
+func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) {
var (
url *common.URL
oldInvoker protocol.Invoker = nil
@@ -162,7 +151,7 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) {
}
-func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker {
+func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker {
newInvokersList := []protocol.Invoker{}
groupInvokersMap := make(map[string][]protocol.Invoker)
groupInvokersList := []protocol.Invoker{}
@@ -198,8 +187,8 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker {
return groupInvokersList
}
-// uncacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil
-func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker {
+// uncacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil
+func (dir *RegistryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker {
logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", url.Key())
if cacheInvoker, ok := dir.cacheInvokersMap.Load(url.Key()); ok {
dir.cacheInvokersMap.Delete(url.Key())
@@ -208,8 +197,8 @@ func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker {
return nil
}
-// cacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil
-func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
+// cacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil
+func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
dir.overrideUrl(dir.GetDirectoryUrl())
referenceUrl := dir.GetDirectoryUrl().SubURL
@@ -244,8 +233,8 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
return nil
}
-//select the protocol invokers from the directory
-func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
+// List selected protocol invokers from the directory
+func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
invokers := dir.cacheInvokers
routerChain := dir.RouterChain()
@@ -255,7 +244,7 @@ func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.In
return routerChain.Route(invokers, dir.cacheOriginUrl, invocation)
}
-func (dir *registryDirectory) IsAvailable() bool {
+func (dir *RegistryDirectory) IsAvailable() bool {
if !dir.BaseDirectory.IsAvailable() {
return dir.BaseDirectory.IsAvailable()
}
@@ -269,7 +258,7 @@ func (dir *registryDirectory) IsAvailable() bool {
return false
}
-func (dir *registryDirectory) Destroy() {
+func (dir *RegistryDirectory) Destroy() {
//TODO:unregister & unsubscribe
dir.BaseDirectory.Destroy(func() {
invokers := dir.cacheInvokers
@@ -280,7 +269,7 @@ func (dir *registryDirectory) Destroy() {
})
}
-func (dir *registryDirectory) overrideUrl(targetUrl *common.URL) {
+func (dir *RegistryDirectory) overrideUrl(targetUrl *common.URL) {
doOverrideUrl(dir.configurators, targetUrl)
doOverrideUrl(dir.consumerConfigurationListener.Configurators(), targetUrl)
doOverrideUrl(dir.referenceConfigurationListener.Configurators(), targetUrl)
@@ -294,11 +283,11 @@ func doOverrideUrl(configurators []config_center.Configurator, targetUrl *common
type referenceConfigurationListener struct {
registry.BaseConfigurationListener
- directory *registryDirectory
+ directory *RegistryDirectory
url *common.URL
}
-func newReferenceConfigurationListener(dir *registryDirectory, url *common.URL) *referenceConfigurationListener {
+func newReferenceConfigurationListener(dir *RegistryDirectory, url *common.URL) *referenceConfigurationListener {
listener := &referenceConfigurationListener{directory: dir, url: url}
listener.InitWith(
url.EncodedServiceKey()+constant.CONFIGURATORS_SUFFIX,
@@ -316,10 +305,10 @@ func (l *referenceConfigurationListener) Process(event *config_center.ConfigChan
type consumerConfigurationListener struct {
registry.BaseConfigurationListener
listeners []registry.NotifyListener
- directory *registryDirectory
+ directory *RegistryDirectory
}
-func newConsumerConfigurationListener(dir *registryDirectory) *consumerConfigurationListener {
+func newConsumerConfigurationListener(dir *RegistryDirectory) *consumerConfigurationListener {
listener := &consumerConfigurationListener{directory: dir}
listener.InitWith(
config.GetConsumerConfig().ApplicationConfig.Name+constant.CONFIGURATORS_SUFFIX,
diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go
index 0dde44e73c18f65f262e01f499e198995907dece..f1d5ce434aa00185f784f208eefe603274f05ab0 100644
--- a/registry/directory/directory_test.go
+++ b/registry/directory/directory_test.go
@@ -79,10 +79,9 @@ func TestSubscribe_Group(t *testing.T) {
suburl.SetParam(constant.CLUSTER_KEY, "mock")
regurl.SubURL = &suburl
mockRegistry, _ := registry.NewMockRegistry(&common.URL{})
- registryDirectory, _ := NewRegistryDirectory(®url, mockRegistry)
-
- go registryDirectory.Subscribe(common.NewURLWithOptions(common.WithPath("testservice")))
+ dir, _ := NewRegistryDirectory(®url, mockRegistry)
+ go dir.(*RegistryDirectory).subscribe(common.NewURLWithOptions(common.WithPath("testservice")))
//for group1
urlmap := url.Values{}
urlmap.Set(constant.GROUP_KEY, "group1")
@@ -101,7 +100,7 @@ func TestSubscribe_Group(t *testing.T) {
}
time.Sleep(1e9)
- assert.Len(t, registryDirectory.cacheInvokers, 2)
+ assert.Len(t, dir.(*RegistryDirectory).cacheInvokers, 2)
}
func Test_Destroy(t *testing.T) {
@@ -173,7 +172,7 @@ Loop1:
}
-func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockRegistry) {
+func normalRegistryDir(noMockEvent ...bool) (*RegistryDirectory, *registry.MockRegistry) {
extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter)
url, _ := common.NewURL("mock://127.0.0.1:1111")
@@ -185,9 +184,9 @@ func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockR
)
url.SubURL = &suburl
mockRegistry, _ := registry.NewMockRegistry(&common.URL{})
- registryDirectory, _ := NewRegistryDirectory(&url, mockRegistry)
+ dir, _ := NewRegistryDirectory(&url, mockRegistry)
- go registryDirectory.Subscribe(&suburl)
+ go dir.(*RegistryDirectory).subscribe(&suburl)
if len(noMockEvent) == 0 {
for i := 0; i < 3; i++ {
mockRegistry.(*registry.MockRegistry).MockEvent(
@@ -201,5 +200,5 @@ func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockR
)
}
}
- return registryDirectory, mockRegistry.(*registry.MockRegistry)
+ return dir.(*RegistryDirectory), mockRegistry.(*registry.MockRegistry)
}
diff --git a/registry/event.go b/registry/event.go
index 37d863d2162cb3b9d6a9f7eba8823286eb99441c..be9f11d00bb5a70b0d787d15bcdc98471aad0a4b 100644
--- a/registry/event.go
+++ b/registry/event.go
@@ -32,9 +32,9 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
-//////////////////////////////////////////
+// ////////////////////////////////////////
// service event
-//////////////////////////////////////////
+// ////////////////////////////////////////
// ServiceEvent ...
type ServiceEvent struct {
@@ -42,6 +42,69 @@ type ServiceEvent struct {
Service common.URL
}
+// String return the description of event
func (e ServiceEvent) String() string {
return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}}", e.Action, e.Service)
}
+
+// Event is align with Event interface in Java.
+// it's the top abstraction
+// Align with 2.7.5
+type Event interface {
+ fmt.Stringer
+ GetSource() interface{}
+ GetTimestamp() time.Time
+}
+
+// baseEvent is the base implementation of Event
+// You should never use it directly
+type baseEvent struct {
+ source interface{}
+ timestamp time.Time
+}
+
+// GetSource return the source
+func (b *baseEvent) GetSource() interface{} {
+ return b.source
+}
+
+// GetTimestamp return the timestamp when the event is created
+func (b *baseEvent) GetTimestamp() time.Time {
+ return b.timestamp
+}
+
+// String return a human readable string representing this event
+func (b *baseEvent) String() string {
+ return fmt.Sprintf("baseEvent[source = %#v]", b.source)
+}
+
+func newBaseEvent(source interface{}) *baseEvent {
+ return &baseEvent{
+ source: source,
+ timestamp: time.Now(),
+ }
+}
+
+// ServiceInstancesChangedEvent represents service instances make some changing
+type ServiceInstancesChangedEvent struct {
+ baseEvent
+ ServiceName string
+ Instances []ServiceInstance
+}
+
+// String return the description of the event
+func (s *ServiceInstancesChangedEvent) String() string {
+ return fmt.Sprintf("ServiceInstancesChangedEvent[source=%s]", s.ServiceName)
+}
+
+// NewServiceInstancesChangedEvent will create the ServiceInstanceChangedEvent instance
+func NewServiceInstancesChangedEvent(serviceName string, instances []ServiceInstance) *ServiceInstancesChangedEvent {
+ return &ServiceInstancesChangedEvent{
+ baseEvent: baseEvent{
+ source: serviceName,
+ timestamp: time.Now(),
+ },
+ ServiceName: serviceName,
+ Instances: instances,
+ }
+}
diff --git a/registry/event_listener.go b/registry/event_listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8d6148442d9e10e210958dead690c4a95b33fb6
--- /dev/null
+++ b/registry/event_listener.go
@@ -0,0 +1,42 @@
+/*
+ * 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 registry
+
+import (
+ gxsort "github.com/dubbogo/gost/sort"
+)
+
+// EventListener is an new interface used to align with dubbo 2.7.5
+// It contains the Prioritized means that the listener has its priority
+type EventListener interface {
+ gxsort.Prioritizer
+ // OnEvent handle this event
+ OnEvent(e Event) error
+}
+
+// ConditionalEventListener only handle the event which it can handle
+type ConditionalEventListener interface {
+ EventListener
+ // Accept will make the decision whether it should handle this event
+ Accept(e Event) bool
+}
+
+// TODO (implement ConditionalEventListener)
+type ServiceInstancesChangedListener struct {
+ ServiceName string
+}
diff --git a/registry/nacos/base_registry.go b/registry/nacos/base_registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..63f4999675470853d0f48d1a22b709efdc1c9d26
--- /dev/null
+++ b/registry/nacos/base_registry.go
@@ -0,0 +1,102 @@
+/*
+ * 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 nacos
+
+import (
+ "net"
+ "strconv"
+ "strings"
+ "time"
+)
+
+import (
+ "github.com/nacos-group/nacos-sdk-go/clients"
+ "github.com/nacos-group/nacos-sdk-go/clients/naming_client"
+ nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant"
+ perrors "github.com/pkg/errors"
+)
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/common/constant"
+)
+
+// baseRegistry is the parent of both interface-level registry
+// and service discovery(related to application-level registry)
+type nacosBaseRegistry struct {
+ *common.URL
+ namingClient naming_client.INamingClient
+}
+
+// newBaseRegistry will create new instance
+func newBaseRegistry(url *common.URL) (nacosBaseRegistry, error) {
+ nacosConfig, err := getNacosConfig(url)
+ if err != nil {
+ return nacosBaseRegistry{}, err
+ }
+ client, err := clients.CreateNamingClient(nacosConfig)
+ if err != nil {
+ return nacosBaseRegistry{}, err
+ }
+ registry := nacosBaseRegistry{
+ URL: url,
+ namingClient: client,
+ }
+ return registry, nil
+}
+
+// getNacosConfig will return the nacos config
+func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
+ if url == nil {
+ return nil, perrors.New("url is empty!")
+ }
+ if len(url.Location) == 0 {
+ return nil, perrors.New("url.location is empty!")
+ }
+ configMap := make(map[string]interface{}, 2)
+
+ addresses := strings.Split(url.Location, ",")
+ serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses))
+ for _, addr := range addresses {
+ ip, portStr, err := net.SplitHostPort(addr)
+ if err != nil {
+ return nil, perrors.WithMessagef(err, "split [%s] ", addr)
+ }
+ port, _ := strconv.Atoi(portStr)
+ serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{
+ IpAddr: ip,
+ Port: uint64(port),
+ })
+ }
+ configMap["serverConfigs"] = serverConfigs
+
+ var clientConfig nacosConstant.ClientConfig
+ timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
+ if err != nil {
+ return nil, err
+ }
+ clientConfig.TimeoutMs = uint64(timeout.Seconds() * 1000)
+ clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs
+ clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "")
+ clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "")
+ clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "")
+ clientConfig.NotLoadCacheAtStart = true
+ configMap["clientConfig"] = clientConfig
+
+ return configMap, nil
+}
diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go
index 965e91e894ac61562bfd25c8f564f789afd6c8a1..a436b85064829b9f42c9dcc45545e5bf2fd2fefe 100644
--- a/registry/nacos/registry.go
+++ b/registry/nacos/registry.go
@@ -19,7 +19,6 @@ package nacos
import (
"bytes"
- "net"
"strconv"
"strings"
"time"
@@ -27,9 +26,6 @@ import (
import (
gxnet "github.com/dubbogo/gost/net"
- "github.com/nacos-group/nacos-sdk-go/clients"
- "github.com/nacos-group/nacos-sdk-go/clients/naming_client"
- nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
perrors "github.com/pkg/errors"
)
@@ -57,64 +53,18 @@ func init() {
}
type nacosRegistry struct {
- *common.URL
- namingClient naming_client.INamingClient
-}
-
-func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
- if url == nil {
- return nil, perrors.New("url is empty!")
- }
- if len(url.Location) == 0 {
- return nil, perrors.New("url.location is empty!")
- }
- configMap := make(map[string]interface{}, 2)
-
- addresses := strings.Split(url.Location, ",")
- serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses))
- for _, addr := range addresses {
- ip, portStr, err := net.SplitHostPort(addr)
- if err != nil {
- return nil, perrors.WithMessagef(err, "split [%s] ", addr)
- }
- port, _ := strconv.Atoi(portStr)
- serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{
- IpAddr: ip,
- Port: uint64(port),
- })
- }
- configMap["serverConfigs"] = serverConfigs
-
- var clientConfig nacosConstant.ClientConfig
- timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
- if err != nil {
- return nil, err
- }
- clientConfig.TimeoutMs = uint64(timeout.Seconds() * 1000)
- clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs
- clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "")
- clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "")
- clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "")
- clientConfig.NotLoadCacheAtStart = true
- configMap["clientConfig"] = clientConfig
-
- return configMap, nil
+ nacosBaseRegistry
}
+// newNacosRegistry will create an instance
func newNacosRegistry(url *common.URL) (registry.Registry, error) {
- nacosConfig, err := getNacosConfig(url)
+ base, err := newBaseRegistry(url)
if err != nil {
- return nil, err
- }
- client, err := clients.CreateNamingClient(nacosConfig)
- if err != nil {
- return nil, err
- }
- registry := nacosRegistry{
- URL: url,
- namingClient: client,
+ return nil, perrors.WithStack(err)
}
- return ®istry, nil
+ return &nacosRegistry{
+ base,
+ }, nil
}
func getCategory(url common.URL) string {
diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..7d3406cac233dd5293c7522b4f12148fdcdd704e
--- /dev/null
+++ b/registry/nacos/service_discovery.go
@@ -0,0 +1,285 @@
+/*
+ * 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 nacos
+
+import (
+ "github.com/dubbogo/gost/container/set"
+ "github.com/dubbogo/gost/page"
+ "github.com/nacos-group/nacos-sdk-go/model"
+ "github.com/nacos-group/nacos-sdk-go/vo"
+ 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/extension"
+ "github.com/apache/dubbo-go/common/logger"
+ "github.com/apache/dubbo-go/registry"
+)
+
+const (
+ defaultGroup = "DEFAULT_GROUP"
+ idKey = "id"
+)
+
+// init will put the service discovery into extension
+func init() {
+ extension.SetServiceDiscovery(constant.NACOS_KEY, newNacosServiceDiscovery)
+}
+
+// nacosServiceDiscovery is the implementation of service discovery based on nacos.
+// There is a problem, the go client for nacos does not support the id field.
+// we will use the metadata to store the id of ServiceInstance
+type nacosServiceDiscovery struct {
+ nacosBaseRegistry
+ group string
+}
+
+// Destroy will close the service discovery.
+// Actually, it only marks the naming client as null and then return
+func (n *nacosServiceDiscovery) Destroy() error {
+ n.namingClient = nil
+ return nil
+}
+
+// Register will register the service to nacos
+func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) error {
+ ins := n.toRegisterInstance(instance)
+ ok, err := n.namingClient.RegisterInstance(ins)
+ if err != nil || !ok {
+ return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName())
+ }
+ return nil
+}
+
+// Update will update the information
+// However, because nacos client doesn't support the update API,
+// so we should unregister the instance and then register it again.
+// the error handling is hard to implement
+func (n *nacosServiceDiscovery) Update(instance registry.ServiceInstance) error {
+ // TODO(wait for nacos support)
+ err := n.Unregister(instance)
+ if err != nil {
+ return perrors.WithStack(err)
+ }
+ return n.Register(instance)
+}
+
+// Unregister will unregister the instance
+func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+ ok, err := n.namingClient.DeregisterInstance(n.toDeregisterInstance(instance))
+ if err != nil || !ok {
+ return perrors.WithMessage(err, "Could not unregister the instance. "+instance.GetServiceName())
+ }
+ return nil
+}
+
+// GetDefaultPageSize will return the constant registry.DefaultPageSize
+func (n *nacosServiceDiscovery) GetDefaultPageSize() int {
+ return registry.DefaultPageSize
+}
+
+// GetServices will return the all services
+func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet {
+ services, err := n.namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
+ GroupName: n.group,
+ })
+
+ res := gxset.NewSet()
+ if err != nil {
+ logger.Errorf("Could not query the services: %v", err)
+ return res
+ }
+
+ for _, e := range services {
+ res.Add(e.Name)
+ }
+ return res
+}
+
+// GetInstances will return the instances of serviceName and the group
+func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+ instances, err := n.namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
+ ServiceName: serviceName,
+ GroupName: n.group,
+ })
+ if err != nil {
+ logger.Errorf("Could not query the instances for service: " + serviceName + ", group: " + n.group)
+ return make([]registry.ServiceInstance, 0, 0)
+ }
+ res := make([]registry.ServiceInstance, 0, len(instances))
+ for _, ins := range instances {
+ metadata := ins.Metadata
+ id := metadata[idKey]
+
+ delete(metadata, idKey)
+
+ res = append(res, ®istry.DefaultServiceInstance{
+ Id: id,
+ ServiceName: ins.ServiceName,
+ Host: ins.Ip,
+ Port: int(ins.Port),
+ Enable: ins.Enable,
+ Healthy: ins.Healthy,
+ Metadata: metadata,
+ })
+ }
+
+ return res
+}
+
+// GetInstancesByPage will return the instances
+// Due to nacos client does not support pagination, so we have to query all instances and then return part of them
+func (n *nacosServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+ all := n.GetInstances(serviceName)
+ res := make([]interface{}, 0, pageSize)
+ // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
+ for i := offset; i < len(all) && i < offset+pageSize; i++ {
+ res = append(res, all[i])
+ }
+ return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// GetHealthyInstancesByPage will return the instance
+// The nacos client has an API SelectInstances, which has a parameter call HealthyOnly.
+// However, the healthy parameter in this method maybe false. So we can not use that API.
+// Thus, we must query all instances and then do filter
+func (n *nacosServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+ all := n.GetInstances(serviceName)
+ res := make([]interface{}, 0, pageSize)
+ // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
+ var (
+ i = offset
+ count = 0
+ )
+ for i < len(all) && count < pageSize {
+ ins := all[i]
+ if ins.IsHealthy() == healthy {
+ res = append(res, all[i])
+ count++
+ }
+ i++
+ }
+ return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// GetRequestInstances will return the instances
+// The nacos client doesn't have batch API, so we should query those serviceNames one by one.
+func (n *nacosServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+ res := make(map[string]gxpage.Pager, len(serviceNames))
+ for _, name := range serviceNames {
+ res[name] = n.GetInstancesByPage(name, offset, requestedSize)
+ }
+ return res
+}
+
+// AddListener will add a listener
+func (n *nacosServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+ return n.namingClient.Subscribe(&vo.SubscribeParam{
+ ServiceName: listener.ServiceName,
+ SubscribeCallback: func(services []model.SubscribeService, err error) {
+ if err != nil {
+ logger.Errorf("Could not handle the subscribe notification because the err is not nil."+
+ " service name: %s, err: %v", listener.ServiceName, err)
+ }
+ instances := make([]registry.ServiceInstance, 0, len(services))
+ for _, service := range services {
+ // we won't use the nacos instance id here but use our instance id
+ metadata := service.Metadata
+ id := metadata[idKey]
+
+ delete(metadata, idKey)
+
+ instances = append(instances, ®istry.DefaultServiceInstance{
+ Id: id,
+ ServiceName: service.ServiceName,
+ Host: service.Ip,
+ Port: int(service.Port),
+ Enable: service.Enable,
+ Healthy: true,
+ Metadata: metadata,
+ })
+ }
+
+ e := n.DispatchEventForInstances(listener.ServiceName, instances)
+ if e != nil {
+ logger.Errorf("Dispatching event got exception, service name: %s, err: %v", listener.ServiceName, err)
+ }
+ },
+ })
+}
+
+// DispatchEventByServiceName will dispatch the event for the service with the service name
+func (n *nacosServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+ return n.DispatchEventForInstances(serviceName, n.GetInstances(serviceName))
+}
+
+// DispatchEventForInstances will dispatch the event to those instances
+func (n *nacosServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+ return n.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances))
+}
+
+// DispatchEvent will dispatch the event
+func (n *nacosServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+ // TODO(waiting for event dispatcher, another task)
+ return nil
+}
+
+// toRegisterInstance convert the ServiceInstance to RegisterInstanceParam
+// the Ephemeral will be true
+func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInstance) vo.RegisterInstanceParam {
+ metadata := instance.GetMetadata()
+ if metadata == nil {
+ metadata = make(map[string]string, 1)
+ }
+ metadata[idKey] = instance.GetId()
+ return vo.RegisterInstanceParam{
+ ServiceName: instance.GetServiceName(),
+ Ip: instance.GetHost(),
+ Port: uint64(instance.GetPort()),
+ Metadata: metadata,
+ Enable: instance.IsEnable(),
+ Healthy: instance.IsHealthy(),
+ GroupName: n.group,
+ Ephemeral: true,
+ }
+}
+
+// toDeregisterInstance will convert the ServiceInstance to DeregisterInstanceParam
+func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceInstance) vo.DeregisterInstanceParam {
+ return vo.DeregisterInstanceParam{
+ ServiceName: instance.GetServiceName(),
+ Ip: instance.GetHost(),
+ Port: uint64(instance.GetPort()),
+ GroupName: n.group,
+ }
+}
+
+// toDeregisterInstance will create new service discovery instance
+func newNacosServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) {
+
+ base, err := newBaseRegistry(url)
+ if err != nil {
+ return nil, perrors.WithStack(err)
+ }
+ return &nacosServiceDiscovery{
+ nacosBaseRegistry: base,
+ group: url.GetParam(constant.NACOS_GROUP, defaultGroup),
+ }, nil
+}
diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a756e8669301919d406a4bcf0e1c962cf532a5c6
--- /dev/null
+++ b/registry/nacos/service_discovery_test.go
@@ -0,0 +1,122 @@
+/*
+ * 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 nacos
+
+import (
+ "strconv"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+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/registry"
+)
+
+func TestNacosServiceDiscovery_Destroy(t *testing.T) {
+ serviceDiscovry, err := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
+ assert.Nil(t, err)
+ assert.NotNil(t, serviceDiscovry)
+ err = serviceDiscovry.Destroy()
+ assert.Nil(t, err)
+ assert.Nil(t, serviceDiscovry.(*nacosServiceDiscovery).namingClient)
+}
+
+func TestNacosServiceDiscovery_CRUD(t *testing.T) {
+ serviceName := "service-name"
+ id := "id"
+ host := "host"
+ port := 123
+ instance := ®istry.DefaultServiceInstance{
+ Id: id,
+ ServiceName: serviceName,
+ Host: host,
+ Port: port,
+ Enable: true,
+ Healthy: true,
+ Metadata: nil,
+ }
+
+ // clean data
+
+ serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
+
+ // clean data for local test
+ serviceDiscovry.Unregister(®istry.DefaultServiceInstance{
+ Id: id,
+ ServiceName: serviceName,
+ Host: host,
+ Port: port,
+ })
+
+ err := serviceDiscovry.Register(instance)
+ assert.Nil(t, err)
+
+ page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true)
+ assert.NotNil(t, page)
+
+ assert.Equal(t, 0, page.GetOffset())
+ assert.Equal(t, 10, page.GetPageSize())
+ assert.Equal(t, 1, page.GetDataSize())
+
+ instance = page.GetData()[0].(*registry.DefaultServiceInstance)
+ assert.NotNil(t, instance)
+ assert.Equal(t, id, instance.GetId())
+ assert.Equal(t, host, instance.GetHost())
+ assert.Equal(t, port, instance.GetPort())
+ assert.Equal(t, serviceName, instance.GetServiceName())
+ assert.Equal(t, 0, len(instance.GetMetadata()))
+
+ instance.Metadata["a"] = "b"
+
+ err = serviceDiscovry.Update(instance)
+ assert.Nil(t, err)
+
+ pageMap := serviceDiscovry.GetRequestInstances([]string{serviceName}, 0, 1)
+ assert.Equal(t, 1, len(pageMap))
+ page = pageMap[serviceName]
+ assert.NotNil(t, page)
+ assert.Equal(t, 1, len(page.GetData()))
+
+ instance = page.GetData()[0].(*registry.DefaultServiceInstance)
+ v, _ := instance.Metadata["a"]
+ assert.Equal(t, "b", v)
+
+ // test dispatcher event
+ err = serviceDiscovry.DispatchEventByServiceName(serviceName)
+ assert.Nil(t, err)
+
+ // test AddListener
+ err = serviceDiscovry.AddListener(®istry.ServiceInstancesChangedListener{})
+ assert.Nil(t, err)
+}
+
+func TestNacosServiceDiscovery_GetDefaultPageSize(t *testing.T) {
+ serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
+ assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize())
+}
+
+func mockUrl() *common.URL {
+ regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+ return ®url
+}
diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go
index a7678ba4e2f38cfeb77f202103e03066a7efdbef..aa8fbcbe7d6eca682892d4627878fe6bfc3756fe 100644
--- a/registry/protocol/protocol.go
+++ b/registry/protocol/protocol.go
@@ -22,7 +22,6 @@ import (
"strings"
"sync"
)
-
import (
gxset "github.com/dubbogo/gost/container/set"
)
@@ -39,12 +38,18 @@ import (
"github.com/apache/dubbo-go/protocol"
"github.com/apache/dubbo-go/protocol/protocolwrapper"
"github.com/apache/dubbo-go/registry"
- directory2 "github.com/apache/dubbo-go/registry/directory"
+ _ "github.com/apache/dubbo-go/registry/directory"
"github.com/apache/dubbo-go/remoting"
)
var (
- regProtocol *registryProtocol
+ regProtocol *registryProtocol
+ once sync.Once
+ reserveParams = []string{
+ "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group",
+ "loadbalance", "mock", "path", "timeout", "token", "version", "warmup", "weight", "timestamp", "dubbo",
+ "release", "interface",
+ }
)
type registryProtocol struct {
@@ -87,6 +92,29 @@ func getRegistry(regUrl *common.URL) registry.Registry {
return reg
}
+func getUrlToRegistry(providerUrl *common.URL, registryUrl *common.URL) *common.URL {
+ if registryUrl.GetParamBool("simplified", false) {
+ return providerUrl.CloneWithParams(reserveParams)
+ } else {
+ return filterHideKey(providerUrl)
+ }
+}
+
+// filterHideKey filter the parameters that do not need to be output in url(Starting with .)
+func filterHideKey(url *common.URL) *common.URL {
+
+ //be careful params maps in url is map type
+ cloneURL := url.Clone()
+ removeSet := gxset.NewSet()
+ for k, _ := range cloneURL.GetParams() {
+ if strings.HasPrefix(k, ".") {
+ removeSet.Add(k)
+ }
+ }
+ cloneURL.RemoveParams(removeSet)
+ return cloneURL
+}
+
func (proto *registryProtocol) initConfigurationListeners() {
proto.overrideListeners = &sync.Map{}
proto.serviceConfigurationListeners = &sync.Map{}
@@ -111,7 +139,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
}
//new registry directory for store service url from registry
- directory, err := directory2.NewRegistryDirectory(®istryUrl, reg)
+ directory, err := extension.GetDefaultRegistryDirectory(®istryUrl, reg)
if err != nil {
logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!",
serviceUrl.String(), err.Error())
@@ -123,7 +151,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
logger.Errorf("consumer service %v register registry %v error, error message is %s",
serviceUrl.String(), registryUrl.String(), err.Error())
}
- go directory.Subscribe(serviceUrl)
//new cluster invoker
cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER))
@@ -150,7 +177,6 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte
serviceConfigurationListener.OverrideUrl(providerUrl)
var reg registry.Registry
-
if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded {
reg = getRegistry(registryUrl)
proto.registries.Store(registryUrl.Key(), reg)
@@ -158,7 +184,8 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte
reg = regI.(registry.Registry)
}
- err := reg.Register(*providerUrl)
+ registeredProviderUrl := getUrlToRegistry(providerUrl, registryUrl)
+ err := reg.Register(*registeredProviderUrl)
if err != nil {
logger.Errorf("provider service %v register registry %v error, error message is %s",
providerUrl.Key(), registryUrl.Key(), err.Error())
@@ -346,12 +373,12 @@ func setProviderUrl(regURL *common.URL, providerURL *common.URL) {
regURL.SubURL = providerURL
}
-// GetProtocol ...
+// GetProtocol return the singleton RegistryProtocol
func GetProtocol() protocol.Protocol {
- if regProtocol != nil {
- return regProtocol
- }
- return newRegistryProtocol()
+ once.Do(func() {
+ regProtocol = newRegistryProtocol()
+ })
+ return regProtocol
}
type wrappedInvoker struct {
diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go
index cee2a6a625368f655d1b9bc5fe8cc37031e1aef7..15fd3cacfacad36309e0ad4deb3c7c7441e47e26 100644
--- a/registry/protocol/protocol_test.go
+++ b/registry/protocol/protocol_test.go
@@ -284,3 +284,12 @@ func TestExportWithApplicationConfig(t *testing.T) {
v2, _ := regProtocol.bounds.Load(getCacheKey(newUrl))
assert.NotNil(t, v2)
}
+
+func TestGetProviderUrlWithHideKey(t *testing.T) {
+ url, _ := common.NewURL("dubbo://127.0.0.1:1111?a=a1&b=b1&.c=c1&.d=d1&e=e1&protocol=registry")
+ providerUrl := getUrlToRegistry(&url, &url)
+ assert.NotContains(t, providerUrl.GetParams(), ".c")
+ assert.NotContains(t, providerUrl.GetParams(), ".d")
+ assert.Contains(t, providerUrl.GetParams(), "a")
+
+}
diff --git a/registry/service_discovery.go b/registry/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8228a4abe8ed07e3c5afda300702f778daea4ae
--- /dev/null
+++ b/registry/service_discovery.go
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package registry
+
+import (
+ "fmt"
+)
+
+import (
+ gxset "github.com/dubbogo/gost/container/set"
+ gxpage "github.com/dubbogo/gost/page"
+)
+
+const DefaultPageSize = 100
+
+type ServiceDiscovery interface {
+ fmt.Stringer
+
+ // ----------------- lifecycle -------------------
+
+ // Destroy will destroy the service discovery.
+ // If the discovery cannot be destroy, it will return an error.
+ Destroy() error
+
+ // ----------------- registration ----------------
+
+ // Register will register an instance of ServiceInstance to registry
+ Register(instance ServiceInstance) error
+
+ // Update will update the data of the instance in registry
+ Update(instance ServiceInstance) error
+
+ // Unregister will unregister this instance from registry
+ Unregister(instance ServiceInstance) error
+
+ // ----------------- discovery -------------------
+ // GetDefaultPageSize will return the default page size
+ GetDefaultPageSize() int
+
+ // GetServices will return the all service names.
+ GetServices() *gxset.HashSet
+
+ // GetInstances will return all service instances with serviceName
+ GetInstances(serviceName string) []ServiceInstance
+
+ // GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName
+ // the page will start at offset
+ GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager
+
+ // GetHealthyInstancesByPage will return a page containing instances of ServiceInstance.
+ // The param healthy indices that the instance should be healthy or not.
+ // The page will start at offset
+ GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager
+
+ // Batch get all instances by the specified service names
+ GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager
+
+ // ----------------- event ----------------------
+ // AddListener adds a new ServiceInstancesChangedListener
+ // see addServiceInstancesChangedListener in Java
+ AddListener(listener *ServiceInstancesChangedListener) error
+
+ // DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName
+ DispatchEventByServiceName(serviceName string) error
+
+ // DispatchEventForInstances dispatches the ServiceInstancesChangedEvent to target instances
+ DispatchEventForInstances(serviceName string, instances []ServiceInstance) error
+
+ // DispatchEvent dispatches the event
+ DispatchEvent(event *ServiceInstancesChangedEvent) error
+}
diff --git a/registry/service_instance.go b/registry/service_instance.go
new file mode 100644
index 0000000000000000000000000000000000000000..2cc229ee3b056da2d9f1a1b70d3e0f5858c9da5f
--- /dev/null
+++ b/registry/service_instance.go
@@ -0,0 +1,89 @@
+/*
+ * 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 registry
+
+type ServiceInstance interface {
+
+ // GetId will return this instance's id. It should be unique.
+ GetId() string
+
+ // GetServiceName will return the serviceName
+ GetServiceName() string
+
+ // GetHost will return the hostname
+ GetHost() string
+
+ // GetPort will return the port.
+ GetPort() int
+
+ // IsEnable will return the enable status of this instance
+ IsEnable() bool
+
+ // IsHealthy will return the value represent the instance whether healthy or not
+ IsHealthy() bool
+
+ // GetMetadata will return the metadata
+ GetMetadata() map[string]string
+}
+
+// DefaultServiceInstance the default implementation of ServiceInstance
+// or change the ServiceInstance to be struct???
+type DefaultServiceInstance struct {
+ Id string
+ ServiceName string
+ Host string
+ Port int
+ Enable bool
+ Healthy bool
+ Metadata map[string]string
+}
+
+// GetId will return this instance's id. It should be unique.
+func (d *DefaultServiceInstance) GetId() string {
+ return d.Id
+}
+
+// GetServiceName will return the serviceName
+func (d *DefaultServiceInstance) GetServiceName() string {
+ return d.ServiceName
+}
+
+// GetHost will return the hostname
+func (d *DefaultServiceInstance) GetHost() string {
+ return d.Host
+}
+
+// GetPort will return the port.
+func (d *DefaultServiceInstance) GetPort() int {
+ return d.Port
+}
+
+// IsEnable will return the enable status of this instance
+func (d *DefaultServiceInstance) IsEnable() bool {
+ return d.Enable
+}
+
+// IsHealthy will return the value represent the instance whether healthy or not
+func (d *DefaultServiceInstance) IsHealthy() bool {
+ return d.Healthy
+}
+
+// GetMetadata will return the metadata
+func (d *DefaultServiceInstance) GetMetadata() map[string]string {
+ return d.Metadata
+}
diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go
index bef1760e04fd6597721fd19b5d19820f45ed2bf0..c5b2f33c6107e82aa172c818c0d8aca1483248c6 100644
--- a/registry/zookeeper/listener.go
+++ b/registry/zookeeper/listener.go
@@ -35,23 +35,28 @@ import (
zk "github.com/apache/dubbo-go/remoting/zookeeper"
)
-// RegistryDataListener ...
+// RegistryDataListener contains all URL information subscribed by zookeeper registry
type RegistryDataListener struct {
- interestedURL []*common.URL
- listener config_center.ConfigurationListener
+ subscribed map[*common.URL]config_center.ConfigurationListener
+ mutex sync.Mutex
+ closed bool
}
-// NewRegistryDataListener ...
-func NewRegistryDataListener(listener config_center.ConfigurationListener) *RegistryDataListener {
- return &RegistryDataListener{listener: listener}
+// NewRegistryDataListener constructs a new RegistryDataListener
+func NewRegistryDataListener() *RegistryDataListener {
+ return &RegistryDataListener{
+ subscribed: make(map[*common.URL]config_center.ConfigurationListener)}
}
-// AddInterestedURL ...
-func (l *RegistryDataListener) AddInterestedURL(url *common.URL) {
- l.interestedURL = append(l.interestedURL, url)
+// SubscribeURL is used to set a watch listener for url
+func (l *RegistryDataListener) SubscribeURL(url *common.URL, listener config_center.ConfigurationListener) {
+ if l.closed {
+ return
+ }
+ l.subscribed[url] = listener
}
-// DataChange ...
+// DataChange accepts all events sent from the zookeeper server and trigger the corresponding listener for processing
func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
// Intercept the last bit
index := strings.Index(eventType.Path, "/providers/")
@@ -65,10 +70,14 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
logger.Errorf("Listen NewURL(r{%s}) = error{%v} eventType.Path={%v}", url, err, eventType.Path)
return false
}
-
- for _, v := range l.interestedURL {
- if serviceURL.URLEqual(*v) {
- l.listener.Process(
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+ if l.closed {
+ return false
+ }
+ for url, listener := range l.subscribed {
+ if serviceURL.URLEqual(*url) {
+ listener.Process(
&config_center.ConfigChangeEvent{
Key: eventType.Path,
Value: serviceURL,
@@ -81,38 +90,48 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
return false
}
-// RegistryConfigurationListener ...
+// Close all RegistryConfigurationListener in subscribed
+func (l *RegistryDataListener) Close() {
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+ for _, listener := range l.subscribed {
+ listener.(*RegistryConfigurationListener).Close()
+ }
+}
+
+// RegistryConfigurationListener represent the processor of zookeeper watcher
type RegistryConfigurationListener struct {
client *zk.ZookeeperClient
registry *zkRegistry
events chan *config_center.ConfigChangeEvent
isClosed bool
+ close chan struct{}
closeOnce sync.Once
}
// NewRegistryConfigurationListener for listening the event of zk.
func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry) *RegistryConfigurationListener {
reg.WaitGroup().Add(1)
- return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32), isClosed: false}
+ return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32), isClosed: false, close: make(chan struct{}, 1)}
}
-// Process ...
+// Process submit the ConfigChangeEvent to the event chan to notify all observer
func (l *RegistryConfigurationListener) Process(configType *config_center.ConfigChangeEvent) {
l.events <- configType
}
-// Next ...
+// Next will observe the registry state and events chan
func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) {
for {
select {
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")
-
+ logger.Warnf("listener's zk client connection (address {%s}) is broken, so zk event listener exit now.", l.client.ZkAddrs)
+ return nil, perrors.New("zookeeper client stopped")
+ case <-l.close:
+ return nil, perrors.New("listener have been closed")
case <-l.registry.Done():
- logger.Warnf("zk consumer register has quit, so zk event listener exit now.")
- return nil, perrors.New("listener stopped")
-
+ logger.Warnf("zk consumer register has quit, so zk event listener exit now. (registry url {%v}", l.registry.BaseRegistry.URL)
+ return nil, perrors.New("zookeeper registry, (registry url{%v}) stopped")
case e := <-l.events:
logger.Debugf("got zk event %s", e)
if e.ConfigType == remoting.EventTypeDel && !l.valid() {
@@ -127,15 +146,17 @@ func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) {
}
}
-// Close ...
+// Close RegistryConfigurationListener only once
func (l *RegistryConfigurationListener) Close() {
// ensure that the listener will be closed at most once.
l.closeOnce.Do(func() {
l.isClosed = true
+ l.close <- struct{}{}
l.registry.WaitGroup().Done()
})
}
+// valid return the true if the client conn isn't nil
func (l *RegistryConfigurationListener) valid() bool {
return l.client.ZkConnValid()
}
diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go
index 1a76b29a6f64e0329b289ce50218032a25f6f5cd..a0e9147a9e0ee8767efcf78d5e2aa536140f6a8b 100644
--- a/registry/zookeeper/listener_test.go
+++ b/registry/zookeeper/listener_test.go
@@ -32,15 +32,15 @@ import (
)
func Test_DataChange(t *testing.T) {
- listener := NewRegistryDataListener(&MockDataListener{})
+ listener := NewRegistryDataListener()
url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100")
- listener.AddInterestedURL(&url)
+ listener.SubscribeURL(&url, &MockConfigurationListener{})
int := listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"})
assert.Equal(t, true, int)
}
-type MockDataListener struct {
+type MockConfigurationListener struct {
}
-func (*MockDataListener) Process(configType *config_center.ConfigChangeEvent) {
+func (*MockConfigurationListener) Process(configType *config_center.ConfigChangeEvent) {
}
diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go
index 4fd58e9e4d9ecd285675fc416f3d7a36bf19fd54..88d5d6221b4bc7136ba4c3e7c95fb53ba35a9a58 100644
--- a/registry/zookeeper/registry.go
+++ b/registry/zookeeper/registry.go
@@ -53,12 +53,11 @@ func init() {
type zkRegistry struct {
registry.BaseRegistry
- client *zookeeper.ZookeeperClient
- listenerLock sync.Mutex
- listener *zookeeper.ZkEventListener
- dataListener *RegistryDataListener
- configListener *RegistryConfigurationListener
- cltLock sync.Mutex
+ client *zookeeper.ZookeeperClient
+ listenerLock sync.Mutex
+ listener *zookeeper.ZkEventListener
+ dataListener *RegistryDataListener
+ cltLock sync.Mutex
//for provider
zkPath map[string]int // key = protocol://ip:port/interface
}
@@ -77,13 +76,12 @@ func newZkRegistry(url *common.URL) (registry.Registry, error) {
if err != nil {
return nil, err
}
- r.WaitGroup().Add(1) //zk client start successful, then wg +1
go zookeeper.HandleClientRestart(r)
r.listener = zookeeper.NewZkEventListener(r.client)
- r.configListener = NewRegistryConfigurationListener(r.client, r)
- r.dataListener = NewRegistryDataListener(r.configListener)
+
+ r.dataListener = NewRegistryDataListener()
return r, nil
}
@@ -120,8 +118,27 @@ func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestClust
func (r *zkRegistry) InitListeners() {
r.listener = zookeeper.NewZkEventListener(r.client)
- r.configListener = NewRegistryConfigurationListener(r.client, r)
- r.dataListener = NewRegistryDataListener(r.configListener)
+ newDataListener := NewRegistryDataListener()
+ // should recover if dataListener isn't nil before
+ if r.dataListener != nil {
+ // close all old listener
+ oldDataListener := r.dataListener
+ oldDataListener.mutex.Lock()
+ defer oldDataListener.mutex.Unlock()
+ recoverd := r.dataListener.subscribed
+ if recoverd != nil && len(recoverd) > 0 {
+ // recover all subscribed url
+ for conf, oldListener := range recoverd {
+ if regConfigListener, ok := oldListener.(*RegistryConfigurationListener); ok {
+ regConfigListener.Close()
+ }
+ newDataListener.SubscribeURL(conf, NewRegistryConfigurationListener(r.client, r))
+ go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), newDataListener)
+
+ }
+ }
+ }
+ r.dataListener = newDataListener
}
func (r *zkRegistry) CreatePath(path string) error {
@@ -154,8 +171,8 @@ func (r *zkRegistry) ZkClientLock() *sync.Mutex {
}
func (r *zkRegistry) CloseListener() {
- if r.configListener != nil {
- r.configListener.Close()
+ if r.dataListener != nil {
+ r.dataListener.Close()
}
}
@@ -172,32 +189,49 @@ func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error {
logger.Errorf("zk.Create(root{%s}) = err{%v}", root, perrors.WithStack(err))
return perrors.WithStack(err)
}
+
+ // try to register the node
zkPath, err = r.client.RegisterTemp(root, node)
if err != nil {
- if err == zk.ErrNodeExists {
- logger.Warnf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
- } else {
- logger.Errorf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+ logger.Errorf("Register temp node(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+ if perrors.Cause(err) == zk.ErrNodeExists {
+ // should delete the old node
+ logger.Info("Register temp node failed, try to delete the old and recreate (root{%s}, node{%s}) , ignore!", root, node)
+ if err = r.client.Delete(zkPath); err == nil {
+ _, err = r.client.RegisterTemp(root, node)
+ }
+ if err != nil {
+ logger.Errorf("Recreate the temp node failed, (root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+ }
}
return perrors.WithMessagef(err, "RegisterTempNode(root{%s}, node{%s})", root, node)
}
- logger.Debugf("create a zookeeper node:%s", zkPath)
+ logger.Debugf("Create a zookeeper node:%s", zkPath)
return nil
}
func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListener, error) {
- var (
- zkListener *RegistryConfigurationListener
- )
- r.listenerLock.Lock()
- if r.configListener.isClosed {
- r.listenerLock.Unlock()
- return nil, perrors.New("configListener already been closed")
+ var zkListener *RegistryConfigurationListener
+ dataListener := r.dataListener
+ dataListener.mutex.Lock()
+ defer dataListener.mutex.Unlock()
+ if r.dataListener.subscribed[conf] != nil {
+
+ zkListener, _ := r.dataListener.subscribed[conf].(*RegistryConfigurationListener)
+ if zkListener != nil {
+ r.listenerLock.Lock()
+ defer r.listenerLock.Unlock()
+ if zkListener.isClosed {
+ return nil, perrors.New("configListener already been closed")
+ } else {
+ return zkListener, nil
+ }
+ }
}
- zkListener = r.configListener
- r.listenerLock.Unlock()
+
+ zkListener = NewRegistryConfigurationListener(r.client, r)
if r.listener == nil {
r.cltLock.Lock()
client := r.client
@@ -215,8 +249,9 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen
}
//Interested register to dataconfig.
- r.dataListener.AddInterestedURL(conf)
- go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), r.dataListener)
+ r.dataListener.SubscribeURL(conf, zkListener)
+
+ go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), r.dataListener)
return zkListener, nil
}
diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go
index 21486aab59c3f9b44c25b68d7433f864a990149a..bd1da547766abb12dc742234787262212e3db314 100644
--- a/remoting/zookeeper/client.go
+++ b/remoting/zookeeper/client.go
@@ -118,7 +118,7 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
for _, opt := range opts {
opt(opions)
}
-
+ connected := false
err = nil
lock := container.ZkClientLock()
@@ -143,6 +143,7 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location)
}
container.SetZkClient(newClient)
+ connected = true
}
if container.ZkClient().Conn == nil {
@@ -150,10 +151,16 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
container.ZkClient().Conn, event, err = zk.Connect(container.ZkClient().ZkAddrs, container.ZkClient().Timeout)
if err == nil {
container.ZkClient().Wait.Add(1)
+ connected = true
go container.ZkClient().HandleZkEvent(event)
}
}
+ if connected {
+ logger.Info("Connect to zookeeper successfully, name{%s}, zk address{%v}", opions.zkName, url.Location)
+ container.WaitGroup().Add(1) //zk client start successful, then registry wg +1
+ }
+
return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.PrimitiveURL)
}
@@ -386,14 +393,23 @@ func (z *ZookeeperClient) Close() {
z.Conn = nil
z.Unlock()
if conn != nil {
+ logger.Warnf("zkClient Conn{name:%s, zk addr:%s} exit now.", z.name, conn.SessionID())
conn.Close()
}
logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs)
}
-// Create ...
+// Create will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first.
+// And the value for the basePath is ""
func (z *ZookeeperClient) Create(basePath string) error {
+ return z.CreateWithValue(basePath, []byte(""))
+}
+
+// CreateWithValue will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first.
+func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error {
var (
err error
tmpPath string
@@ -407,7 +423,7 @@ func (z *ZookeeperClient) Create(basePath string) error {
conn := z.Conn
z.Unlock()
if conn != nil {
- _, err = conn.Create(tmpPath, []byte(""), 0, zk.WorldACL(zk.PermAll))
+ _, err = conn.Create(tmpPath, value, 0, zk.WorldACL(zk.PermAll))
}
if err != nil {
@@ -462,7 +478,7 @@ func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, er
//if err != nil && err != zk.ErrNodeExists {
if err != nil {
logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err))
- return "", perrors.WithStack(err)
+ return zkPath, perrors.WithStack(err)
}
logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath)
diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go
index 055db4f716a914354d1bada653fbc0a850b615b5..4e3945388ff402f60a02150615a8914f9cba2435 100644
--- a/remoting/zookeeper/facade.go
+++ b/remoting/zookeeper/facade.go
@@ -48,11 +48,11 @@ func HandleClientRestart(r zkClientFacade) {
failTimes int
)
- defer r.WaitGroup().Done()
LOOP:
for {
select {
case <-r.Done():
+ r.WaitGroup().Done() // dec the wg when registry is closed
logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...")
break LOOP
// re-register all services
@@ -63,12 +63,14 @@ LOOP:
zkAddress := r.ZkClient().ZkAddrs
r.SetZkClient(nil)
r.ZkClientLock().Unlock()
+ r.WaitGroup().Done() // dec the wg when zk client is closed
// Connect zk until success.
failTimes = 0
for {
select {
case <-r.Done():
+ r.WaitGroup().Done() // dec the wg when registry is closed
logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...")
break LOOP
case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): // Prevent crazy reconnection zk.
diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go
index a41f6cd3230700332519ce1c2d3489bfcc4b6ef0..01d46da6cc1abae90210a323d32ac84bad80249b 100644
--- a/remoting/zookeeper/facade_test.go
+++ b/remoting/zookeeper/facade_test.go
@@ -38,6 +38,16 @@ type mockFacade struct {
done chan struct{}
}
+func newMockFacade(client *ZookeeperClient, url *common.URL) zkClientFacade {
+ mock := &mockFacade{
+ client: client,
+ URL: url,
+ }
+
+ mock.wg.Add(1)
+ return mock
+}
+
func (r *mockFacade) ZkClient() *ZookeeperClient {
return r.client
}
@@ -80,7 +90,7 @@ func Test_Facade(t *testing.T) {
assert.NoError(t, err)
defer ts.Stop()
url, _ := common.NewURL("mock://127.0.0.1")
- mock := &mockFacade{client: z, URL: &url}
+ mock := newMockFacade(z, &url)
go HandleClientRestart(mock)
states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession}
verifyEventStateOrder(t, event, states, "event channel")
diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go
index eaf259f4417201c95172e95d7a87476575e004d5..84877667763ce870e76202844e9dc9dc1c3f008c 100644
--- a/remoting/zookeeper/listener.go
+++ b/remoting/zookeeper/listener.go
@@ -18,6 +18,7 @@
package zookeeper
import (
+ "github.com/apache/dubbo-go/common"
"path"
"strings"
"sync"
@@ -173,7 +174,7 @@ func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, li
}
}
-func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataListener) {
+func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listener remoting.DataListener) {
defer l.wg.Done()
var (
@@ -224,7 +225,16 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi
}
failTimes = 0
for _, c := range children {
- // listen l service node
+
+ // Only need to compare Path when subscribing to provider
+ if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) != -1 {
+ provider, _ := common.NewURL(c)
+ if provider.Path != conf.Path {
+ continue
+ }
+ }
+
+ //listen l service node
dubboPath := path.Join(zkPath, c)
//Save the path to avoid listen repeatedly
@@ -232,7 +242,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi
_, ok := l.pathMap[dubboPath]
l.pathMapLock.Unlock()
if ok {
- logger.Warnf("@zkPath %s has already been listened.", zkPath)
+ logger.Warnf("@zkPath %s has already been listened.", dubboPath)
continue
}
@@ -263,7 +273,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi
strings.LastIndex(zkPath, constant.CONSUMER_CATEGORY) == -1 {
l.wg.Add(1)
go func(zkPath string, listener remoting.DataListener) {
- l.listenDirEvent(zkPath, listener)
+ l.listenDirEvent(conf, zkPath, listener)
logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath)
}(dubboPath, listener)
}
@@ -291,11 +301,11 @@ func timeSecondDuration(sec int) time.Duration {
// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent
// |
// --------> ListenServiceNodeEvent
-func (l *ZkEventListener) ListenServiceEvent(zkPath string, listener remoting.DataListener) {
+func (l *ZkEventListener) ListenServiceEvent(conf *common.URL, zkPath string, listener remoting.DataListener) {
logger.Infof("listen dubbo path{%s}", zkPath)
l.wg.Add(1)
go func(zkPath string, listener remoting.DataListener) {
- l.listenDirEvent(zkPath, listener)
+ l.listenDirEvent(conf, zkPath, listener)
logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath)
}(zkPath, listener)
}
diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go
index 7301cd52c392b6950b3a49f78e8124eae532b083..ba7d6ba81b6af97dc5ad3788e8399d08cbe5b2bb 100644
--- a/remoting/zookeeper/listener_test.go
+++ b/remoting/zookeeper/listener_test.go
@@ -97,7 +97,7 @@ func TestListener(t *testing.T) {
go client.HandleZkEvent(event)
listener := NewZkEventListener(client)
dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait}
- listener.ListenServiceEvent("/dubbo", dataListener)
+ listener.ListenServiceEvent(nil, "/dubbo", dataListener)
time.Sleep(1 * time.Second)
_, err := client.Conn.Set("/dubbo/dubbo.properties", []byte(changedData), 1)
assert.NoError(t, err)