dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/nacos/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 nacos
    19  
    20  import (
    21  	"fmt"
    22  	"sync"
    23  )
    24  
    25  import (
    26  	gxset "github.com/dubbogo/gost/container/set"
    27  	nacosClient "github.com/dubbogo/gost/database/kv/nacos"
    28  	gxpage "github.com/dubbogo/gost/hash/page"
    29  	"github.com/dubbogo/gost/log/logger"
    30  
    31  	"github.com/nacos-group/nacos-sdk-go/v2/model"
    32  	"github.com/nacos-group/nacos-sdk-go/v2/vo"
    33  
    34  	perrors "github.com/pkg/errors"
    35  )
    36  
    37  import (
    38  	"dubbo.apache.org/dubbo-go/v3/common"
    39  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    40  	"dubbo.apache.org/dubbo-go/v3/common/extension"
    41  	"dubbo.apache.org/dubbo-go/v3/registry"
    42  	"dubbo.apache.org/dubbo-go/v3/remoting/nacos"
    43  )
    44  
    45  const (
    46  	defaultGroup = constant.ServiceDiscoveryDefaultGroup
    47  	idKey        = "id"
    48  )
    49  
    50  func init() {
    51  	extension.SetServiceDiscovery(constant.NacosKey, newNacosServiceDiscovery)
    52  }
    53  
    54  // nacosServiceDiscovery is the implementation of service discovery based on nacos.
    55  // There is a problem, the go namingClient for nacos does not support the id field.
    56  // we will use the metadata to store the id of ServiceInstance
    57  type nacosServiceDiscovery struct {
    58  	group string
    59  	// descriptor is a short string about the basic information of this instance
    60  	descriptor string
    61  
    62  	// namingClient is the Nacos' namingClient
    63  	namingClient *nacosClient.NacosNamingClient
    64  	// cache registry instances
    65  	registryInstances []registry.ServiceInstance
    66  
    67  	instanceListenerMap map[string]*gxset.HashSet
    68  	listenerLock        sync.Mutex
    69  }
    70  
    71  // Destroy will close the service discovery.
    72  // Actually, it only marks the naming namingClient as null and then return
    73  func (n *nacosServiceDiscovery) Destroy() error {
    74  	for _, inst := range n.registryInstances {
    75  		err := n.Unregister(inst)
    76  		logger.Infof("Unregister nacos instance:%+v", inst)
    77  		if err != nil {
    78  			logger.Errorf("Unregister nacos instance:%+v, err:%+v", inst, err)
    79  		}
    80  	}
    81  	n.namingClient.Close()
    82  	return nil
    83  }
    84  
    85  // Register will register the service to nacos
    86  func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) error {
    87  	ins := n.toRegisterInstance(instance)
    88  	ok, err := n.namingClient.Client().RegisterInstance(ins)
    89  	if err != nil || !ok {
    90  		return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName())
    91  	}
    92  	n.registryInstances = append(n.registryInstances, instance)
    93  	return nil
    94  }
    95  
    96  // Update will update the information
    97  // However, because nacos client doesn't support the update API,
    98  // so we should unregister the instance and then register it again.
    99  // the error handling is hard to implement
   100  func (n *nacosServiceDiscovery) Update(instance registry.ServiceInstance) error {
   101  	// TODO(wait for nacos support)
   102  	err := n.Unregister(instance)
   103  	if err != nil {
   104  		return perrors.WithStack(err)
   105  	}
   106  	return n.Register(instance)
   107  }
   108  
   109  // Unregister will unregister the instance
   110  func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
   111  	ok, err := n.namingClient.Client().DeregisterInstance(n.toDeregisterInstance(instance))
   112  	if err != nil || !ok {
   113  		return perrors.WithMessage(err, "Could not unregister the instance. "+instance.GetServiceName())
   114  	}
   115  	return nil
   116  }
   117  
   118  // GetDefaultPageSize will return the constant registry.DefaultPageSize
   119  func (n *nacosServiceDiscovery) GetDefaultPageSize() int {
   120  	return registry.DefaultPageSize
   121  }
   122  
   123  // GetServices will return the all services
   124  func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet {
   125  	services, err := n.namingClient.Client().GetAllServicesInfo(vo.GetAllServiceInfoParam{
   126  		GroupName: n.group,
   127  	})
   128  
   129  	res := gxset.NewSet()
   130  	if err != nil {
   131  		logger.Errorf("Could not query the services: %v", err)
   132  		return res
   133  	}
   134  
   135  	for _, e := range services.Doms {
   136  		res.Add(e)
   137  	}
   138  	return res
   139  }
   140  
   141  // GetInstances will return the instances of serviceName and the group
   142  func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
   143  	instances, err := n.namingClient.Client().SelectAllInstances(vo.SelectAllInstancesParam{
   144  		ServiceName: serviceName,
   145  		GroupName:   n.group,
   146  	})
   147  	if err != nil {
   148  		logger.Errorf("Could not query the instances for service: %+v, group: %+v . It happened err %+v",
   149  			serviceName, n.group, err)
   150  		return make([]registry.ServiceInstance, 0)
   151  	}
   152  	res := make([]registry.ServiceInstance, 0, len(instances))
   153  	for _, ins := range instances {
   154  		metadata := ins.Metadata
   155  		id := metadata[idKey]
   156  
   157  		delete(metadata, idKey)
   158  
   159  		res = append(res, &registry.DefaultServiceInstance{
   160  			ID: id,
   161  			// ins.ServiceName is nacos service name like 'DEFAULT_GROUP@@MyAppName",
   162  			// which is not the service name we wanted, so we use serviceName directly.
   163  			ServiceName: serviceName,
   164  			Host:        ins.Ip,
   165  			Port:        int(ins.Port),
   166  			Enable:      ins.Enable,
   167  			Healthy:     ins.Healthy,
   168  			Metadata:    metadata,
   169  			GroupName:   n.group,
   170  		})
   171  	}
   172  	return res
   173  }
   174  
   175  // GetInstancesByPage will return the instances
   176  // Due to nacos namingClient does not support pagination, so we have to query all instances and then return part of them
   177  func (n *nacosServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
   178  	all := n.GetInstances(serviceName)
   179  	res := make([]interface{}, 0, pageSize)
   180  	// could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
   181  	for i := offset; i < len(all) && i < offset+pageSize; i++ {
   182  		res = append(res, all[i])
   183  	}
   184  	return gxpage.NewPage(offset, pageSize, res, len(all))
   185  }
   186  
   187  // GetHealthyInstancesByPage will return the instance
   188  // The nacos namingClient has an API SelectInstances, which has a parameter call HealthyOnly.
   189  // However, the healthy parameter in this method maybe false. So we can not use that API.
   190  // Thus, we must query all instances and then do filter
   191  func (n *nacosServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
   192  	all := n.GetInstances(serviceName)
   193  	res := make([]interface{}, 0, pageSize)
   194  	// could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
   195  	var (
   196  		i     = offset
   197  		count = 0
   198  	)
   199  	for i < len(all) && count < pageSize {
   200  		ins := all[i]
   201  		if ins.IsHealthy() == healthy {
   202  			res = append(res, all[i])
   203  			count++
   204  		}
   205  		i++
   206  	}
   207  	return gxpage.NewPage(offset, pageSize, res, len(all))
   208  }
   209  
   210  // GetRequestInstances will return the instances
   211  // The nacos namingClient doesn't have batch API, so we should query those serviceNames one by one.
   212  func (n *nacosServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
   213  	res := make(map[string]gxpage.Pager, len(serviceNames))
   214  	for _, name := range serviceNames {
   215  		res[name] = n.GetInstancesByPage(name, offset, requestedSize)
   216  	}
   217  	return res
   218  }
   219  
   220  func (n *nacosServiceDiscovery) registerInstanceListener(listener registry.ServiceInstancesChangedListener) {
   221  	n.listenerLock.Lock()
   222  	defer n.listenerLock.Unlock()
   223  
   224  	for _, t := range listener.GetServiceNames().Values() {
   225  		serviceName, ok := t.(string)
   226  		if !ok {
   227  			logger.Errorf("service name error %s", t)
   228  			continue
   229  		}
   230  		listenerSet, found := n.instanceListenerMap[serviceName]
   231  		if !found {
   232  			listenerSet = gxset.NewSet(listener)
   233  			listenerSet.Add(listener)
   234  			n.instanceListenerMap[serviceName] = listenerSet
   235  		} else {
   236  			listenerSet.Add(listener)
   237  		}
   238  	}
   239  }
   240  
   241  // AddListener will add a listener
   242  func (n *nacosServiceDiscovery) AddListener(listener registry.ServiceInstancesChangedListener) error {
   243  	n.registerInstanceListener(listener)
   244  	for _, t := range listener.GetServiceNames().Values() {
   245  		serviceName := t.(string)
   246  		err := n.namingClient.Client().Subscribe(&vo.SubscribeParam{
   247  			ServiceName: serviceName,
   248  			GroupName:   n.group,
   249  			SubscribeCallback: func(services []model.Instance, err error) {
   250  				if err != nil {
   251  					logger.Errorf("Could not handle the subscribe notification because the err is not nil."+
   252  						" service name: %s, err: %v", serviceName, err)
   253  				}
   254  				instances := make([]registry.ServiceInstance, 0, len(services))
   255  				for _, service := range services {
   256  					// we won't use the nacos instance id here but use our instance id
   257  					metadata := service.Metadata
   258  					id := metadata[idKey]
   259  
   260  					delete(metadata, idKey)
   261  
   262  					instances = append(instances, &registry.DefaultServiceInstance{
   263  						ID:          id,
   264  						ServiceName: service.ServiceName,
   265  						Host:        service.Ip,
   266  						Port:        int(service.Port),
   267  						Enable:      service.Enable,
   268  						Healthy:     true,
   269  						Metadata:    metadata,
   270  						GroupName:   n.group,
   271  					})
   272  				}
   273  
   274  				var e error
   275  				for _, lis := range n.instanceListenerMap[serviceName].Values() {
   276  					var instanceListener registry.ServiceInstancesChangedListener
   277  					instanceListener = lis.(registry.ServiceInstancesChangedListener)
   278  					e = instanceListener.OnEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances))
   279  				}
   280  
   281  				if e != nil {
   282  					logger.Errorf("Dispatching event got exception, service name: %s, err: %v", serviceName, err)
   283  				}
   284  			},
   285  		})
   286  		if err != nil {
   287  			return err
   288  		}
   289  	}
   290  	return nil
   291  }
   292  
   293  // toRegisterInstance convert the ServiceInstance to RegisterInstanceParam
   294  // the Ephemeral will be true
   295  func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInstance) vo.RegisterInstanceParam {
   296  	metadata := instance.GetMetadata()
   297  	if metadata == nil {
   298  		metadata = make(map[string]string, 1)
   299  	}
   300  	metadata[idKey] = instance.GetID()
   301  	return vo.RegisterInstanceParam{
   302  		ServiceName: instance.GetServiceName(),
   303  		Ip:          instance.GetHost(),
   304  		Port:        uint64(instance.GetPort()),
   305  		Metadata:    metadata,
   306  		// We must specify the weight since Java nacos namingClient will ignore the instance whose weight is 0
   307  		Weight:    1,
   308  		Enable:    instance.IsEnable(),
   309  		Healthy:   instance.IsHealthy(),
   310  		GroupName: n.group,
   311  		Ephemeral: true,
   312  	}
   313  }
   314  
   315  // toDeregisterInstance will convert the ServiceInstance to DeregisterInstanceParam
   316  func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceInstance) vo.DeregisterInstanceParam {
   317  	return vo.DeregisterInstanceParam{
   318  		ServiceName: instance.GetServiceName(),
   319  		Ip:          instance.GetHost(),
   320  		Port:        uint64(instance.GetPort()),
   321  		GroupName:   n.group,
   322  	}
   323  }
   324  
   325  func (n *nacosServiceDiscovery) String() string {
   326  	return n.descriptor
   327  }
   328  
   329  // newNacosServiceDiscovery will create new service discovery instance
   330  func newNacosServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) {
   331  	discoveryURL := common.NewURLWithOptions(
   332  		common.WithParams(url.GetParams()),
   333  		common.WithParamsValue(constant.TimeoutKey, url.GetParam(constant.RegistryTimeoutKey, constant.DefaultRegTimeout)),
   334  		common.WithParamsValue(constant.NacosGroupKey, url.GetParam(constant.RegistryGroupKey, defaultGroup)),
   335  		common.WithParamsValue(constant.NacosUsername, url.Username),
   336  		common.WithParamsValue(constant.NacosPassword, url.Password),
   337  		common.WithParamsValue(constant.NacosNamespaceID, url.GetParam(constant.RegistryNamespaceKey, "")))
   338  	discoveryURL.Location = url.Location
   339  	discoveryURL.Username = url.Username
   340  	discoveryURL.Password = url.Password
   341  	client, err := nacos.NewNacosClientByURL(discoveryURL)
   342  	if err != nil {
   343  		return nil, perrors.WithMessage(err, "create nacos namingClient failed.")
   344  	}
   345  
   346  	descriptor := fmt.Sprintf("nacos-service-discovery[%s]", discoveryURL.Location)
   347  
   348  	group := url.GetParam(constant.RegistryGroupKey, defaultGroup)
   349  	newInstance := &nacosServiceDiscovery{
   350  		group:               group,
   351  		namingClient:        client,
   352  		descriptor:          descriptor,
   353  		registryInstances:   []registry.ServiceInstance{},
   354  		instanceListenerMap: make(map[string]*gxset.HashSet),
   355  	}
   356  	return newInstance, nil
   357  }