github.com/polarismesh/polaris@v1.17.8/cache/config/config_file.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 config
    19  
    20  import (
    21  	"errors"
    22  	"os"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  	"time"
    27  
    28  	"go.etcd.io/bbolt"
    29  	"go.uber.org/zap"
    30  	"golang.org/x/sync/singleflight"
    31  
    32  	types "github.com/polarismesh/polaris/cache/api"
    33  	"github.com/polarismesh/polaris/common/eventhub"
    34  	"github.com/polarismesh/polaris/common/model"
    35  	"github.com/polarismesh/polaris/common/utils"
    36  	"github.com/polarismesh/polaris/store"
    37  )
    38  
    39  // FileCache 文件缓存,使用 loading cache 懒加载策略。同时写入时设置过期时间,定时清理过期的缓存。
    40  type fileCache struct {
    41  	*types.BaseCache
    42  	storage store.Store
    43  	// releases config_release.id -> model.SimpleConfigFileRelease
    44  	releases *utils.SegmentMap[uint64, *model.SimpleConfigFileRelease]
    45  	// name2release namespace -> group -> file_name -> []model.ConfigFileRelease
    46  	name2release *utils.SyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string,
    47  		*utils.SyncMap[string, *model.SimpleConfigFileRelease]]]]
    48  	// activeReleases namespace -> group -> []model.ConfigFileRelease
    49  	activeReleases *utils.SyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string, *model.SimpleConfigFileRelease]]]
    50  	// groupedActiveReleaseRevisions namespace -> group -> revision
    51  	activeReleaseRevisions *utils.SyncMap[string, *utils.SyncMap[string, string]]
    52  	// singleGroup
    53  	singleGroup *singleflight.Group
    54  	// valueCache save ConfigFileRelease.Content into local file to reduce memory use
    55  	valueCache *bbolt.DB
    56  	// metricsReleaseCount
    57  	metricsReleaseCount *utils.SyncMap[string, *utils.SyncMap[string, uint64]]
    58  	// preMetricsFiles
    59  	preMetricsFiles *utils.AtomicValue[map[string]map[string]struct{}]
    60  	// lastReportTime
    61  	lastReportTime *utils.AtomicValue[time.Time]
    62  }
    63  
    64  // NewConfigFileCache 创建文件缓存
    65  func NewConfigFileCache(storage store.Store, cacheMgr types.CacheManager) types.ConfigFileCache {
    66  	cache := &fileCache{
    67  		BaseCache: types.NewBaseCache(storage, cacheMgr),
    68  		storage:   storage,
    69  	}
    70  	return cache
    71  }
    72  
    73  // Initialize
    74  func (fc *fileCache) Initialize(opt map[string]interface{}) error {
    75  	fc.releases = utils.NewSegmentMap[uint64, *model.SimpleConfigFileRelease](1, func(k uint64) int {
    76  		return int(k)
    77  	})
    78  	fc.name2release = utils.NewSyncMap[string, *utils.SyncMap[string, *utils.SyncMap[string,
    79  		*utils.SyncMap[string, *model.SimpleConfigFileRelease]]]]()
    80  	fc.activeReleases = utils.NewSyncMap[string, *utils.SyncMap[string,
    81  		*utils.SyncMap[string, *model.SimpleConfigFileRelease]]]()
    82  	fc.activeReleaseRevisions = utils.NewSyncMap[string, *utils.SyncMap[string, string]]()
    83  	fc.singleGroup = &singleflight.Group{}
    84  	fc.metricsReleaseCount = utils.NewSyncMap[string, *utils.SyncMap[string, uint64]]()
    85  	fc.preMetricsFiles = utils.NewAtomicValue[map[string]map[string]struct{}](map[string]map[string]struct{}{})
    86  	fc.lastReportTime = utils.NewAtomicValue[time.Time](time.Time{})
    87  	valueCache, err := openBoltCache(opt)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	fc.valueCache = valueCache
    92  	return nil
    93  }
    94  
    95  func openBoltCache(opt map[string]interface{}) (*bbolt.DB, error) {
    96  	path, _ := opt["cachePath"].(string)
    97  	if path == "" {
    98  		path = "./data/cache/config"
    99  	}
   100  	if err := os.MkdirAll(path, os.ModePerm); err != nil {
   101  		return nil, err
   102  	}
   103  	dbFile := filepath.Join(path, "config_file.bolt")
   104  	_ = os.Remove(dbFile)
   105  	valueCache, err := bbolt.Open(dbFile, os.ModePerm, &bbolt.Options{})
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return valueCache, nil
   110  }
   111  
   112  // Update 更新缓存函数
   113  func (fc *fileCache) Update() error {
   114  	err, _ := fc.singleUpdate()
   115  	return err
   116  }
   117  
   118  func (fc *fileCache) singleUpdate() (error, bool) {
   119  	// 多个线程竞争,只有一个线程进行更新
   120  	_, err, shared := fc.singleGroup.Do(fc.Name(), func() (interface{}, error) {
   121  		defer func() {
   122  			fc.reportMetricsInfo()
   123  		}()
   124  		return nil, fc.DoCacheUpdate(fc.Name(), fc.realUpdate)
   125  	})
   126  	return err, shared
   127  }
   128  
   129  func (fc *fileCache) realUpdate() (map[string]time.Time, int64, error) {
   130  	// 拉取diff前的所有数据
   131  	start := time.Now()
   132  	releases, err := fc.storage.GetMoreReleaseFile(fc.IsFirstUpdate(), fc.LastFetchTime())
   133  	if err != nil {
   134  		return nil, 0, err
   135  	}
   136  	if len(releases) == 0 {
   137  		return nil, 0, nil
   138  	}
   139  
   140  	lastMimes, update, del, err := fc.setReleases(releases)
   141  	if err != nil {
   142  		return nil, 0, err
   143  	}
   144  	log.Info("[Cache][ConfigReleases] get more releases",
   145  		zap.Int("update", update), zap.Int("delete", del),
   146  		zap.Time("last", fc.LastMtime()), zap.Duration("used", time.Since(start)))
   147  	return lastMimes, int64(len(releases)), err
   148  }
   149  
   150  func (fc *fileCache) setReleases(releases []*model.ConfigFileRelease) (map[string]time.Time, int, int, error) {
   151  	lastMtime := fc.LastMtime().Unix()
   152  	update := 0
   153  	del := 0
   154  
   155  	affect := map[string]map[string]struct{}{}
   156  
   157  	for i := range releases {
   158  		item := releases[i]
   159  		if _, ok := affect[item.Namespace]; !ok {
   160  			affect[item.Namespace] = map[string]struct{}{}
   161  		}
   162  		affect[item.Namespace][item.Group] = struct{}{}
   163  
   164  		modifyUnix := item.ModifyTime.Unix()
   165  		if modifyUnix > lastMtime {
   166  			lastMtime = modifyUnix
   167  		}
   168  		oldVal, _ := fc.releases.Get(item.Id)
   169  		if !item.Valid {
   170  			del++
   171  			if err := fc.handleDeleteRelease(oldVal, item); err != nil {
   172  				return nil, update, del, err
   173  			}
   174  		} else {
   175  			update++
   176  			if err := fc.handleUpdateRelease(oldVal, item); err != nil {
   177  				return nil, update, del, err
   178  			}
   179  		}
   180  
   181  		if item.Active {
   182  			configLog.Info("[Config][Release][Cache] notify config release change",
   183  				zap.Any("info", item.SimpleConfigFileRelease))
   184  			fc.sendEvent(item)
   185  		}
   186  	}
   187  	fc.postProcessUpdatedRelease(affect)
   188  	return map[string]time.Time{fc.Name(): time.Unix(lastMtime, 0)}, update, del, nil
   189  }
   190  
   191  func (fc *fileCache) sendEvent(item *model.ConfigFileRelease) {
   192  	err := eventhub.Publish(eventhub.ConfigFilePublishTopic, &eventhub.PublishConfigFileEvent{
   193  		Message: item.SimpleConfigFileRelease,
   194  	})
   195  	if err != nil {
   196  		configLog.Error("[Config][Release][Cache] notify config release change",
   197  			zap.Any("info", item.ConfigFileReleaseKey), zap.Error(err))
   198  	}
   199  }
   200  
   201  // handleUpdateRelease
   202  func (fc *fileCache) handleUpdateRelease(oldVal *model.SimpleConfigFileRelease, item *model.ConfigFileRelease) error {
   203  	fc.releases.Put(item.Id, item.SimpleConfigFileRelease)
   204  
   205  	func() {
   206  		// 记录 namespace -> group -> file_name -> []SimpleRelease 信息
   207  		if _, ok := fc.name2release.Load(item.Namespace); !ok {
   208  			fc.name2release.Store(item.Namespace, utils.NewSyncMap[string,
   209  				*utils.SyncMap[string, *utils.SyncMap[string, *model.SimpleConfigFileRelease]]]())
   210  		}
   211  		namespace, _ := fc.name2release.Load(item.Namespace)
   212  		if _, ok := namespace.Load(item.Group); !ok {
   213  			namespace.Store(item.Group, utils.NewSyncMap[string, *utils.SyncMap[string, *model.SimpleConfigFileRelease]]())
   214  		}
   215  		group, _ := namespace.Load(item.Group)
   216  		group.ComputeIfAbsent(item.FileName, func(k string) *utils.SyncMap[string, *model.SimpleConfigFileRelease] {
   217  			return utils.NewSyncMap[string, *model.SimpleConfigFileRelease]()
   218  		})
   219  		files, _ := group.Load(item.FileName)
   220  		files.Store(item.Name, item.SimpleConfigFileRelease)
   221  	}()
   222  
   223  	if !item.Active {
   224  		return nil
   225  	}
   226  
   227  	func() {
   228  		// 保存 active 状态的所有发布 release 信息
   229  		if _, ok := fc.activeReleases.Load(item.Namespace); !ok {
   230  			fc.activeReleases.Store(item.Namespace, utils.NewSyncMap[string,
   231  				*utils.SyncMap[string, *model.SimpleConfigFileRelease]]())
   232  		}
   233  		namespace, _ := fc.activeReleases.Load(item.Namespace)
   234  		if _, ok := namespace.Load(item.Group); !ok {
   235  			namespace.Store(item.Group, utils.NewSyncMap[string, *model.SimpleConfigFileRelease]())
   236  		}
   237  		group, _ := namespace.Load(item.Group)
   238  		group.Store(item.ActiveKey(), item.SimpleConfigFileRelease)
   239  	}()
   240  
   241  	if err := fc.valueCache.Update(func(tx *bbolt.Tx) error {
   242  		bucket, err := tx.CreateBucketIfNotExists([]byte(item.OwnerKey()))
   243  		if err != nil {
   244  			return err
   245  		}
   246  		return bucket.Put([]byte(item.ActiveKey()), []byte(item.Content))
   247  	}); err != nil {
   248  		return errors.Join(err, errors.New("persistent config_file content fail"))
   249  	}
   250  	return nil
   251  }
   252  
   253  // handleDeleteRelease
   254  func (fc *fileCache) handleDeleteRelease(oldVal *model.SimpleConfigFileRelease, item *model.ConfigFileRelease) error {
   255  	fc.releases.Del(item.Id)
   256  
   257  	func() {
   258  		// 记录 namespace -> group -> file_name -> []SimpleRelease 信息
   259  		if _, ok := fc.name2release.Load(item.Namespace); !ok {
   260  			return
   261  		}
   262  		namespace, _ := fc.name2release.Load(item.Namespace)
   263  		if _, ok := namespace.Load(item.Group); !ok {
   264  			return
   265  		}
   266  		group, _ := namespace.Load(item.Group)
   267  		if _, ok := group.Load(item.FileName); !ok {
   268  			return
   269  		}
   270  
   271  		files, _ := group.Load(item.FileName)
   272  		files.Delete(item.Name)
   273  
   274  		if files.Len() == 0 {
   275  			group.Delete(item.FileName)
   276  		}
   277  	}()
   278  
   279  	if oldVal == nil {
   280  		return nil
   281  	}
   282  	if !oldVal.Active {
   283  		return nil
   284  	}
   285  	if namespace, ok := fc.activeReleases.Load(item.Namespace); ok {
   286  		if group, ok := namespace.Load(item.Group); ok {
   287  			group.Delete(item.ActiveKey())
   288  		}
   289  	}
   290  	if err := fc.valueCache.Update(func(tx *bbolt.Tx) error {
   291  		bucket := tx.Bucket([]byte(item.OwnerKey()))
   292  		if bucket == nil {
   293  			return nil
   294  		}
   295  		return bucket.Delete([]byte(item.ActiveKey()))
   296  	}); err != nil {
   297  		return errors.Join(err, errors.New("remove config_file content fail"))
   298  	}
   299  	return nil
   300  }
   301  
   302  // postProcessUpdatedRelease
   303  func (fc *fileCache) postProcessUpdatedRelease(affect map[string]map[string]struct{}) {
   304  	for ns, groups := range affect {
   305  		nsBucket, ok := fc.name2release.Load(ns)
   306  		if !ok {
   307  			continue
   308  		}
   309  		if _, ok := fc.metricsReleaseCount.Load(ns); !ok {
   310  			fc.metricsReleaseCount.Store(ns, utils.NewSyncMap[string, uint64]())
   311  		}
   312  		nsMetric, _ := fc.metricsReleaseCount.Load(ns)
   313  		for group := range groups {
   314  			groupBucket, ok := nsBucket.Load(group)
   315  			if !ok {
   316  				continue
   317  			}
   318  			nsMetric.Store(group, uint64(groupBucket.Len()))
   319  		}
   320  	}
   321  }
   322  
   323  func (fc *fileCache) LastMtime() time.Time {
   324  	return fc.BaseCache.LastMtime(fc.Name())
   325  }
   326  
   327  // Clear
   328  func (fc *fileCache) Clear() error {
   329  	return nil
   330  }
   331  
   332  func (fc *fileCache) Close() error {
   333  	if fc.valueCache != nil {
   334  		if err := fc.valueCache.Close(); err != nil {
   335  			return err
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  // name
   342  func (fc *fileCache) Name() string {
   343  	return types.ConfigFileCacheName
   344  }
   345  
   346  // GetActiveRelease
   347  func (fc *fileCache) GetGroupActiveReleases(namespace, group string) ([]*model.ConfigFileRelease, string) {
   348  	nsBucket, ok := fc.activeReleases.Load(namespace)
   349  	if !ok {
   350  		return nil, ""
   351  	}
   352  	groupBucket, ok := nsBucket.Load(group)
   353  	if !ok {
   354  		return nil, ""
   355  	}
   356  	ret := make([]*model.ConfigFileRelease, 0, 8)
   357  	groupBucket.Range(func(key string, val *model.SimpleConfigFileRelease) bool {
   358  		ret = append(ret, &model.ConfigFileRelease{
   359  			SimpleConfigFileRelease: val,
   360  		})
   361  		return true
   362  	})
   363  	groupRevisions, ok := fc.activeReleaseRevisions.Load(namespace)
   364  	if !ok {
   365  		return ret, utils.NewUUID()
   366  	}
   367  	revision, _ := groupRevisions.Load(group)
   368  	return ret, revision
   369  }
   370  
   371  // GetActiveRelease
   372  func (fc *fileCache) GetActiveRelease(namespace, group, fileName string) *model.ConfigFileRelease {
   373  	nsBucket, ok := fc.activeReleases.Load(namespace)
   374  	if !ok {
   375  		return nil
   376  	}
   377  	groupBucket, ok := nsBucket.Load(group)
   378  	if !ok {
   379  		return nil
   380  	}
   381  	searchKey := &model.ConfigFileReleaseKey{
   382  		Namespace: namespace,
   383  		Group:     group,
   384  		FileName:  fileName,
   385  	}
   386  	simple, ok := groupBucket.Load(searchKey.ActiveKey())
   387  	if !ok {
   388  		return nil
   389  	}
   390  	ret := &model.ConfigFileRelease{
   391  		SimpleConfigFileRelease: simple,
   392  	}
   393  	fc.loadValueCache(ret)
   394  	return ret
   395  }
   396  
   397  // GetRelease
   398  func (fc *fileCache) GetRelease(key model.ConfigFileReleaseKey) *model.ConfigFileRelease {
   399  	var (
   400  		simple *model.SimpleConfigFileRelease
   401  	)
   402  	if key.Id != 0 {
   403  		simple, _ = fc.releases.Get(key.Id)
   404  	} else {
   405  		nsB, ok := fc.name2release.Load(key.Namespace)
   406  		if !ok {
   407  			return nil
   408  		}
   409  		groupB, ok := nsB.Load(key.Group)
   410  		if !ok {
   411  			return nil
   412  		}
   413  		fileB, ok := groupB.Load(key.FileName)
   414  		if !ok {
   415  			return nil
   416  		}
   417  		simple, _ = fileB.Load(key.Name)
   418  	}
   419  	if simple == nil {
   420  		return nil
   421  	}
   422  	ret := &model.ConfigFileRelease{
   423  		SimpleConfigFileRelease: simple,
   424  	}
   425  	fc.loadValueCache(ret)
   426  	return ret
   427  }
   428  
   429  func (fc *fileCache) QueryReleases(args *types.ConfigReleaseArgs) (uint32, []*model.SimpleConfigFileRelease, error) {
   430  	if err := fc.Update(); err != nil {
   431  		return 0, nil, err
   432  	}
   433  
   434  	values := make([]*model.SimpleConfigFileRelease, 0, args.Limit)
   435  	fc.name2release.Range(func(namespace string, groups *utils.SyncMap[string, *utils.SyncMap[string,
   436  		*utils.SyncMap[string, *model.SimpleConfigFileRelease]]]) bool {
   437  
   438  		if args.Namespace != "" && utils.IsWildNotMatch(namespace, args.Namespace) {
   439  			return true
   440  		}
   441  		groups.Range(func(group string, files *utils.SyncMap[string, *utils.SyncMap[string,
   442  			*model.SimpleConfigFileRelease]]) bool {
   443  
   444  			if args.Group != "" && utils.IsWildNotMatch(group, args.Group) {
   445  				return true
   446  			}
   447  			files.Range(func(fileName string, releases *utils.SyncMap[string, *model.SimpleConfigFileRelease]) bool {
   448  				if args.FileName != "" && utils.IsWildNotMatch(fileName, args.FileName) {
   449  					return true
   450  				}
   451  				releases.Range(func(releaseName string, item *model.SimpleConfigFileRelease) bool {
   452  					if args.ReleaseName != "" && utils.IsWildNotMatch(item.Name, args.ReleaseName) {
   453  						return true
   454  					}
   455  					if args.OnlyActive && !item.Active {
   456  						return true
   457  					}
   458  					if len(args.Metadata) > 0 {
   459  						for k, v := range args.Metadata {
   460  							sv := item.Metadata[k]
   461  							if sv != v {
   462  								return true
   463  							}
   464  						}
   465  					}
   466  
   467  					values = append(values, item)
   468  					return true
   469  				})
   470  				return true
   471  			})
   472  			return true
   473  		})
   474  		return true
   475  	})
   476  
   477  	sort.Slice(values, func(i, j int) bool {
   478  		asc := strings.ToLower(args.OrderType) == "asc"
   479  		if strings.ToLower(args.OrderField) == "name" {
   480  			return orderByConfigReleaseName(values[i], values[j], asc)
   481  		}
   482  		if strings.ToLower(args.OrderField) == "mtime" {
   483  			return orderByConfigReleaseMtime(values[i], values[j], asc)
   484  		}
   485  		return orderByConfigReleaseVersion(values[i], values[j], asc)
   486  	})
   487  
   488  	return uint32(len(values)), doPageConfigReleases(values, args), nil
   489  }
   490  
   491  func orderByConfigReleaseName(a, b *model.SimpleConfigFileRelease, asc bool) bool {
   492  	if asc {
   493  		return a.Name <= b.Name
   494  	}
   495  	return a.Name > b.Name
   496  }
   497  
   498  func orderByConfigReleaseMtime(a, b *model.SimpleConfigFileRelease, asc bool) bool {
   499  	if asc {
   500  		return a.ModifyTime.Before(b.ModifyTime)
   501  	}
   502  	return a.ModifyTime.After(b.ModifyTime)
   503  }
   504  
   505  func orderByConfigReleaseVersion(a, b *model.SimpleConfigFileRelease, asc bool) bool {
   506  	if asc {
   507  		return a.Version < b.Version
   508  	}
   509  	return a.Version > b.Version
   510  }
   511  
   512  func doPageConfigReleases(values []*model.SimpleConfigFileRelease,
   513  	args *types.ConfigReleaseArgs) []*model.SimpleConfigFileRelease {
   514  
   515  	if args.NoPage {
   516  		return values
   517  	}
   518  
   519  	offset := args.Offset
   520  	limit := args.Limit
   521  
   522  	amount := uint32(len(values))
   523  	if offset >= amount || limit == 0 {
   524  		return nil
   525  	}
   526  	endIdx := offset + limit
   527  	if endIdx > amount {
   528  		endIdx = amount
   529  	}
   530  	return values[offset:endIdx]
   531  }
   532  
   533  func (fc *fileCache) loadValueCache(release *model.ConfigFileRelease) {
   534  	_ = fc.valueCache.View(func(tx *bbolt.Tx) error {
   535  		bucket := tx.Bucket([]byte(release.OwnerKey()))
   536  		if bucket == nil {
   537  			return nil
   538  		}
   539  		val := bucket.Get([]byte(release.ActiveKey()))
   540  		release.Content = string(val)
   541  		return nil
   542  	})
   543  }