You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by GitBox <gi...@apache.org> on 2020/08/31 15:03:45 UTC

[GitHub] [dubbo-go] DogBaoBao opened a new pull request #732: Ftr: File system service discovery

DogBaoBao opened a new pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732


   <!--  Thanks for sending a pull request! 
   -->
   
   **What this PR does**:
   The implementation of ServiceDiscovery based on file system.
   
   **Which issue(s) this PR fixes**:
   <!--
   *Automatically closes linked issue when PR is merged.
   Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
   _If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
   -->
   Fixes #426
   
   **Special notes for your reviewer**:
   
   **Does this PR introduce a user-facing change?**:
   <!--
   If no, just write "NONE" in the release-note block below.
   If yes, a release note is required:
   Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
   -->
   ```release-note
   
   ```


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] zouyx commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
zouyx commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r484622036



##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,155 @@
+/*
+ * 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 file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+// CacheListener is file watcher
+type CacheListener struct {
+	watch        *fsnotify.Watcher
+	keyListeners sync.Map
+	rootPath     string
+}
+
+// NewCacheListener creates a new CacheListener
+func NewCacheListener(rootPath string) *CacheListener {
+	cl := &CacheListener{rootPath: rootPath}
+	// start watcher
+	watch, err := fsnotify.NewWatcher()
+	if err != nil {
+		logger.Errorf("file : listen config fail, error:%v ", err)
+	}
+	go func() {
+		for {
+			select {
+			case event := <-watch.Events:
+				key := event.Name
+				logger.Debugf("watcher %s, event %v", cl.rootPath, event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeUpdate)
+					}
+				}
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeAdd)
+					}
+				}
+				if event.Op&fsnotify.Remove == fsnotify.Remove {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel)
+					}
+				}
+			case err := <-watch.Errors:
+				// err may be nil, ignore
+				if err != nil {
+					logger.Warnf("file : listen watch fail:%+v", err)
+				}
+			}
+		}
+	}()
+	cl.watch = watch
+
+	extension.AddCustomShutdownCallback(func() {
+		cl.watch.Close()
+	})
+
+	return cl
+}
+
+func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)

Review comment:
       ```suggestion
   		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
   		return
   ```
   
   Should return directly here

##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,155 @@
+/*
+ * 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 file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+// CacheListener is file watcher
+type CacheListener struct {
+	watch        *fsnotify.Watcher
+	keyListeners sync.Map
+	rootPath     string
+}
+
+// NewCacheListener creates a new CacheListener
+func NewCacheListener(rootPath string) *CacheListener {
+	cl := &CacheListener{rootPath: rootPath}
+	// start watcher
+	watch, err := fsnotify.NewWatcher()
+	if err != nil {
+		logger.Errorf("file : listen config fail, error:%v ", err)
+	}
+	go func() {
+		for {
+			select {
+			case event := <-watch.Events:
+				key := event.Name
+				logger.Debugf("watcher %s, event %v", cl.rootPath, event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeUpdate)
+					}
+				}
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeAdd)
+					}
+				}
+				if event.Op&fsnotify.Remove == fsnotify.Remove {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel)
+					}
+				}
+			case err := <-watch.Errors:
+				// err may be nil, ignore
+				if err != nil {
+					logger.Warnf("file : listen watch fail:%+v", err)
+				}
+			}
+		}
+	}()
+	cl.watch = watch
+
+	extension.AddCustomShutdownCallback(func() {
+		cl.watch.Close()
+	})
+
+	return cl
+}
+
+func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	for l := range lmap {
+		callback(l, key, "", event)
+	}
+}
+
+func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)

Review comment:
       As above?

##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,155 @@
+/*
+ * 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 file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+// CacheListener is file watcher
+type CacheListener struct {
+	watch        *fsnotify.Watcher
+	keyListeners sync.Map
+	rootPath     string
+}
+
+// NewCacheListener creates a new CacheListener
+func NewCacheListener(rootPath string) *CacheListener {
+	cl := &CacheListener{rootPath: rootPath}
+	// start watcher
+	watch, err := fsnotify.NewWatcher()
+	if err != nil {
+		logger.Errorf("file : listen config fail, error:%v ", err)
+	}
+	go func() {
+		for {
+			select {
+			case event := <-watch.Events:
+				key := event.Name
+				logger.Debugf("watcher %s, event %v", cl.rootPath, event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeUpdate)
+					}
+				}
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeAdd)
+					}
+				}
+				if event.Op&fsnotify.Remove == fsnotify.Remove {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel)
+					}
+				}
+			case err := <-watch.Errors:
+				// err may be nil, ignore
+				if err != nil {
+					logger.Warnf("file : listen watch fail:%+v", err)
+				}
+			}
+		}
+	}()
+	cl.watch = watch
+
+	extension.AddCustomShutdownCallback(func() {
+		cl.watch.Close()
+	})
+
+	return cl
+}
+
+func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	for l := range lmap {
+		callback(l, key, "", event)
+	}
+}
+
+func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	c := getFileContent(key)
+	for l := range lmap {
+		callback(l, key, c, event)
+	}
+}
+
+func callback(listener config_center.ConfigurationListener, path, data string, event remoting.EventType) {
+	listener.Process(&config_center.ConfigChangeEvent{Key: path, Value: data, ConfigType: event})
+}
+
+// Close will remove key listener and close watcher
+func (cl *CacheListener) Close() error {
+	cl.keyListeners.Range(func(key, value interface{}) bool {
+		cl.keyListeners.Delete(key)
+		return true
+	})
+	return cl.watch.Close()
+}
+
+// AddListener will add a listener if loaded
+// if you watcher a file or directory not exist, will error with no such file or directory
+func (cl *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) {
+	// reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure
+	// make a map[your type]struct{} like set in java
+	listeners, loaded := cl.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{listener: {}})
+	if loaded {
+		listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{}
+		cl.keyListeners.Store(key, listeners)
+	} else {
+		if err := cl.watch.Add(key); err != nil {
+			logger.Errorf("watcher add path:%s err:%v", key, err)
+		}
+	}
+}
+
+// RemoveListener will delete a listener if loaded
+func (cl *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) {
+	listeners, loaded := cl.keyListeners.Load(key)
+	if loaded {
+		delete(listeners.(map[config_center.ConfigurationListener]struct{}), listener)
+		if err := cl.watch.Remove(key); err != nil {
+			logger.Errorf("watcher remove path:%s err:%v", key, err)
+		}
+	}

Review comment:
       ```suggestion
   	if !loaded {
   		return
   	}
   	delete(listeners.(map[config_center.ConfigurationListener]struct{}), listener)
   		if err := cl.watch.Remove(key); err != nil {
   			logger.Errorf("watcher remove path:%s err:%v", key, err)
   		}
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")
+		url, _ := common.NewURL("")
+		url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p)
+		if c, err := fdcf.GetDynamicConfiguration(&url); err != nil {
+			return nil, perrors.New("could not find the config for name: " + name)
+		} else {
+			sd := &fileSystemServiceDiscovery{
+				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
+				rootPath:             p,
+				fileMap:              make(map[string]string),
+			}
+
+			extension.AddCustomShutdownCallback(func() {
+				sd.Destroy()
+			})
+
+			for _, v := range sd.GetServices().Values() {
+				for _, i := range sd.GetInstances(v.(string)) {
+					// like java do nothing
+					l := &RegistryConfigurationListener{}
+					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
+				}
+			}
+
+			return sd, nil
+		}
+	}
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) String() string {
+	return fmt.Sprintf("file-system-service-discovery")
+}
+
+// Destroy will destroy the service discovery.
+// If the discovery cannot be destroy, it will return an error.
+func (fssd *fileSystemServiceDiscovery) Destroy() error {
+	fssd.dynamicConfiguration.Close()
+
+	for _, f := range fssd.fileMap {
+		fssd.releaseAndRemoveRegistrationFiles(f)
+	}
+
+	return nil
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) releaseAndRemoveRegistrationFiles(file string) {
+	os.RemoveAll(file)
+}
+
+// ----------------- registration ----------------
+
+// Register will register an instance of ServiceInstance to registry
+func (fssd *fileSystemServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if c, err := getContent(instance); err != nil {
+		return err
+	} else {
+		if err := fssd.dynamicConfiguration.PublishConfig(id, sn, c); err != nil {
+			return perrors.WithStack(err)
+		} else {
+			fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn)
+		}
+	}

Review comment:
       ```suggestion
   	} 
   	if err := fssd.dynamicConfiguration.PublishConfig(id, sn, c); err != nil {
   		return perrors.WithStack(err)
   	}
   	fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn)
   		
   ```

##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,155 @@
+/*
+ * 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 file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+// CacheListener is file watcher
+type CacheListener struct {
+	watch        *fsnotify.Watcher
+	keyListeners sync.Map
+	rootPath     string
+}
+
+// NewCacheListener creates a new CacheListener
+func NewCacheListener(rootPath string) *CacheListener {
+	cl := &CacheListener{rootPath: rootPath}
+	// start watcher
+	watch, err := fsnotify.NewWatcher()
+	if err != nil {
+		logger.Errorf("file : listen config fail, error:%v ", err)
+	}
+	go func() {
+		for {
+			select {
+			case event := <-watch.Events:
+				key := event.Name
+				logger.Debugf("watcher %s, event %v", cl.rootPath, event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeUpdate)
+					}
+				}
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeAdd)
+					}
+				}
+				if event.Op&fsnotify.Remove == fsnotify.Remove {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel)
+					}
+				}
+			case err := <-watch.Errors:
+				// err may be nil, ignore
+				if err != nil {
+					logger.Warnf("file : listen watch fail:%+v", err)
+				}
+			}
+		}
+	}()
+	cl.watch = watch
+
+	extension.AddCustomShutdownCallback(func() {
+		cl.watch.Close()
+	})
+
+	return cl
+}
+
+func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	for l := range lmap {
+		callback(l, key, "", event)
+	}
+}
+
+func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	c := getFileContent(key)
+	for l := range lmap {
+		callback(l, key, c, event)
+	}
+}
+
+func callback(listener config_center.ConfigurationListener, path, data string, event remoting.EventType) {
+	listener.Process(&config_center.ConfigChangeEvent{Key: path, Value: data, ConfigType: event})
+}
+
+// Close will remove key listener and close watcher
+func (cl *CacheListener) Close() error {
+	cl.keyListeners.Range(func(key, value interface{}) bool {
+		cl.keyListeners.Delete(key)
+		return true
+	})
+	return cl.watch.Close()
+}
+
+// AddListener will add a listener if loaded
+// if you watcher a file or directory not exist, will error with no such file or directory
+func (cl *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) {
+	// reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure
+	// make a map[your type]struct{} like set in java
+	listeners, loaded := cl.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{listener: {}})
+	if loaded {
+		listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{}
+		cl.keyListeners.Store(key, listeners)
+	} else {
+		if err := cl.watch.Add(key); err != nil {
+			logger.Errorf("watcher add path:%s err:%v", key, err)
+		}
+	}

Review comment:
       ```suggestion
   	if loaded {
   		listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{}
   		cl.keyListeners.Store(key, listeners)
   		return
   	} 
   		if err := cl.watch.Add(key); err != nil {
   			logger.Errorf("watcher add path:%s err:%v", key, err)
   		}
   	
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")

Review comment:
       ```suggestion
   		p := path.Join(rp, ".dubbo",constant.REGISTRY_KEY)
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")
+		url, _ := common.NewURL("")
+		url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p)
+		if c, err := fdcf.GetDynamicConfiguration(&url); err != nil {
+			return nil, perrors.New("could not find the config for name: " + name)
+		} else {
+			sd := &fileSystemServiceDiscovery{
+				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
+				rootPath:             p,
+				fileMap:              make(map[string]string),
+			}
+
+			extension.AddCustomShutdownCallback(func() {
+				sd.Destroy()
+			})
+
+			for _, v := range sd.GetServices().Values() {
+				for _, i := range sd.GetInstances(v.(string)) {
+					// like java do nothing
+					l := &RegistryConfigurationListener{}
+					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
+				}
+			}
+
+			return sd, nil
+		}

Review comment:
       ```suggestion
   		} 
   			sd := &fileSystemServiceDiscovery{
   				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
   				rootPath:             p,
   				fileMap:              make(map[string]string),
   			}
   
   			extension.AddCustomShutdownCallback(func() {
   				sd.Destroy()
   			})
   
   			for _, v := range sd.GetServices().Values() {
   				for _, i := range sd.GetInstances(v.(string)) {
   					// like java do nothing
   					l := &RegistryConfigurationListener{}
   					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
   				}
   			}
   
   			return sd, nil
   		
   ```

##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,155 @@
+/*
+ * 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 file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+// CacheListener is file watcher
+type CacheListener struct {
+	watch        *fsnotify.Watcher
+	keyListeners sync.Map
+	rootPath     string
+}
+
+// NewCacheListener creates a new CacheListener
+func NewCacheListener(rootPath string) *CacheListener {
+	cl := &CacheListener{rootPath: rootPath}
+	// start watcher
+	watch, err := fsnotify.NewWatcher()
+	if err != nil {
+		logger.Errorf("file : listen config fail, error:%v ", err)
+	}
+	go func() {
+		for {
+			select {
+			case event := <-watch.Events:
+				key := event.Name
+				logger.Debugf("watcher %s, event %v", cl.rootPath, event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeUpdate)
+					}
+				}
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeAdd)
+					}
+				}
+				if event.Op&fsnotify.Remove == fsnotify.Remove {
+					if l, ok := cl.keyListeners.Load(key); ok {
+						removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel)
+					}
+				}
+			case err := <-watch.Errors:
+				// err may be nil, ignore
+				if err != nil {
+					logger.Warnf("file : listen watch fail:%+v", err)
+				}
+			}
+		}
+	}()
+	cl.watch = watch
+
+	extension.AddCustomShutdownCallback(func() {
+		cl.watch.Close()
+	})
+
+	return cl
+}
+
+func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	for l := range lmap {
+		callback(l, key, "", event)
+	}
+}
+
+func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) {
+	if len(lmap) == 0 {
+		logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event)
+	}
+	c := getFileContent(key)
+	for l := range lmap {
+		callback(l, key, c, event)
+	}
+}
+
+func callback(listener config_center.ConfigurationListener, path, data string, event remoting.EventType) {
+	listener.Process(&config_center.ConfigChangeEvent{Key: path, Value: data, ConfigType: event})
+}
+
+// Close will remove key listener and close watcher
+func (cl *CacheListener) Close() error {
+	cl.keyListeners.Range(func(key, value interface{}) bool {
+		cl.keyListeners.Delete(key)
+		return true
+	})
+	return cl.watch.Close()
+}
+
+// AddListener will add a listener if loaded
+// if you watcher a file or directory not exist, will error with no such file or directory
+func (cl *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) {
+	// reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure
+	// make a map[your type]struct{} like set in java
+	listeners, loaded := cl.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{listener: {}})
+	if loaded {
+		listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{}
+		cl.keyListeners.Store(key, listeners)
+	} else {
+		if err := cl.watch.Add(key); err != nil {
+			logger.Errorf("watcher add path:%s err:%v", key, err)
+		}
+	}
+}
+
+// RemoveListener will delete a listener if loaded
+func (cl *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) {
+	listeners, loaded := cl.keyListeners.Load(key)
+	if loaded {
+		delete(listeners.(map[config_center.ConfigurationListener]struct{}), listener)
+		if err := cl.watch.Remove(key); err != nil {
+			logger.Errorf("watcher remove path:%s err:%v", key, err)
+		}
+	}
+}
+
+func getFileContent(path string) string {
+	if c, err := ioutil.ReadFile(path); err != nil {
+		logger.Errorf("read file path:%s err:%v", path, err)
+		return ""
+	} else {
+		return string(c)
+	}

Review comment:
       ```suggestion
   	} 
   	return string(c)
   	
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")
+		url, _ := common.NewURL("")
+		url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p)
+		if c, err := fdcf.GetDynamicConfiguration(&url); err != nil {
+			return nil, perrors.New("could not find the config for name: " + name)
+		} else {
+			sd := &fileSystemServiceDiscovery{
+				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
+				rootPath:             p,
+				fileMap:              make(map[string]string),
+			}
+
+			extension.AddCustomShutdownCallback(func() {
+				sd.Destroy()
+			})
+
+			for _, v := range sd.GetServices().Values() {
+				for _, i := range sd.GetInstances(v.(string)) {
+					// like java do nothing
+					l := &RegistryConfigurationListener{}
+					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
+				}
+			}
+
+			return sd, nil
+		}
+	}
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) String() string {
+	return fmt.Sprintf("file-system-service-discovery")
+}
+
+// Destroy will destroy the service discovery.
+// If the discovery cannot be destroy, it will return an error.
+func (fssd *fileSystemServiceDiscovery) Destroy() error {
+	fssd.dynamicConfiguration.Close()
+
+	for _, f := range fssd.fileMap {
+		fssd.releaseAndRemoveRegistrationFiles(f)
+	}
+
+	return nil
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) releaseAndRemoveRegistrationFiles(file string) {
+	os.RemoveAll(file)
+}
+
+// ----------------- registration ----------------
+
+// Register will register an instance of ServiceInstance to registry
+func (fssd *fileSystemServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if c, err := getContent(instance); err != nil {
+		return err
+	} else {
+		if err := fssd.dynamicConfiguration.PublishConfig(id, sn, c); err != nil {
+			return perrors.WithStack(err)
+		} else {
+			fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn)
+		}
+	}
+
+	return nil
+}
+
+// nolint
+func getServiceInstanceId(si registry.ServiceInstance) string {
+	if si.GetId() == "" {
+		return si.GetHost() + "." + strconv.Itoa(si.GetPort())
+	}
+
+	return si.GetId()
+}
+
+// nolint
+func getServiceName(si registry.ServiceInstance) string {
+	return si.GetServiceName()
+}
+
+// getContent json string
+func getContent(si registry.ServiceInstance) (string, error) {
+	if bytes, err := json.Marshal(si); err != nil {
+		return "", err
+	} else {
+		return string(bytes), nil
+	}

Review comment:
       ```suggestion
   	} 
   	return string(bytes), nil
   	
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")
+		url, _ := common.NewURL("")
+		url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p)
+		if c, err := fdcf.GetDynamicConfiguration(&url); err != nil {
+			return nil, perrors.New("could not find the config for name: " + name)
+		} else {
+			sd := &fileSystemServiceDiscovery{
+				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
+				rootPath:             p,
+				fileMap:              make(map[string]string),
+			}
+
+			extension.AddCustomShutdownCallback(func() {
+				sd.Destroy()
+			})
+
+			for _, v := range sd.GetServices().Values() {
+				for _, i := range sd.GetInstances(v.(string)) {
+					// like java do nothing
+					l := &RegistryConfigurationListener{}
+					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
+				}
+			}
+
+			return sd, nil
+		}
+	}
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) String() string {
+	return fmt.Sprintf("file-system-service-discovery")
+}
+
+// Destroy will destroy the service discovery.
+// If the discovery cannot be destroy, it will return an error.
+func (fssd *fileSystemServiceDiscovery) Destroy() error {
+	fssd.dynamicConfiguration.Close()
+
+	for _, f := range fssd.fileMap {
+		fssd.releaseAndRemoveRegistrationFiles(f)
+	}
+
+	return nil
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) releaseAndRemoveRegistrationFiles(file string) {
+	os.RemoveAll(file)
+}
+
+// ----------------- registration ----------------
+
+// Register will register an instance of ServiceInstance to registry
+func (fssd *fileSystemServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if c, err := getContent(instance); err != nil {
+		return err
+	} else {
+		if err := fssd.dynamicConfiguration.PublishConfig(id, sn, c); err != nil {
+			return perrors.WithStack(err)
+		} else {
+			fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn)
+		}
+	}
+
+	return nil
+}
+
+// nolint
+func getServiceInstanceId(si registry.ServiceInstance) string {
+	if si.GetId() == "" {
+		return si.GetHost() + "." + strconv.Itoa(si.GetPort())
+	}
+
+	return si.GetId()
+}
+
+// nolint
+func getServiceName(si registry.ServiceInstance) string {
+	return si.GetServiceName()
+}
+
+// getContent json string
+func getContent(si registry.ServiceInstance) (string, error) {
+	if bytes, err := json.Marshal(si); err != nil {
+		return "", err
+	} else {
+		return string(bytes), nil
+	}
+}
+
+// Update will update the data of the instance in registry
+func (fssd *fileSystemServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	return fssd.Register(instance)
+}
+
+// Unregister will unregister this instance from registry
+func (fssd *fileSystemServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if err := fssd.dynamicConfiguration.RemoveConfig(id, sn); err != nil {
+		return err
+	} else {
+		delete(fssd.fileMap, instance.GetId())
+		return nil
+	}
+}
+
+// ----------------- discovery -------------------
+// GetDefaultPageSize will return the default page size
+func (fssd *fileSystemServiceDiscovery) GetDefaultPageSize() int {
+	return 100
+}
+
+// GetServices will return the all service names.
+func (fssd *fileSystemServiceDiscovery) GetServices() *gxset.HashSet {
+	r := gxset.NewSet()
+	// dynamicConfiguration root path is the actual root path
+	fileInfo, _ := ioutil.ReadDir(fssd.dynamicConfiguration.RootPath())
+
+	for _, file := range fileInfo {
+		if file.IsDir() {
+			r.Add(file.Name())
+		}
+	}
+
+	return r
+}
+
+// GetInstances will return all service instances with serviceName
+func (fssd *fileSystemServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+	if set, err := fssd.dynamicConfiguration.GetConfigKeysByGroup(serviceName); err != nil {
+		logger.Errorf("[FileServiceDiscovery] Could not query the instances for service{%s}, error = err{%v} ",
+			serviceName, err)
+
+		return make([]registry.ServiceInstance, 0, 0)
+	} else {

Review comment:
       remove else - -

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")
+		url, _ := common.NewURL("")
+		url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p)
+		if c, err := fdcf.GetDynamicConfiguration(&url); err != nil {
+			return nil, perrors.New("could not find the config for name: " + name)
+		} else {
+			sd := &fileSystemServiceDiscovery{
+				dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration),
+				rootPath:             p,
+				fileMap:              make(map[string]string),
+			}
+
+			extension.AddCustomShutdownCallback(func() {
+				sd.Destroy()
+			})
+
+			for _, v := range sd.GetServices().Values() {
+				for _, i := range sd.GetInstances(v.(string)) {
+					// like java do nothing
+					l := &RegistryConfigurationListener{}
+					sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i)))
+				}
+			}
+
+			return sd, nil
+		}
+	}
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) String() string {
+	return fmt.Sprintf("file-system-service-discovery")
+}
+
+// Destroy will destroy the service discovery.
+// If the discovery cannot be destroy, it will return an error.
+func (fssd *fileSystemServiceDiscovery) Destroy() error {
+	fssd.dynamicConfiguration.Close()
+
+	for _, f := range fssd.fileMap {
+		fssd.releaseAndRemoveRegistrationFiles(f)
+	}
+
+	return nil
+}
+
+// nolint
+func (fssd *fileSystemServiceDiscovery) releaseAndRemoveRegistrationFiles(file string) {
+	os.RemoveAll(file)
+}
+
+// ----------------- registration ----------------
+
+// Register will register an instance of ServiceInstance to registry
+func (fssd *fileSystemServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if c, err := getContent(instance); err != nil {
+		return err
+	} else {
+		if err := fssd.dynamicConfiguration.PublishConfig(id, sn, c); err != nil {
+			return perrors.WithStack(err)
+		} else {
+			fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn)
+		}
+	}
+
+	return nil
+}
+
+// nolint
+func getServiceInstanceId(si registry.ServiceInstance) string {
+	if si.GetId() == "" {
+		return si.GetHost() + "." + strconv.Itoa(si.GetPort())
+	}
+
+	return si.GetId()
+}
+
+// nolint
+func getServiceName(si registry.ServiceInstance) string {
+	return si.GetServiceName()
+}
+
+// getContent json string
+func getContent(si registry.ServiceInstance) (string, error) {
+	if bytes, err := json.Marshal(si); err != nil {
+		return "", err
+	} else {
+		return string(bytes), nil
+	}
+}
+
+// Update will update the data of the instance in registry
+func (fssd *fileSystemServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	return fssd.Register(instance)
+}
+
+// Unregister will unregister this instance from registry
+func (fssd *fileSystemServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	id := getServiceInstanceId(instance)
+	sn := getServiceName(instance)
+	if err := fssd.dynamicConfiguration.RemoveConfig(id, sn); err != nil {
+		return err
+	} else {
+		delete(fssd.fileMap, instance.GetId())
+		return nil
+	}

Review comment:
       ```suggestion
   	} 
   		delete(fssd.fileMap, instance.GetId())
   		return nil
   	
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] DogBaoBao commented on pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
DogBaoBao commented on pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#issuecomment-683836982


   Not finished, I'm working on it.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] DogBaoBao commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
DogBaoBao commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r485545761



##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || sdc.Protocol != constant.FILE_KEY {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+
+	if rp, err := file.Home(); err != nil {
+		return nil, perrors.WithStack(err)
+	} else {
+		fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY)
+		p := path.Join(rp, ".dubbo", "registry")

Review comment:
       this may not the registry meaning the registry




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] AlexStocks merged pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
AlexStocks merged pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] AlexStocks merged pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
AlexStocks merged pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732






----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] zouyx commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
zouyx commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r484283770



##########
File path: common/constant/key.go
##########
@@ -169,6 +169,10 @@ const (
 	NACOS_USERNAME               = "username"
 )
 
+const (
+	FILE_KEY = "file"

Review comment:
       file_discovery_service_key?

##########
File path: config_center/file/impl.go
##########
@@ -0,0 +1,304 @@
+/*
+ * 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 file
+
+import (
+	"bytes"
+	"errors"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"os/user"
+	"path"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+import (

Review comment:
       second block is 3rd-party pkg,third block is internal pkg

##########
File path: config_center/dynamic_configuration.go
##########
@@ -57,6 +57,8 @@ type DynamicConfiguration interface {
 
 	// PublishConfig will publish the config with the (key, group, value) pair
 	PublishConfig(string, string, string) error
+	// PublishConfig will remove the config white the (key, group) pair

Review comment:
       comment is wrong

##########
File path: config_center/file/listener.go
##########
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package file
+
+import (
+	"io/ioutil"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+import (

Review comment:
       sort this block

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,291 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"

Review comment:
       sort block

##########
File path: registry/file/service_discovery_test.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 file
+
+import (
+	"math/rand"
+	"strconv"
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/registry"
+)
+
+import (

Review comment:
       sort block




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] zouyx commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
zouyx commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r486095803



##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,306 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]

Review comment:
       where do you set instance into  instanceMap? do i miss something?

##########
File path: config_center/file/impl.go
##########
@@ -0,0 +1,310 @@
+/*
+ * 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 file
+
+import (
+	"bytes"
+	"errors"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"os/user"
+	"path"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/parser"
+)
+
+const (
+	PARAM_NAME_PREFIX                 = "dubbo.config-center."
+	CONFIG_CENTER_DIR_PARAM_NAME      = PARAM_NAME_PREFIX + "dir"
+	CONFIG_CENTER_ENCODING_PARAM_NAME = PARAM_NAME_PREFIX + "encoding"
+	DEFAULT_CONFIG_CENTER_ENCODING    = "UTF-8"
+)
+
+// FileSystemDynamicConfiguration
+type FileSystemDynamicConfiguration struct {
+	config_center.BaseDynamicConfiguration
+	url           *common.URL
+	rootPath      string
+	encoding      string
+	cacheListener *CacheListener
+	parser        parser.ConfigurationParser
+}
+
+func newFileSystemDynamicConfiguration(url *common.URL) (*FileSystemDynamicConfiguration, error) {
+	encode := url.GetParam(CONFIG_CENTER_ENCODING_PARAM_NAME, DEFAULT_CONFIG_CENTER_ENCODING)
+
+	root := url.GetParam(CONFIG_CENTER_DIR_PARAM_NAME, "")
+	var c *FileSystemDynamicConfiguration
+	if _, err := os.Stat(root); err != nil {
+		// not exist, use default, /XXX/xx/.dubbo/config-center
+		if rp, err := Home(); err != nil {
+			return nil, perrors.WithStack(err)
+		} else {
+			root = path.Join(rp, ".dubbo", "config-center")
+		}
+	}
+
+	if _, err := os.Stat(root); err != nil {
+		// it must be dir, if not exist, will create
+		if err = createDir(root); err != nil {
+			return nil, perrors.WithStack(err)
+		}
+	}
+
+	c = &FileSystemDynamicConfiguration{
+		url:      url,
+		rootPath: root,
+		encoding: encode,
+	}
+
+	c.cacheListener = NewCacheListener(c.rootPath)
+
+	return c, nil
+}
+
+// RootPath get root path
+func (fsdc *FileSystemDynamicConfiguration) RootPath() string {
+	return fsdc.rootPath
+}
+
+// Parser Get Parser
+func (fsdc *FileSystemDynamicConfiguration) Parser() parser.ConfigurationParser {
+	return fsdc.parser
+}
+
+// SetParser Set Parser
+func (fsdc *FileSystemDynamicConfiguration) SetParser(p parser.ConfigurationParser) {
+	fsdc.parser = p
+}
+
+// AddListener Add listener
+func (fsdc *FileSystemDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener,
+	opts ...config_center.Option) {
+	tmpOpts := &config_center.Options{}
+	for _, opt := range opts {
+		opt(tmpOpts)
+	}
+
+	path := fsdc.GetPath(key, tmpOpts.Group)
+
+	fsdc.cacheListener.AddListener(path, listener)
+}
+
+// RemoveListener Remove listener
+func (fsdc *FileSystemDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener,
+	opts ...config_center.Option) {
+	tmpOpts := &config_center.Options{}
+	for _, opt := range opts {
+		opt(tmpOpts)
+	}
+
+	path := fsdc.GetPath(key, tmpOpts.Group)
+
+	fsdc.cacheListener.RemoveListener(path, listener)
+}
+
+// GetProperties get properties file
+func (fsdc *FileSystemDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) {
+	tmpOpts := &config_center.Options{}
+	for _, opt := range opts {
+		opt(tmpOpts)
+	}
+
+	path := fsdc.GetPath(key, tmpOpts.Group)
+	file, err := ioutil.ReadFile(path)
+	if err != nil {
+		return "", perrors.WithStack(err)
+	}
+
+	return string(file), nil
+}
+
+// GetRule get Router rule properties file
+func (fsdc *FileSystemDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
+	return fsdc.GetProperties(key, opts...)
+}
+
+// GetInternalProperty get value by key in Default properties file(dubbo.properties)
+func (fsdc *FileSystemDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string,
+	error) {
+	return fsdc.GetProperties(key, opts...)
+}
+
+// PublishConfig will publish the config with the (key, group, value) pair
+func (fsdc *FileSystemDynamicConfiguration) PublishConfig(key string, group string, value string) error {
+	path := fsdc.GetPath(key, group)
+	return fsdc.write2File(path, value)
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (fsdc *FileSystemDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+	path := fsdc.GetPath("", group)
+	r := gxset.NewSet()
+
+	fileInfo, _ := ioutil.ReadDir(path)
+
+	for _, file := range fileInfo {
+		// list file
+		if !file.IsDir() {
+			r.Add(file.Name())
+		}

Review comment:
       ```suggestion
   		if file.IsDir() {
   			continue
   		}
   		r.Add(file.Name())
   ```

##########
File path: registry/file/service_discovery.go
##########
@@ -0,0 +1,306 @@
+/*
+ * 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 file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	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/config"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/config_center/file"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery)
+}
+
+// fileServiceDiscovery is the implementation of service discovery based on file.
+type fileSystemServiceDiscovery struct {
+	dynamicConfiguration file.FileSystemDynamicConfiguration
+	rootPath             string
+	fileMap              map[string]string
+}
+
+func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()

Review comment:
       This lock is too long, i suggest unlock it after set into instanceMap .




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] AlexStocks commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
AlexStocks commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r484648533



##########
File path: config_center/file/factory.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 file
+
+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/config_center"
+	"github.com/apache/dubbo-go/config_center/parser"
+)
+
+func init() {
+	extension.SetConfigCenterFactory(constant.FILE_KEY, func() config_center.DynamicConfigurationFactory { return &fileDynamicConfigurationFactory{} })

Review comment:
       代码太长了,拆分成多行吧。




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] DogBaoBao commented on a change in pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
DogBaoBao commented on a change in pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732#discussion_r484457075



##########
File path: common/constant/key.go
##########
@@ -169,6 +169,10 @@ const (
 	NACOS_USERNAME               = "username"
 )
 
+const (
+	FILE_KEY = "file"

Review comment:
       yes,the key from java:
   
   ```
   file=org.apache.dubbo.registry.client.FileSystemServiceDiscovery
   ```
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org


[GitHub] [dubbo-go] AlexStocks merged pull request #732: Ftr: File system service discovery

Posted by GitBox <gi...@apache.org>.
AlexStocks merged pull request #732:
URL: https://github.com/apache/dubbo-go/pull/732






----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org