dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/etcdv3/service_discovery.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package etcdv3
    19  
    20  import (
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  )
    25  
    26  import (
    27  	gxset "github.com/dubbogo/gost/container/set"
    28  	gxetcd "github.com/dubbogo/gost/database/kv/etcd/v3"
    29  	gxpage "github.com/dubbogo/gost/hash/page"
    30  	"github.com/dubbogo/gost/log/logger"
    31  
    32  	"github.com/hashicorp/vault/sdk/helper/jsonutil"
    33  )
    34  
    35  import (
    36  	"dubbo.apache.org/dubbo-go/v3/common"
    37  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    38  	"dubbo.apache.org/dubbo-go/v3/common/extension"
    39  	"dubbo.apache.org/dubbo-go/v3/registry"
    40  	"dubbo.apache.org/dubbo-go/v3/remoting"
    41  	"dubbo.apache.org/dubbo-go/v3/remoting/etcdv3"
    42  )
    43  
    44  const (
    45  	ROOT = "/services"
    46  )
    47  
    48  var initLock sync.Mutex
    49  
    50  func init() {
    51  	extension.SetServiceDiscovery(constant.EtcdV3Key, newEtcdV3ServiceDiscovery)
    52  }
    53  
    54  // new etcd service discovery struct
    55  type etcdV3ServiceDiscovery struct {
    56  	// descriptor is a short string about the basic information of this instance
    57  	descriptor string
    58  	// client is current Etcdv3 client
    59  	client *gxetcd.Client
    60  	// serviceInstance is current serviceInstance
    61  	serviceInstance *registry.ServiceInstance
    62  	// services is when register or update will add service name
    63  	services *gxset.HashSet
    64  	// child listener
    65  	childListenerMap    map[string]*etcdv3.EventListener
    66  	instanceListenerMap map[string]*gxset.HashSet
    67  }
    68  
    69  // basic information of this instance
    70  func (e *etcdV3ServiceDiscovery) String() string {
    71  	return e.descriptor
    72  }
    73  
    74  // Destroy service discovery
    75  func (e *etcdV3ServiceDiscovery) Destroy() error {
    76  	if e.client != nil {
    77  		e.client.Close()
    78  	}
    79  	return nil
    80  }
    81  
    82  // Register will register an instance of ServiceInstance to registry
    83  func (e *etcdV3ServiceDiscovery) Register(instance registry.ServiceInstance) error {
    84  	e.serviceInstance = &instance
    85  
    86  	path := toPath(instance)
    87  
    88  	if nil != e.client {
    89  		ins, err := jsonutil.EncodeJSON(instance)
    90  		if err == nil {
    91  			err = e.client.RegisterTemp(path, string(ins))
    92  			if err != nil {
    93  				logger.Errorf("cannot register the instance: %s", string(ins), err)
    94  			} else {
    95  				e.services.Add(instance.GetServiceName())
    96  			}
    97  		}
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  // Update will update the data of the instance in registry
   104  func (e *etcdV3ServiceDiscovery) Update(instance registry.ServiceInstance) error {
   105  	path := toPath(instance)
   106  
   107  	if nil != e.client {
   108  		ins, err := jsonutil.EncodeJSON(instance)
   109  		if err == nil {
   110  			if err = e.client.RegisterTemp(path, string(ins)); err != nil {
   111  				logger.Warnf("etcdV3ServiceDiscovery.client.RegisterTemp(path:%v, instance:%v) = error:%v",
   112  					path, string(ins), err)
   113  			}
   114  			e.services.Add(instance.GetServiceName())
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Unregister will unregister this instance from registry
   122  func (e *etcdV3ServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
   123  	path := toPath(instance)
   124  
   125  	if nil != e.client {
   126  		err := e.client.Delete(path)
   127  		e.services.Remove(instance.GetServiceName())
   128  		e.serviceInstance = nil
   129  		return err
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // GetDefaultPageSize will return the default page size
   136  func (e *etcdV3ServiceDiscovery) GetDefaultPageSize() int {
   137  	return registry.DefaultPageSize
   138  }
   139  
   140  // GetServices will return the all service names.
   141  func (e *etcdV3ServiceDiscovery) GetServices() *gxset.HashSet {
   142  	return e.services
   143  }
   144  
   145  // GetInstances will return all service instances with serviceName
   146  func (e *etcdV3ServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
   147  	if nil != e.client {
   148  		// get keys and values
   149  		_, vList, err := e.client.GetChildrenKVList(toParentPath(serviceName))
   150  		if nil == err {
   151  			serviceInstances := make([]registry.ServiceInstance, 0, len(vList))
   152  			for _, v := range vList {
   153  				instance := &registry.DefaultServiceInstance{}
   154  				err = jsonutil.DecodeJSON([]byte(v), &instance)
   155  				if nil == err {
   156  					serviceInstances = append(serviceInstances, instance)
   157  				}
   158  			}
   159  			return serviceInstances
   160  		}
   161  		logger.Infof("could not getChildrenKVList the err is:%v", err)
   162  	}
   163  
   164  	return make([]registry.ServiceInstance, 0)
   165  }
   166  
   167  // GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName
   168  // the page will start at offset
   169  func (e *etcdV3ServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
   170  	all := e.GetInstances(serviceName)
   171  
   172  	res := make([]interface{}, 0, pageSize)
   173  
   174  	for i := offset; i < len(all) && i < offset+pageSize; i++ {
   175  		res = append(res, all[i])
   176  	}
   177  
   178  	return gxpage.NewPage(offset, pageSize, res, len(all))
   179  }
   180  
   181  // GetHealthyInstancesByPage will return a page containing instances of ServiceInstance.
   182  // The param healthy indices that the instance should be healthy or not.
   183  // The page will start at offset
   184  func (e *etcdV3ServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
   185  	all := e.GetInstances(serviceName)
   186  	res := make([]interface{}, 0, pageSize)
   187  
   188  	var (
   189  		i     = offset
   190  		count = 0
   191  	)
   192  	for i < len(all) && count < pageSize {
   193  		ins := all[i]
   194  		if ins.IsHealthy() == healthy {
   195  			res = append(res, all[i])
   196  			count++
   197  		}
   198  		i++
   199  	}
   200  	return gxpage.NewPage(offset, pageSize, res, len(all))
   201  }
   202  
   203  // GetRequestInstances Batch get all instances by the specified service names
   204  func (e *etcdV3ServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
   205  	res := make(map[string]gxpage.Pager, len(serviceNames))
   206  	for _, name := range serviceNames {
   207  		res[name] = e.GetInstancesByPage(name, offset, requestedSize)
   208  	}
   209  	return res
   210  }
   211  
   212  // AddListener adds a new ServiceInstancesChangedListenerImpl
   213  // see addServiceInstancesChangedListener in Java
   214  func (e *etcdV3ServiceDiscovery) AddListener(listener registry.ServiceInstancesChangedListener) error {
   215  	for _, t := range listener.GetServiceNames().Values() {
   216  		err := e.registerServiceInstanceListener(t.(string), listener)
   217  		if err != nil {
   218  			return err
   219  		}
   220  
   221  		err = e.registerServiceWatcher(t.(string))
   222  		if err != nil {
   223  			return err
   224  		}
   225  	}
   226  	return nil
   227  }
   228  
   229  // Convert instance to dubbo path
   230  func toPath(instance registry.ServiceInstance) string {
   231  	if instance == nil {
   232  		return ""
   233  	}
   234  	// like: /services/servicename1/host(127.0.0.1)/8080
   235  	return fmt.Sprintf("%s%d", ROOT+constant.PathSeparator+instance.GetServiceName()+constant.PathSeparator+instance.GetHost()+constant.KeySeparator, instance.GetPort())
   236  }
   237  
   238  // to dubbo service path
   239  func toParentPath(serviceName string) string {
   240  	return ROOT + constant.PathSeparator + serviceName
   241  }
   242  
   243  // register service instance listener, instance listener and watcher are matched through serviceName
   244  func (e *etcdV3ServiceDiscovery) registerServiceInstanceListener(serviceName string, listener registry.ServiceInstancesChangedListener) error {
   245  	initLock.Lock()
   246  	defer initLock.Unlock()
   247  
   248  	set, found := e.instanceListenerMap[serviceName]
   249  	if !found {
   250  		set = gxset.NewSet(listener)
   251  		set.Add(listener)
   252  		e.instanceListenerMap[serviceName] = set
   253  		return nil
   254  	}
   255  	set.Add(listener)
   256  	return nil
   257  }
   258  
   259  // register service watcher
   260  func (e *etcdV3ServiceDiscovery) registerServiceWatcher(serviceName string) error {
   261  	initLock.Lock()
   262  	defer initLock.Unlock()
   263  
   264  	path := toParentPath(serviceName)
   265  
   266  	listener, found := e.childListenerMap[serviceName]
   267  
   268  	if !found {
   269  		listener = etcdv3.NewEventListener(e.client)
   270  		e.childListenerMap[serviceName] = listener
   271  	}
   272  
   273  	listener.ListenServiceEvent(path, e)
   274  
   275  	return nil
   276  }
   277  
   278  // DataChange when child data change should DispatchEventByServiceName
   279  func (e *etcdV3ServiceDiscovery) DataChange(eventType remoting.Event) bool {
   280  	if eventType.Action == remoting.EventTypeUpdate {
   281  		instance := &registry.DefaultServiceInstance{}
   282  		err := jsonutil.DecodeJSON([]byte(eventType.Content), &instance)
   283  		if err != nil {
   284  			instance.ServiceName = ""
   285  		}
   286  
   287  		// notify instance listener instance change
   288  		name := instance.ServiceName
   289  		instances := e.GetInstances(name)
   290  		for _, lis := range e.instanceListenerMap[instance.ServiceName].Values() {
   291  			var instanceLis registry.ServiceInstancesChangedListener
   292  			instanceLis = lis.(registry.ServiceInstancesChangedListener)
   293  			err = instanceLis.OnEvent(registry.NewServiceInstancesChangedEvent(name, instances))
   294  		}
   295  		if err != nil {
   296  			return false
   297  		}
   298  	}
   299  
   300  	return true
   301  }
   302  
   303  // newEtcdv3ServiceDiscovery
   304  func newEtcdV3ServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) {
   305  	initLock.Lock()
   306  	defer initLock.Unlock()
   307  
   308  	timeout := url.GetParamDuration(constant.RegistryTimeoutKey, constant.DefaultRegTimeout)
   309  
   310  	logger.Infof("etcd address is: %v,timeout is:%s", url.Location, timeout.String())
   311  
   312  	client := etcdv3.NewServiceDiscoveryClient(
   313  		gxetcd.WithName(gxetcd.RegistryETCDV3Client),
   314  		gxetcd.WithTimeout(timeout),
   315  		gxetcd.WithEndpoints(strings.Split(url.Location, ",")...),
   316  	)
   317  
   318  	descriptor := fmt.Sprintf("etcd-service-discovery[%s]", url.Location)
   319  
   320  	return &etcdV3ServiceDiscovery{
   321  		descriptor:          descriptor,
   322  		client:              client,
   323  		serviceInstance:     nil,
   324  		services:            gxset.NewSet(),
   325  		childListenerMap:    make(map[string]*etcdv3.EventListener),
   326  		instanceListenerMap: make(map[string]*gxset.HashSet)}, nil
   327  }