github.com/nacos-group/nacos-sdk-go@v1.1.4/clients/naming_client/host_reactor.go (about)

     1  /*
     2   * Copyright 1999-2020 Alibaba Group Holding Ltd.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package naming_client
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"reflect"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/nacos-group/nacos-sdk-go/clients/cache"
    30  	"github.com/nacos-group/nacos-sdk-go/common/logger"
    31  	"github.com/nacos-group/nacos-sdk-go/model"
    32  	"github.com/nacos-group/nacos-sdk-go/util"
    33  )
    34  
    35  type HostReactor struct {
    36  	serviceInfoMap       cache.ConcurrentMap
    37  	cacheDir             string
    38  	updateThreadNum      int
    39  	serviceProxy         NamingProxy
    40  	pushReceiver         PushReceiver
    41  	subCallback          SubscribeCallback
    42  	updateTimeMap        cache.ConcurrentMap
    43  	updateCacheWhenEmpty bool
    44  }
    45  
    46  const Default_Update_Thread_Num = 20
    47  
    48  func NewHostReactor(serviceProxy NamingProxy, cacheDir string, updateThreadNum int, notLoadCacheAtStart bool, subCallback SubscribeCallback, updateCacheWhenEmpty bool) HostReactor {
    49  	if updateThreadNum <= 0 {
    50  		updateThreadNum = Default_Update_Thread_Num
    51  	}
    52  	hr := HostReactor{
    53  		serviceProxy:         serviceProxy,
    54  		cacheDir:             cacheDir,
    55  		updateThreadNum:      updateThreadNum,
    56  		serviceInfoMap:       cache.NewConcurrentMap(),
    57  		subCallback:          subCallback,
    58  		updateTimeMap:        cache.NewConcurrentMap(),
    59  		updateCacheWhenEmpty: updateCacheWhenEmpty,
    60  	}
    61  	pr := NewPushReceiver(&hr)
    62  	hr.pushReceiver = *pr
    63  	if !notLoadCacheAtStart {
    64  		hr.loadCacheFromDisk()
    65  	}
    66  	go hr.asyncUpdateService()
    67  	return hr
    68  }
    69  
    70  func (hr *HostReactor) loadCacheFromDisk() {
    71  	serviceMap := cache.ReadServicesFromFile(hr.cacheDir)
    72  	if len(serviceMap) == 0 {
    73  		return
    74  	}
    75  	for k, v := range serviceMap {
    76  		hr.serviceInfoMap.Set(k, v)
    77  	}
    78  }
    79  
    80  func (hr *HostReactor) ProcessServiceJson(result string) {
    81  	service := util.JsonToService(result)
    82  	if service == nil {
    83  		return
    84  	}
    85  	cacheKey := util.GetServiceCacheKey(service.Name, service.Clusters)
    86  
    87  	oldDomain, ok := hr.serviceInfoMap.Get(cacheKey)
    88  	if ok && !hr.updateCacheWhenEmpty {
    89  		//if instance list is empty,not to update cache
    90  		if service.Hosts == nil || len(service.Hosts) == 0 {
    91  			logger.Errorf("do not have useful host, ignore it, name:%s", service.Name)
    92  			return
    93  		}
    94  	}
    95  	hr.updateTimeMap.Set(cacheKey, uint64(util.CurrentMillis()))
    96  	hr.serviceInfoMap.Set(cacheKey, *service)
    97  	oldService, serviceOk := oldDomain.(model.Service)
    98  	if !ok || ok && serviceOk && isServiceInstanceChanged(&oldService, service) {
    99  		if !ok {
   100  			logger.Info("service not found in cache " + cacheKey)
   101  		} else {
   102  			logger.Info("service key:%s was updated to:%s", cacheKey, util.ToJsonString(service))
   103  		}
   104  		cache.WriteServicesToFile(*service, hr.cacheDir)
   105  		hr.subCallback.ServiceChanged(service)
   106  	}
   107  }
   108  
   109  func (hr *HostReactor) GetServiceInfo(serviceName string, clusters string) (model.Service, error) {
   110  	key := util.GetServiceCacheKey(serviceName, clusters)
   111  	cacheService, ok := hr.serviceInfoMap.Get(key)
   112  	if !ok {
   113  		hr.updateServiceNow(serviceName, clusters)
   114  		if cacheService, ok = hr.serviceInfoMap.Get(key); !ok {
   115  			return model.Service{}, errors.New("get service info failed")
   116  		}
   117  	}
   118  
   119  	return cacheService.(model.Service), nil
   120  }
   121  
   122  func (hr *HostReactor) GetAllServiceInfo(nameSpace, groupName string, pageNo, pageSize uint32) (model.ServiceList, error) {
   123  	data := model.ServiceList{}
   124  	result, err := hr.serviceProxy.GetAllServiceInfoList(nameSpace, groupName, pageNo, pageSize)
   125  	if err != nil {
   126  		logger.Errorf("GetAllServiceInfoList return error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d err:%+v",
   127  			nameSpace, groupName, pageNo, pageSize, err)
   128  		return data, err
   129  	}
   130  	if result == "" {
   131  		logger.Warnf("GetAllServiceInfoList result is empty!nameSpace:%s  groupName:%s pageNo:%d, pageSize:%d",
   132  			nameSpace, groupName, pageNo, pageSize)
   133  		return data, nil
   134  	}
   135  
   136  	err = json.Unmarshal([]byte(result), &data)
   137  	if err != nil {
   138  		logger.Errorf("GetAllServiceInfoList result json.Unmarshal error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d",
   139  			nameSpace, groupName, pageNo, pageSize)
   140  		return data, err
   141  	}
   142  	return data, nil
   143  }
   144  
   145  func (hr *HostReactor) updateServiceNow(serviceName, clusters string) {
   146  	result, err := hr.serviceProxy.QueryList(serviceName, clusters, hr.pushReceiver.port, false)
   147  
   148  	if err != nil {
   149  		logger.Errorf("QueryList return error!serviceName:%s cluster:%s err:%+v", serviceName, clusters, err)
   150  		return
   151  	}
   152  	if result == "" {
   153  		logger.Errorf("QueryList result is empty!serviceName:%s cluster:%s", serviceName, clusters)
   154  		return
   155  	}
   156  	hr.ProcessServiceJson(result)
   157  }
   158  
   159  func (hr *HostReactor) asyncUpdateService() {
   160  	sema := util.NewSemaphore(hr.updateThreadNum)
   161  	for {
   162  		for _, v := range hr.serviceInfoMap.Items() {
   163  			service := v.(model.Service)
   164  			lastRefTime, ok := hr.updateTimeMap.Get(util.GetServiceCacheKey(service.Name, service.Clusters))
   165  			if !ok {
   166  				lastRefTime = uint64(0)
   167  			}
   168  			if uint64(util.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
   169  				sema.Acquire()
   170  				go func() {
   171  					hr.updateServiceNow(service.Name, service.Clusters)
   172  					sema.Release()
   173  				}()
   174  			}
   175  		}
   176  		time.Sleep(1 * time.Second)
   177  	}
   178  }
   179  
   180  // return true when service instance changed ,otherwise return false.
   181  func isServiceInstanceChanged(oldService, newService *model.Service) bool {
   182  	oldHostsLen := len(oldService.Hosts)
   183  	newHostsLen := len(newService.Hosts)
   184  	if oldHostsLen != newHostsLen {
   185  		return true
   186  	}
   187  	// compare refTime
   188  	oldRefTime := oldService.LastRefTime
   189  	newRefTime := newService.LastRefTime
   190  	if oldRefTime > newRefTime {
   191  		logger.Warn(fmt.Sprintf("out of date data received, old-t: %v , new-t:  %v", oldRefTime, newRefTime))
   192  		return false
   193  	}
   194  	// sort instance list
   195  	oldInstance := oldService.Hosts
   196  	newInstance := make([]model.Instance, len(newService.Hosts))
   197  	copy(newInstance, newService.Hosts)
   198  	sortInstance(oldInstance)
   199  	sortInstance(newInstance)
   200  	return !reflect.DeepEqual(oldInstance, newInstance)
   201  }
   202  
   203  type instanceSorter []model.Instance
   204  
   205  func (s instanceSorter) Len() int {
   206  	return len(s)
   207  }
   208  func (s instanceSorter) Swap(i, j int) {
   209  	s[i], s[j] = s[j], s[i]
   210  }
   211  func (s instanceSorter) Less(i, j int) bool {
   212  	insI, insJ := s[i], s[j]
   213  	// using ip and port to sort
   214  	ipNum1, _ := strconv.Atoi(strings.ReplaceAll(insI.Ip, ".", ""))
   215  	ipNum2, _ := strconv.Atoi(strings.ReplaceAll(insJ.Ip, ".", ""))
   216  	if ipNum1 < ipNum2 {
   217  		return true
   218  	}
   219  	if insI.Port < insJ.Port {
   220  		return true
   221  	}
   222  	return false
   223  }
   224  
   225  // sort instances
   226  func sortInstance(instances []model.Instance) {
   227  	sort.Sort(instanceSorter(instances))
   228  }