dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/nacos/listener.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  	"bytes"
    22  	"net/url"
    23  	"reflect"
    24  	"strconv"
    25  	"sync"
    26  )
    27  
    28  import (
    29  	gxchan "github.com/dubbogo/gost/container/chan"
    30  	nacosClient "github.com/dubbogo/gost/database/kv/nacos"
    31  	"github.com/dubbogo/gost/log/logger"
    32  
    33  	"github.com/nacos-group/nacos-sdk-go/v2/model"
    34  	"github.com/nacos-group/nacos-sdk-go/v2/vo"
    35  
    36  	perrors "github.com/pkg/errors"
    37  )
    38  
    39  import (
    40  	"dubbo.apache.org/dubbo-go/v3/common"
    41  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    42  	"dubbo.apache.org/dubbo-go/v3/config_center"
    43  	"dubbo.apache.org/dubbo-go/v3/registry"
    44  	"dubbo.apache.org/dubbo-go/v3/remoting"
    45  )
    46  
    47  var (
    48  	listenerCache sync.Map
    49  )
    50  
    51  type callback func(services []model.Instance, err error)
    52  
    53  type nacosListener struct {
    54  	namingClient   *nacosClient.NacosNamingClient
    55  	listenURL      *common.URL
    56  	regURL         *common.URL
    57  	events         *gxchan.UnboundedChan
    58  	instanceMap    map[string]model.Instance
    59  	cacheLock      sync.Mutex
    60  	done           chan struct{}
    61  	subscribeParam *vo.SubscribeParam
    62  }
    63  
    64  // NewNacosListener creates a data listener for nacos
    65  func NewNacosListener(url, regURL *common.URL, namingClient *nacosClient.NacosNamingClient) (*nacosListener, error) {
    66  	listener := &nacosListener{
    67  		namingClient: namingClient,
    68  		listenURL:    url,
    69  		regURL:       regURL,
    70  		events:       gxchan.NewUnboundedChan(32),
    71  		instanceMap:  map[string]model.Instance{},
    72  		done:         make(chan struct{}),
    73  	}
    74  	err := listener.startListen()
    75  	return listener, err
    76  }
    77  
    78  // NewNacosListener creates a data listener for nacos
    79  func NewNacosListenerWithServiceName(serviceName string, url, regURL *common.URL, namingClient *nacosClient.NacosNamingClient) (*nacosListener, error) {
    80  	listener := &nacosListener{
    81  		namingClient: namingClient,
    82  		listenURL:    url,
    83  		regURL:       regURL,
    84  		events:       gxchan.NewUnboundedChan(32),
    85  		instanceMap:  map[string]model.Instance{},
    86  		done:         make(chan struct{}),
    87  	}
    88  	err := listener.startListenWithServiceName(serviceName)
    89  	return listener, err
    90  }
    91  
    92  func generateUrl(instance model.Instance) *common.URL {
    93  	if instance.Metadata == nil {
    94  		logger.Errorf("nacos instance metadata is empty,instance:%+v", instance)
    95  		return nil
    96  	}
    97  	path := instance.Metadata["path"]
    98  	myInterface := instance.Metadata["interface"]
    99  	if len(path) == 0 && len(myInterface) == 0 {
   100  		logger.Errorf("nacos instance metadata does not have  both path key and interface key,instance:%+v", instance)
   101  		return nil
   102  	}
   103  	if len(path) == 0 && len(myInterface) != 0 {
   104  		path = "/" + myInterface
   105  	}
   106  	protocol := instance.Metadata["protocol"]
   107  	if len(protocol) == 0 {
   108  		logger.Errorf("nacos instance metadata does not have protocol key,instance:%+v", instance)
   109  		return nil
   110  	}
   111  	urlMap := url.Values{}
   112  	for k, v := range instance.Metadata {
   113  		urlMap.Set(k, v)
   114  	}
   115  	return common.NewURLWithOptions(
   116  		common.WithIp(instance.Ip),
   117  		common.WithPort(strconv.Itoa(int(instance.Port))),
   118  		common.WithProtocol(protocol),
   119  		common.WithParams(urlMap),
   120  		common.WithPath(path),
   121  	)
   122  }
   123  
   124  // Callback will be invoked when got subscribed events.
   125  func (nl *nacosListener) Callback(services []model.Instance, err error) {
   126  	if err != nil {
   127  		logger.Errorf("nacos subscribe callback error:%s , subscribe:%+v ", err.Error(), nl.subscribeParam)
   128  		return
   129  	}
   130  
   131  	addInstances := make([]model.Instance, 0, len(services))
   132  	delInstances := make([]model.Instance, 0, len(services))
   133  	updateInstances := make([]model.Instance, 0, len(services))
   134  	newInstanceMap := make(map[string]model.Instance, len(services))
   135  
   136  	nl.cacheLock.Lock()
   137  	defer nl.cacheLock.Unlock()
   138  	for i := range services {
   139  		if !services[i].Enable {
   140  			// instance is not available,so ignore it
   141  			continue
   142  		}
   143  		host := services[i].Ip + ":" + strconv.Itoa(int(services[i].Port))
   144  		instance := services[i]
   145  		newInstanceMap[host] = instance
   146  		if old, ok := nl.instanceMap[host]; !ok && instance.Healthy {
   147  			// instance does not exist in cache, add it to cache
   148  			addInstances = append(addInstances, instance)
   149  		} else if !reflect.DeepEqual(old, instance) && instance.Healthy {
   150  			// instance is not different from cache, update it to cache
   151  			updateInstances = append(updateInstances, instance)
   152  		}
   153  	}
   154  
   155  	for host, inst := range nl.instanceMap {
   156  		if newInstance, ok := newInstanceMap[host]; !ok || !newInstance.Healthy {
   157  			// cache instance does not exist in new instance list, remove it from cache
   158  			delInstances = append(delInstances, inst)
   159  		}
   160  	}
   161  
   162  	nl.instanceMap = newInstanceMap
   163  	for i := range addInstances {
   164  		if newUrl := generateUrl(addInstances[i]); newUrl != nil {
   165  			nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeAdd})
   166  		}
   167  	}
   168  	for i := range delInstances {
   169  		if newUrl := generateUrl(delInstances[i]); newUrl != nil {
   170  			nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeDel})
   171  		}
   172  	}
   173  
   174  	for i := range updateInstances {
   175  		if newUrl := generateUrl(updateInstances[i]); newUrl != nil {
   176  			nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeUpdate})
   177  		}
   178  	}
   179  }
   180  
   181  func getSubscribeName(url *common.URL) string {
   182  	var buffer bytes.Buffer
   183  
   184  	buffer.Write([]byte(common.DubboNodes[common.PROVIDER]))
   185  	appendParam(&buffer, url, constant.InterfaceKey)
   186  	appendParam(&buffer, url, constant.VersionKey)
   187  	appendParam(&buffer, url, constant.GroupKey)
   188  	return buffer.String()
   189  }
   190  
   191  func (nl *nacosListener) startListen() error {
   192  	if nl.namingClient == nil {
   193  		return perrors.New("nacos naming namingClient stopped")
   194  	}
   195  	nl.subscribeParam = createSubscribeParam(nl.listenURL, nl.regURL, nl.Callback)
   196  	if nl.subscribeParam == nil {
   197  		return perrors.New("create nacos subscribeParam failed")
   198  	}
   199  	go func() {
   200  		err := nl.namingClient.Client().Subscribe(nl.subscribeParam)
   201  		if err == nil {
   202  			listenerCache.Store(nl.subscribeParam.ServiceName+nl.subscribeParam.GroupName, nl)
   203  		}
   204  	}()
   205  	return nil
   206  }
   207  
   208  func (nl *nacosListener) startListenWithServiceName(serviceName string) error {
   209  	if nl.namingClient == nil {
   210  		return perrors.New("nacos naming namingClient stopped")
   211  	}
   212  	nl.subscribeParam = createSubscribeParamWithServiceName(serviceName, nl.regURL, nl.Callback)
   213  	if nl.subscribeParam == nil {
   214  		return perrors.New("create nacos subscribeParam failed")
   215  	}
   216  	go func() {
   217  		err := nl.namingClient.Client().Subscribe(nl.subscribeParam)
   218  		if err == nil {
   219  			listenerCache.Store(nl.subscribeParam.ServiceName+nl.subscribeParam.GroupName, nl)
   220  		}
   221  	}()
   222  	return nil
   223  }
   224  
   225  func (nl *nacosListener) stopListen() error {
   226  	return nl.namingClient.Client().Unsubscribe(nl.subscribeParam)
   227  }
   228  
   229  func (nl *nacosListener) process(configType *config_center.ConfigChangeEvent) {
   230  	nl.events.In() <- configType
   231  }
   232  
   233  // Next returns the service event from nacos.
   234  func (nl *nacosListener) Next() (*registry.ServiceEvent, error) {
   235  	for {
   236  		select {
   237  		case <-nl.done:
   238  			logger.Warnf("nacos listener is close!listenUrl:%+v", nl.listenURL)
   239  			return nil, perrors.New("listener stopped")
   240  
   241  		case val := <-nl.events.Out():
   242  			e, _ := val.(*config_center.ConfigChangeEvent)
   243  			logger.Debugf("got nacos event %s", e)
   244  			return &registry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil
   245  		}
   246  	}
   247  }
   248  
   249  // nolint
   250  func (nl *nacosListener) Close() {
   251  	_ = nl.stopListen()
   252  	close(nl.done)
   253  }