github.com/polarismesh/polaris@v1.17.8/apiserver/eurekaserver/applications.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package eurekaserver
    19  
    20  import (
    21  	"encoding/json"
    22  	"encoding/xml"
    23  	"fmt"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage"
    31  
    32  	"github.com/polarismesh/polaris/common/model"
    33  	"github.com/polarismesh/polaris/service"
    34  )
    35  
    36  var (
    37  	getCacheServicesFunc  = getCacheServices
    38  	getCacheInstancesFunc = getCacheInstances
    39  )
    40  
    41  // ApplicationsRespCache 全量服务缓存
    42  type ApplicationsRespCache struct {
    43  	AppsResp      *ApplicationsResponse
    44  	Revision      string
    45  	JsonBytes     []byte
    46  	XmlBytes      []byte
    47  	createTimeSec int64
    48  }
    49  
    50  // ApplicationsBuilder builder to do application construct
    51  type ApplicationsBuilder struct {
    52  	namingServer           service.DiscoverServer
    53  	namespace              string
    54  	enableSelfPreservation bool
    55  	// autoincrement version
    56  	VersionIncrement int64
    57  }
    58  
    59  func getCacheServices(namingServer service.DiscoverServer, namespace string) map[string]*model.Service {
    60  	var newServices = make(map[string]*model.Service)
    61  	_ = namingServer.Cache().Service().IteratorServices(func(key string, value *model.Service) (bool, error) {
    62  		if value.Namespace == namespace {
    63  			newServices[value.Name] = value
    64  		}
    65  		return true, nil
    66  	})
    67  	return newServices
    68  }
    69  
    70  func getCacheInstances(namingServer service.DiscoverServer, svcId string) ([]*model.Instance, string, error) {
    71  	var instances []*model.Instance
    72  	_ = namingServer.Cache().Instance().IteratorInstancesWithService(svcId,
    73  		func(key string, value *model.Instance) (bool, error) {
    74  			instances = append(instances, value)
    75  			return true, nil
    76  		})
    77  	revision, err := namingServer.GetServiceInstanceRevision(svcId, instances)
    78  	return instances, revision, err
    79  }
    80  
    81  // BuildApplications build applications cache with compare to the latest cache
    82  func (a *ApplicationsBuilder) BuildApplications(oldAppsCache *ApplicationsRespCache) *ApplicationsRespCache {
    83  	// 获取所有的服务数据
    84  	var newServices = getCacheServicesFunc(a.namingServer, a.namespace)
    85  	var instCount int
    86  	svcToRevision := make(map[string]string, len(newServices))
    87  	svcToToInstances := make(map[string][]*model.Instance)
    88  	var changed bool
    89  	for _, newService := range newServices {
    90  		instances, revision, err := getCacheInstancesFunc(a.namingServer, newService.ID)
    91  		if err != nil {
    92  			eurekalog.Errorf("[EurekaServer]fail to get revision for service %s, err is %v", newService.Name, err)
    93  			continue
    94  		}
    95  		// eureka does not return services without instances
    96  		if len(instances) == 0 {
    97  			continue
    98  		}
    99  		instCount += len(instances)
   100  		svcName := formatReadName(newService.Name)
   101  		svcToRevision[svcName] = revision
   102  		svcToToInstances[svcName] = instances
   103  	}
   104  	// 比较并构建Applications缓存
   105  	hashBuilder := make(map[string]int)
   106  	newApps := newApplications()
   107  	var oldApps *Applications
   108  	if oldAppsCache != nil {
   109  		oldApps = oldAppsCache.AppsResp.Applications
   110  	}
   111  	for svc, instances := range svcToToInstances {
   112  		var newRevision = svcToRevision[svc]
   113  		var targetApp *Application
   114  		if oldApps != nil {
   115  			oldApp, ok := oldApps.ApplicationMap[svc]
   116  			if ok && len(oldApp.Revision) > 0 && oldApp.Revision == newRevision {
   117  				// 没有变化
   118  				targetApp = oldApp
   119  			}
   120  		}
   121  		if targetApp == nil {
   122  			// 重新构建
   123  			targetApp = &Application{
   124  				Name:        svc,
   125  				InstanceMap: make(map[string]*InstanceInfo),
   126  				Revision:    newRevision,
   127  			}
   128  			a.constructApplication(targetApp, instances)
   129  			changed = true
   130  		}
   131  		statusCount := targetApp.StatusCounts
   132  		if len(statusCount) > 0 {
   133  			for status, count := range statusCount {
   134  				hashBuilder[status] = hashBuilder[status] + count
   135  			}
   136  		}
   137  		if len(targetApp.Instance) > 0 {
   138  			newApps.Application = append(newApps.Application, targetApp)
   139  			newApps.ApplicationMap[targetApp.Name] = targetApp
   140  		}
   141  	}
   142  	if oldApps != nil && len(oldApps.Application) != len(newApps.Application) {
   143  		changed = true
   144  	}
   145  	a.buildVersionAndHashCode(changed, hashBuilder, newApps)
   146  	return constructResponseCache(newApps, instCount, false)
   147  }
   148  
   149  func (a *ApplicationsBuilder) constructApplication(app *Application, instances []*model.Instance) {
   150  	if len(instances) == 0 {
   151  		return
   152  	}
   153  	app.StatusCounts = make(map[string]int)
   154  
   155  	// 转换时候要区分2种情况,一种是从eureka注册上来的,一种不是
   156  	for _, instance := range instances {
   157  		if !instance.Healthy() {
   158  			continue
   159  		}
   160  		instanceInfo := buildInstance(app.Name, instance.Proto, instance.ModifyTime.UnixNano()/1e6)
   161  		status := instanceInfo.Status
   162  		app.StatusCounts[status] = app.StatusCounts[status] + 1
   163  		app.Instance = append(app.Instance, instanceInfo)
   164  		app.InstanceMap[instanceInfo.InstanceId] = instanceInfo
   165  	}
   166  	if nil == app.Instance {
   167  		app.Instance = []*InstanceInfo{}
   168  	}
   169  }
   170  
   171  func (a *ApplicationsBuilder) buildVersionAndHashCode(changed bool, hashBuilder map[string]int, newApps *Applications) {
   172  	var nextVersion int64
   173  	if changed {
   174  		nextVersion = atomic.AddInt64(&a.VersionIncrement, 1)
   175  	} else {
   176  		nextVersion = atomic.LoadInt64(&a.VersionIncrement)
   177  	}
   178  	buildHashCode(strconv.Itoa(int(nextVersion)), hashBuilder, newApps)
   179  }
   180  
   181  func buildHashCode(version string, hashBuilder map[string]int, newApps *Applications) {
   182  	// 构建hashValue
   183  	newApps.AppsHashCode = buildHashStr(hashBuilder)
   184  	newApps.VersionsDelta = version
   185  }
   186  
   187  func parseStatus(instance *apiservice.Instance) string {
   188  	if instance.GetIsolate().GetValue() {
   189  		status := instance.Metadata[InternalMetadataStatus]
   190  		switch status {
   191  		case StatusDown:
   192  			return StatusDown
   193  		default:
   194  			return StatusOutOfService
   195  		}
   196  	}
   197  	return StatusUp
   198  }
   199  
   200  func parsePortWrapper(info *InstanceInfo, instance *apiservice.Instance) {
   201  	metadata := instance.GetMetadata()
   202  	var securePortOk bool
   203  	var securePortEnabledOk bool
   204  	var securePort string
   205  	var securePortEnabled string
   206  	var insecurePortOk bool
   207  	var insecurePortEnabledOk bool
   208  	var insecurePort string
   209  	var insecurePortEnabled string
   210  	if len(metadata) > 0 {
   211  		securePort, securePortOk = instance.GetMetadata()[MetadataSecurePort]
   212  		securePortEnabled, securePortEnabledOk = instance.GetMetadata()[MetadataSecurePortEnabled]
   213  		insecurePort, insecurePortOk = instance.GetMetadata()[MetadataInsecurePort]
   214  		insecurePortEnabled, insecurePortEnabledOk = instance.GetMetadata()[MetadataInsecurePortEnabled]
   215  	}
   216  	if securePortOk && securePortEnabledOk && insecurePortOk && insecurePortEnabledOk {
   217  		// if metadata contains all port/securePort,port.enabled/securePort.enabled
   218  		sePort, err := strconv.Atoi(securePort)
   219  		if err != nil {
   220  			sePort = 0
   221  			eurekalog.Errorf("[EUREKA_SERVER]parse secure port error: %+v", err)
   222  		}
   223  		sePortEnabled, err := strconv.ParseBool(securePortEnabled)
   224  		if err != nil {
   225  			sePortEnabled = false
   226  			eurekalog.Errorf("[EUREKA_SERVER]parse secure port enabled error: %+v", err)
   227  		}
   228  
   229  		info.SecurePort.Port = sePort
   230  		info.SecurePort.Enabled = sePortEnabled
   231  
   232  		insePort, err := strconv.Atoi(insecurePort)
   233  		if err != nil {
   234  			insePort = 0
   235  			eurekalog.Errorf("[EUREKA_SERVER]parse insecure port error: %+v", err)
   236  		}
   237  		insePortEnabled, err := strconv.ParseBool(insecurePortEnabled)
   238  		if err != nil {
   239  			insePortEnabled = false
   240  			eurekalog.Errorf("[EUREKA_SERVER]parse insecure port enabled error: %+v", err)
   241  		}
   242  
   243  		info.Port.Port = insePort
   244  		info.Port.Enabled = insePortEnabled
   245  	} else {
   246  		protocol := instance.GetProtocol().GetValue()
   247  		port := instance.GetPort().GetValue()
   248  		if protocol == SecureProtocol {
   249  			info.SecurePort.Port = int(port)
   250  			info.SecurePort.Enabled = "true"
   251  			if len(metadata) > 0 {
   252  				if insecurePortStr, ok := metadata[MetadataInsecurePort]; ok {
   253  					insecurePort, _ := strconv.Atoi(insecurePortStr)
   254  					if insecurePort > 0 {
   255  						info.Port.Port = insecurePort
   256  						info.Port.Enabled = "true"
   257  					}
   258  				}
   259  			}
   260  		} else {
   261  			info.Port.Port = int(port)
   262  			info.Port.Enabled = "true"
   263  		}
   264  	}
   265  }
   266  
   267  func parseLeaseInfo(leaseInfo *LeaseInfo, instance *apiservice.Instance) {
   268  	var (
   269  		metadata         = instance.GetMetadata()
   270  		durationInSec    int
   271  		renewIntervalSec int
   272  	)
   273  	if metadata != nil {
   274  		durationInSecStr, ok := metadata[MetadataDuration]
   275  		if ok {
   276  			durationInSec, _ = strconv.Atoi(durationInSecStr)
   277  		}
   278  		renewIntervalStr, ok := metadata[MetadataRenewalInterval]
   279  		if ok {
   280  			renewIntervalSec, _ = strconv.Atoi(renewIntervalStr)
   281  		}
   282  	}
   283  	if durationInSec > 0 {
   284  		leaseInfo.DurationInSecs = durationInSec
   285  	}
   286  	if renewIntervalSec > 0 {
   287  		leaseInfo.RenewalIntervalInSecs = renewIntervalSec
   288  	}
   289  }
   290  
   291  func buildInstance(appName string, instance *apiservice.Instance, lastModifyTime int64) *InstanceInfo {
   292  	instanceInfo := &InstanceInfo{
   293  		CountryId: DefaultCountryIdInt,
   294  		Port: &PortWrapper{
   295  			Enabled: "false",
   296  			Port:    DefaultInsecurePort,
   297  		},
   298  		SecurePort: &PortWrapper{
   299  			Enabled: "false",
   300  			Port:    DefaultSSLPort,
   301  		},
   302  		LeaseInfo: &LeaseInfo{
   303  			RenewalIntervalInSecs: DefaultRenewInterval,
   304  			DurationInSecs:        DefaultDuration,
   305  		},
   306  		Metadata: &Metadata{
   307  			Meta: make(map[string]interface{}),
   308  		},
   309  		RealInstance: instance,
   310  	}
   311  	instanceInfo.AppName = appName
   312  	// 属于eureka注册的实例
   313  	instanceInfo.InstanceId = instance.GetId().GetValue()
   314  	metadata := instance.GetMetadata()
   315  	if metadata == nil {
   316  		metadata = map[string]string{}
   317  	}
   318  	if eurekaInstanceId, ok := metadata[MetadataInstanceId]; ok {
   319  		instanceInfo.InstanceId = eurekaInstanceId
   320  	}
   321  	if hostName, ok := metadata[MetadataHostName]; ok {
   322  		instanceInfo.HostName = hostName
   323  	}
   324  	instanceInfo.IpAddr = instance.GetHost().GetValue()
   325  	instanceInfo.Status = parseStatus(instance)
   326  	instanceInfo.OverriddenStatus = StatusUnknown
   327  	parsePortWrapper(instanceInfo, instance)
   328  	if countryIdStr, ok := metadata[MetadataCountryId]; ok {
   329  		cId, err := strconv.Atoi(countryIdStr)
   330  		if err == nil {
   331  			instanceInfo.CountryId = cId
   332  		}
   333  	}
   334  	dciClazz, ok1 := metadata[MetadataDataCenterInfoClazz]
   335  	dciName, ok2 := metadata[MetadataDataCenterInfoName]
   336  	if ok1 && ok2 {
   337  		instanceInfo.DataCenterInfo = &DataCenterInfo{
   338  			Clazz: dciClazz,
   339  			Name:  dciName,
   340  		}
   341  	} else {
   342  		instanceInfo.DataCenterInfo = buildDataCenterInfo()
   343  	}
   344  	parseLeaseInfo(instanceInfo.LeaseInfo, instance)
   345  	for metaKey, metaValue := range metadata {
   346  		if strings.HasPrefix(metaKey, "internal-") {
   347  			continue
   348  		}
   349  		instanceInfo.Metadata.Meta[metaKey] = metaValue
   350  	}
   351  	if url, ok := metadata[MetadataHomePageUrl]; ok {
   352  		instanceInfo.HomePageUrl = url
   353  	}
   354  	if url, ok := metadata[MetadataStatusPageUrl]; ok {
   355  		instanceInfo.StatusPageUrl = url
   356  	}
   357  	if url, ok := metadata[MetadataHealthCheckUrl]; ok {
   358  		instanceInfo.HealthCheckUrl = url
   359  	}
   360  	if address, ok := metadata[MetadataVipAddress]; ok {
   361  		instanceInfo.VipAddress = address
   362  	}
   363  	if address, ok := metadata[MetadataSecureVipAddress]; ok {
   364  		instanceInfo.SecureVipAddress = address
   365  	}
   366  	if instanceInfo.VipAddress == "" {
   367  		instanceInfo.VipAddress = appName
   368  	}
   369  	if instanceInfo.HostName == "" {
   370  		instanceInfo.HostName = instance.GetHost().GetValue()
   371  	}
   372  	buildLocationInfo(instanceInfo, instance)
   373  	instanceInfo.LastUpdatedTimestamp = strconv.Itoa(int(lastModifyTime))
   374  	instanceInfo.ActionType = ActionAdded
   375  	return instanceInfo
   376  }
   377  
   378  func buildDataCenterInfo() *DataCenterInfo {
   379  	customDciClass, ok1 := CustomEurekaParameters[CustomKeyDciClass]
   380  	customDciName, ok2 := CustomEurekaParameters[CustomKeyDciName]
   381  	if ok1 && ok2 {
   382  		return &DataCenterInfo{
   383  			Clazz: customDciClass,
   384  			Name:  customDciName,
   385  		}
   386  	} else if ok1 && !ok2 {
   387  		return &DataCenterInfo{
   388  			Clazz: customDciClass,
   389  			Name:  DefaultDciName,
   390  		}
   391  	} else if !ok1 && ok2 {
   392  		return &DataCenterInfo{
   393  			Clazz: DefaultDciClazz,
   394  			Name:  customDciName,
   395  		}
   396  	} else {
   397  		return DefaultDataCenterInfo
   398  	}
   399  }
   400  
   401  func buildLocationInfo(instanceInfo *InstanceInfo, instance *apiservice.Instance) {
   402  	var region string
   403  	var zone string
   404  	var campus string
   405  	if location := instance.GetLocation(); location != nil {
   406  		region = location.GetRegion().GetValue()
   407  		zone = location.GetZone().GetValue()
   408  		campus = location.GetCampus().GetValue()
   409  	}
   410  	if _, ok := instanceInfo.Metadata.Meta[KeyRegion]; !ok && len(region) > 0 {
   411  		instanceInfo.Metadata.Meta[KeyRegion] = region
   412  	}
   413  	if _, ok := instanceInfo.Metadata.Meta[keyZone]; !ok && len(zone) > 0 {
   414  		instanceInfo.Metadata.Meta[keyZone] = zone
   415  	}
   416  	if _, ok := instanceInfo.Metadata.Meta[keyCampus]; !ok && len(campus) > 0 {
   417  		instanceInfo.Metadata.Meta[keyCampus] = campus
   418  	}
   419  }
   420  
   421  func newApplications() *Applications {
   422  	return &Applications{
   423  		ApplicationMap: make(map[string]*Application),
   424  		Application:    make([]*Application, 0),
   425  	}
   426  }
   427  
   428  func constructResponseCache(newApps *Applications, instCount int, delta bool) *ApplicationsRespCache {
   429  	appsHashCode := newApps.AppsHashCode
   430  	newAppsCache := &ApplicationsRespCache{
   431  		AppsResp:      &ApplicationsResponse{Applications: newApps},
   432  		createTimeSec: time.Now().Unix(),
   433  	}
   434  	// 预先做一次序列化,以免高并发时候序列化会使得内存峰值过高
   435  	jsonBytes, err := json.MarshalIndent(newAppsCache.AppsResp, "", " ")
   436  	if err != nil {
   437  		eurekalog.Errorf("[EUREKA_SERVER]fail to marshal apps %s to json, err is %v", appsHashCode, err)
   438  	} else {
   439  		newAppsCache.JsonBytes = jsonBytes
   440  	}
   441  	xmlBytes, err := xml.MarshalIndent(newAppsCache.AppsResp.Applications, " ", " ")
   442  	if err != nil {
   443  		eurekalog.Errorf("[EUREKA_SERVER]fail to marshal apps %s to xml, err is %v", appsHashCode, err)
   444  	} else {
   445  		newAppsCache.XmlBytes = xmlBytes
   446  	}
   447  	if !delta && len(jsonBytes) > 0 {
   448  		newAppsCache.Revision = sha1s(jsonBytes)
   449  	}
   450  	eurekalog.Infof("[EUREKA_SERVER]success to build apps cache, delta is %v, "+
   451  		"length xmlBytes is %d, length jsonBytes is %d, instCount is %d", delta, len(xmlBytes), len(jsonBytes), instCount)
   452  	return newAppsCache
   453  }
   454  
   455  func buildHashStr(counts map[string]int) string {
   456  	if len(counts) == 0 {
   457  		return ""
   458  	}
   459  	slice := make([]string, 0, len(counts))
   460  	for k := range counts {
   461  		slice = append(slice, k)
   462  	}
   463  	sort.Strings(slice)
   464  	builder := &strings.Builder{}
   465  	for _, status := range slice {
   466  		builder.WriteString(fmt.Sprintf("%s_%d_", status, counts[status]))
   467  	}
   468  	return builder.String()
   469  }