github.com/polarismesh/polaris@v1.17.8/service/healthcheck/cache.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 healthcheck
    19  
    20  import (
    21  	"runtime"
    22  
    23  	apiservice "github.com/polarismesh/specification/source/go/api/v1/service_manage"
    24  
    25  	"github.com/polarismesh/polaris/common/hash"
    26  	commonhash "github.com/polarismesh/polaris/common/hash"
    27  	"github.com/polarismesh/polaris/common/model"
    28  	"github.com/polarismesh/polaris/common/utils"
    29  	"github.com/polarismesh/polaris/plugin"
    30  )
    31  
    32  var DefaultShardSize uint32
    33  
    34  func init() {
    35  	DefaultShardSize = uint32(runtime.GOMAXPROCS(0) * 16)
    36  	// Different machines can adjust this parameter of 16.In more cases, 16 is suitable ,
    37  	// can test it in shardmap_test.go
    38  }
    39  
    40  // CacheProvider provider health check objects for service cache
    41  type CacheProvider struct {
    42  	svr                  *Server
    43  	selfService          string
    44  	selfServiceInstances *utils.SegmentMap[string, ItemWithChecker]
    45  	healthCheckInstances *utils.SegmentMap[string, ItemWithChecker]
    46  	healthCheckClients   *utils.SegmentMap[string, ItemWithChecker]
    47  }
    48  
    49  // CacheEvent provides the event for cache changes
    50  type CacheEvent struct {
    51  	healthCheckInstancesChanged bool
    52  	selfServiceInstancesChanged bool
    53  	healthCheckClientChanged    bool
    54  }
    55  
    56  func newCacheProvider(selfService string, svr *Server) *CacheProvider {
    57  	return &CacheProvider{
    58  		svr:                  svr,
    59  		selfService:          selfService,
    60  		selfServiceInstances: utils.NewSegmentMap[string, ItemWithChecker](1, hash.Fnv32),
    61  		healthCheckInstances: utils.NewSegmentMap[string, ItemWithChecker](int(DefaultShardSize), hash.Fnv32),
    62  		healthCheckClients:   utils.NewSegmentMap[string, ItemWithChecker](int(DefaultShardSize), hash.Fnv32),
    63  	}
    64  }
    65  
    66  func (c *CacheProvider) isSelfServiceInstance(instance *apiservice.Instance) bool {
    67  	metadata := instance.GetMetadata()
    68  	if svcName, ok := metadata[model.MetaKeyPolarisService]; ok {
    69  		return svcName == c.selfService
    70  	}
    71  	return false
    72  }
    73  
    74  // OnCreated callback when cache value created
    75  func (c *CacheProvider) OnCreated(value interface{}) {
    76  	switch actual := value.(type) {
    77  	case *model.Instance:
    78  		instProto := actual.Proto
    79  		if c.isSelfServiceInstance(instProto) {
    80  			storeServiceInstance(newInstanceWithChecker(actual, nil), c.selfServiceInstances)
    81  			c.sendEvent(CacheEvent{selfServiceInstancesChanged: true})
    82  			return
    83  		}
    84  		hcEnable, checker := c.isHealthCheckEnable(instProto)
    85  		if !hcEnable {
    86  			return
    87  		}
    88  		storeServiceInstance(newInstanceWithChecker(actual, checker), c.healthCheckInstances)
    89  		c.sendEvent(CacheEvent{healthCheckInstancesChanged: true})
    90  	case *model.Client:
    91  		checker, ok := c.getHealthChecker(apiservice.HealthCheck_HEARTBEAT)
    92  		if !ok {
    93  			return
    94  		}
    95  		storeClient(newClientWithChecker(actual, checker), c.healthCheckClients)
    96  		c.sendEvent(CacheEvent{healthCheckClientChanged: true})
    97  	}
    98  }
    99  
   100  func (c *CacheProvider) getHealthChecker(hcType apiservice.HealthCheck_HealthCheckType) (plugin.HealthChecker, bool) {
   101  	checker, ok := c.svr.checkers[int32(hcType)]
   102  	return checker, ok
   103  }
   104  
   105  func (c *CacheProvider) isHealthCheckEnable(instance *apiservice.Instance) (bool, plugin.HealthChecker) {
   106  	if !instance.GetEnableHealthCheck().GetValue() || instance.GetHealthCheck() == nil {
   107  		return false, nil
   108  	}
   109  	checker, ok := c.getHealthChecker(instance.GetHealthCheck().GetType())
   110  	if !ok {
   111  		return false, nil
   112  	}
   113  	return true, checker
   114  }
   115  
   116  // OnUpdated callback when cache value updated
   117  func (c *CacheProvider) OnUpdated(value interface{}) {
   118  	switch actual := value.(type) {
   119  	case *model.Instance:
   120  		instProto := actual.Proto
   121  		if c.isSelfServiceInstance(instProto) {
   122  			if compareAndStoreServiceInstance(newInstanceWithChecker(actual, nil), c.selfServiceInstances) {
   123  				c.sendEvent(CacheEvent{selfServiceInstancesChanged: true})
   124  			}
   125  			return
   126  		}
   127  		// check exists
   128  		instanceId := actual.ID()
   129  		value, exists := c.healthCheckInstances.Get(instanceId)
   130  		hcEnable, checker := c.isHealthCheckEnable(instProto)
   131  		if !hcEnable {
   132  			if !exists {
   133  				// instance is unhealthy, not exist, just return.
   134  				return
   135  			}
   136  			log.Infof("[Health Check][Cache]delete health check disabled instance is %s:%d, id is %s",
   137  				actual.Host(), actual.Port(), instanceId)
   138  			// instance is unhealthy, but exist, delete it.
   139  			ok := c.healthCheckInstances.Del(instanceId)
   140  			if ok {
   141  				c.sendEvent(CacheEvent{healthCheckInstancesChanged: true})
   142  			}
   143  			return
   144  		}
   145  		var noChanged bool
   146  		if exists {
   147  			// instance is healthy, exists, consistent healthCheckInstance.Revision(), no need to change。
   148  			healthCheckInstance := value.GetInstance()
   149  			noChanged = healthCheckInstance.Revision() == actual.Revision()
   150  		}
   151  		if !noChanged {
   152  			log.Infof("[Health Check][Cache]update service instance is %s:%d, id is %s",
   153  				actual.Host(), actual.Port(), instanceId)
   154  			//   In the concurrent scenario, when the healthCheckInstance.Revision() of the same health instance is the same,
   155  			//   if it arrives here at the same time, it will be saved multiple times
   156  			c.healthCheckInstances.Put(instanceId, newInstanceWithChecker(actual, checker))
   157  			c.sendEvent(CacheEvent{healthCheckInstancesChanged: true})
   158  		}
   159  	case *model.Client:
   160  		checker, ok := c.getHealthChecker(apiservice.HealthCheck_HEARTBEAT)
   161  		if !ok {
   162  			return
   163  		}
   164  		if compareAndStoreClient(newClientWithChecker(actual, checker), c.healthCheckClients) {
   165  			c.sendEvent(CacheEvent{healthCheckClientChanged: true})
   166  		}
   167  	}
   168  }
   169  
   170  // OnDeleted callback when cache value deleted
   171  func (c *CacheProvider) OnDeleted(value interface{}) {
   172  	switch actual := value.(type) {
   173  	case *model.Instance:
   174  		instProto := actual.Proto
   175  		if c.isSelfServiceInstance(instProto) {
   176  			deleteServiceInstance(instProto, c.selfServiceInstances)
   177  			c.sendEvent(CacheEvent{selfServiceInstancesChanged: true})
   178  			return
   179  		}
   180  		if !instProto.GetEnableHealthCheck().GetValue() || instProto.GetHealthCheck() == nil {
   181  			return
   182  		}
   183  		deleteServiceInstance(instProto, c.healthCheckInstances)
   184  		c.sendEvent(CacheEvent{healthCheckInstancesChanged: true})
   185  	case *model.Client:
   186  		deleteClient(actual.Proto(), c.healthCheckClients)
   187  		c.sendEvent(CacheEvent{healthCheckClientChanged: true})
   188  	}
   189  }
   190  
   191  // OnBatchCreated callback when cache value created
   192  func (c *CacheProvider) OnBatchCreated(value interface{}) {
   193  
   194  }
   195  
   196  // OnBatchUpdated callback when cache value updated
   197  func (c *CacheProvider) OnBatchUpdated(value interface{}) {
   198  
   199  }
   200  
   201  // OnBatchDeleted callback when cache value deleted
   202  func (c *CacheProvider) OnBatchDeleted(value interface{}) {
   203  
   204  }
   205  
   206  // RangeHealthCheckInstances range loop values
   207  func (c *CacheProvider) RangeHealthCheckInstances(check func(itemChecker ItemWithChecker, ins *model.Instance)) {
   208  	c.healthCheckInstances.Range(func(instanceId string, value ItemWithChecker) {
   209  		check(value, value.GetInstance())
   210  	})
   211  }
   212  
   213  // RangeHealthCheckClients range loop values
   214  func (c *CacheProvider) RangeHealthCheckClients(check func(itemChecker ItemWithChecker, client *model.Client)) {
   215  	c.healthCheckClients.Range(func(instanceId string, value ItemWithChecker) {
   216  		check(value, value.GetClient())
   217  	})
   218  }
   219  
   220  // RangeSelfServiceInstances range loop selfServiceInstances
   221  func (c *CacheProvider) RangeSelfServiceInstances(check func(instance *apiservice.Instance)) {
   222  	c.selfServiceInstances.Range(func(instanceId string, value ItemWithChecker) {
   223  		check(value.GetInstance().Proto)
   224  	})
   225  }
   226  
   227  // GetSelfServiceInstance get self service instance by id
   228  func (c *CacheProvider) GetSelfServiceInstance(instanceId string) *model.Instance {
   229  	value, ok := c.selfServiceInstances.Get(instanceId)
   230  	if !ok {
   231  		return nil
   232  	}
   233  
   234  	ins := value.GetInstance()
   235  	if ins == nil {
   236  		return nil
   237  	}
   238  	return ins
   239  }
   240  
   241  // GetInstance get instance by id
   242  func (c *CacheProvider) GetInstance(instanceId string) *model.Instance {
   243  	value, ok := c.healthCheckInstances.Get(instanceId)
   244  	if !ok {
   245  		return nil
   246  	}
   247  
   248  	ins := value.GetInstance()
   249  	if ins == nil {
   250  		return nil
   251  	}
   252  	return ins
   253  }
   254  
   255  // GetInstance get instance by id
   256  func (c *CacheProvider) GetClient(clientId string) *model.Client {
   257  	value, ok := c.healthCheckClients.Get(clientId)
   258  	if !ok {
   259  		return nil
   260  	}
   261  
   262  	client := value.GetClient()
   263  	if client == nil {
   264  		return nil
   265  	}
   266  	return client
   267  }
   268  
   269  func (c *CacheProvider) sendEvent(event CacheEvent) {
   270  	c.svr.dispatcher.UpdateStatusByEvent(event)
   271  }
   272  
   273  func compareAndStoreServiceInstance(instanceWithChecker *InstanceWithChecker,
   274  	values *utils.SegmentMap[string, ItemWithChecker]) bool {
   275  	instanceId := instanceWithChecker.instance.ID()
   276  	value, isNew := values.PutIfAbsent(instanceId, instanceWithChecker)
   277  	if isNew {
   278  		log.Infof("[Health Check][Cache]create service instance is %s:%d, id is %s",
   279  			instanceWithChecker.instance.Host(), instanceWithChecker.instance.Port(),
   280  			instanceId)
   281  		return true
   282  	}
   283  	instanceValue := value.(*InstanceWithChecker)
   284  	lastInstance := instanceValue.instance
   285  	if lastInstance.Revision() == instanceWithChecker.instance.Revision() {
   286  		return false
   287  	}
   288  	log.Infof("[Health Check][Cache]update service instance is %s:%d, id is %s",
   289  		instanceWithChecker.instance.Host(), instanceWithChecker.instance.Port(), instanceId)
   290  	// In the concurrent scenario, when the key and version are the same,
   291  	// if they arrive here at the same time, they will be saved multiple times.
   292  	values.Put(instanceId, instanceWithChecker)
   293  	return true
   294  }
   295  
   296  func storeServiceInstance(instanceWithChecker *InstanceWithChecker,
   297  	values *utils.SegmentMap[string, ItemWithChecker]) bool {
   298  	log.Infof("[Health Check][Cache]create service instance is %s:%d, id is %s",
   299  		instanceWithChecker.instance.Host(), instanceWithChecker.instance.Port(),
   300  		instanceWithChecker.instance.ID())
   301  	instanceId := instanceWithChecker.instance.ID()
   302  	values.Put(instanceId, instanceWithChecker)
   303  	return true
   304  }
   305  
   306  func deleteServiceInstance(instance *apiservice.Instance, values *utils.SegmentMap[string, ItemWithChecker]) bool {
   307  	instanceId := instance.GetId().GetValue()
   308  	ok := values.Del(instanceId)
   309  	if ok {
   310  		log.Infof("[Health Check][Cache]delete service instance is %s:%d, id is %s",
   311  			instance.GetHost().GetValue(), instance.GetPort().GetValue(), instanceId)
   312  	}
   313  	return true
   314  }
   315  
   316  func compareAndStoreClient(clientWithChecker *ClientWithChecker,
   317  	values *utils.SegmentMap[string, ItemWithChecker]) bool {
   318  	clientId := clientWithChecker.client.Proto().GetId().GetValue()
   319  	_, isNew := values.PutIfAbsent(clientId, clientWithChecker)
   320  	if isNew {
   321  		log.Infof("[Health Check][Cache]create client is %s, id is %s",
   322  			clientWithChecker.client.Proto().GetHost().GetValue(), clientId)
   323  		return true
   324  	}
   325  	return false
   326  }
   327  
   328  func storeClient(clientWithChecker *ClientWithChecker,
   329  	values *utils.SegmentMap[string, ItemWithChecker]) bool {
   330  
   331  	log.Infof("[Health Check][Cache]create client is %s, id is %s",
   332  		clientWithChecker.client.Proto().GetHost().GetValue(), clientWithChecker.client.Proto().GetId().GetValue())
   333  	clientId := clientWithChecker.client.Proto().GetId().GetValue()
   334  	values.Put(clientId, clientWithChecker)
   335  	return true
   336  }
   337  
   338  func deleteClient(client *apiservice.Client, values *utils.SegmentMap[string, ItemWithChecker]) bool {
   339  	clientId := client.GetId().GetValue()
   340  	ok := values.Del(clientId)
   341  	if ok {
   342  		log.Infof("[Health Check][Cache]delete client is %s, id is %s",
   343  			client.GetHost().GetValue(), clientId)
   344  	}
   345  	return true
   346  }
   347  
   348  // ItemWithChecker item and checker combine
   349  // GetInstance 与 GetClient 互斥
   350  type ItemWithChecker interface {
   351  	// GetInstance 获取服务实例
   352  	GetInstance() *model.Instance
   353  	// GetClient 获取上报客户端信息
   354  	GetClient() *model.Client
   355  	// GetChecker 获取对应的 checker 对象
   356  	GetChecker() plugin.HealthChecker
   357  	// GetHashValue 获取 hashvalue 信息
   358  	GetHashValue() uint
   359  }
   360  
   361  // InstanceWithChecker instance and checker combine
   362  type InstanceWithChecker struct {
   363  	instance  *model.Instance
   364  	checker   plugin.HealthChecker
   365  	hashValue uint
   366  }
   367  
   368  // GetInstance 获取服务实例
   369  func (ic *InstanceWithChecker) GetInstance() *model.Instance {
   370  	return ic.instance
   371  }
   372  
   373  // GetClient 获取上报客户端信息
   374  func (ic *InstanceWithChecker) GetClient() *model.Client {
   375  	return nil
   376  }
   377  
   378  // GetChecker 获取对应的 checker 对象
   379  func (ic *InstanceWithChecker) GetChecker() plugin.HealthChecker {
   380  	return ic.checker
   381  }
   382  
   383  // GetHashValue 获取 hashvalue 信息
   384  func (ic *InstanceWithChecker) GetHashValue() uint {
   385  	return ic.hashValue
   386  }
   387  
   388  func newInstanceWithChecker(instance *model.Instance, checker plugin.HealthChecker) *InstanceWithChecker {
   389  	return &InstanceWithChecker{
   390  		instance:  instance,
   391  		checker:   checker,
   392  		hashValue: commonhash.HashString(instance.ID()),
   393  	}
   394  }
   395  
   396  // ClientWithChecker instance and checker combine
   397  type ClientWithChecker struct {
   398  	client    *model.Client
   399  	checker   plugin.HealthChecker
   400  	hashValue uint
   401  }
   402  
   403  // GetInstance 获取服务实例
   404  func (ic *ClientWithChecker) GetInstance() *model.Instance {
   405  	return nil
   406  }
   407  
   408  // GetClient 获取上报客户端信息
   409  func (ic *ClientWithChecker) GetClient() *model.Client {
   410  	return ic.client
   411  }
   412  
   413  // GetChecker 获取对应的 checker 对象
   414  func (ic *ClientWithChecker) GetChecker() plugin.HealthChecker {
   415  	return ic.checker
   416  }
   417  
   418  // GetHashValue 获取 hashvalue 信息
   419  func (ic *ClientWithChecker) GetHashValue() uint {
   420  	return ic.hashValue
   421  }
   422  
   423  func newClientWithChecker(client *model.Client, checker plugin.HealthChecker) *ClientWithChecker {
   424  	return &ClientWithChecker{
   425  		client:    client,
   426  		checker:   checker,
   427  		hashValue: commonhash.HashString(client.Proto().GetId().GetValue()),
   428  	}
   429  }