github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/resolver/resolver.go (about)

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"hash/crc64"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strconv"
    12  	"sync"
    13  	"time"
    14  	"unsafe"
    15  
    16  	"github.com/qiniu/go-sdk/v7/internal/cache"
    17  	"github.com/qiniu/go-sdk/v7/internal/log"
    18  )
    19  
    20  type (
    21  	// Resolver 域名解析器的接口
    22  	Resolver interface {
    23  		// Resolve 解析域名的 IP 地址
    24  		Resolve(context.Context, string) ([]net.IP, error)
    25  
    26  		// FeedbackGood 反馈一批 IP 地址请求成功
    27  		FeedbackGood(context.Context, string, []net.IP)
    28  
    29  		// FeedbackBad 反馈一批 IP 地址请求失败
    30  		FeedbackBad(context.Context, string, []net.IP)
    31  	}
    32  
    33  	defaultResolver    struct{}
    34  	customizedResolver struct {
    35  		resolveFn func(context.Context, string) ([]net.IP, error)
    36  	}
    37  )
    38  
    39  // NewEmptyResolver 空的 DNS Resolver,相当于关闭 SDK 的 DNS 解析功能
    40  func NewEmptyResolver() Resolver {
    41  	return customizedResolver{
    42  		resolveFn: func(ctx context.Context, s string) ([]net.IP, error) {
    43  			return nil, nil
    44  		},
    45  	}
    46  }
    47  
    48  // NewResolver 创建自定义的域名解析器
    49  func NewResolver(fn func(context.Context, string) ([]net.IP, error)) Resolver {
    50  	return customizedResolver{resolveFn: fn}
    51  }
    52  
    53  func (resolver customizedResolver) Resolve(ctx context.Context, host string) ([]net.IP, error) {
    54  	return resolver.resolveFn(ctx, host)
    55  }
    56  
    57  func (resolver customizedResolver) FeedbackGood(context.Context, string, []net.IP) {}
    58  func (resolver customizedResolver) FeedbackBad(context.Context, string, []net.IP)  {}
    59  
    60  // NewDefaultResolver 创建默认的域名解析器
    61  func NewDefaultResolver() Resolver {
    62  	return &defaultResolver{}
    63  }
    64  
    65  func (resolver *defaultResolver) Resolve(ctx context.Context, host string) ([]net.IP, error) {
    66  	addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	ips := make([]net.IP, len(addrs))
    71  	for i, ia := range addrs {
    72  		ips[i] = ia.IP
    73  	}
    74  	return ips, nil
    75  }
    76  
    77  func (resolver defaultResolver) FeedbackGood(context.Context, string, []net.IP) {}
    78  func (resolver defaultResolver) FeedbackBad(context.Context, string, []net.IP)  {}
    79  
    80  type (
    81  	cacheResolver struct {
    82  		resolver          Resolver
    83  		cache             *cache.Cache
    84  		cacheLifetime     time.Duration
    85  		cacheRefreshAfter time.Duration
    86  		cacheMaxLifetime  time.Duration
    87  	}
    88  
    89  	// CacheResolverConfig 缓存域名解析器选项
    90  	CacheResolverConfig struct {
    91  		// 压缩周期(默认:60s)
    92  		CompactInterval time.Duration
    93  
    94  		// 持久化路径(默认:$TMPDIR/qiniu-golang-sdk/resolver_01.cache.json)
    95  		PersistentFilePath string
    96  
    97  		// 持久化周期(默认:60s)
    98  		PersistentDuration time.Duration
    99  
   100  		// 缓存有效期(默认:120s)
   101  		CacheLifetime time.Duration
   102  
   103  		// 缓存刷新时间(默认:80s)
   104  		CacheRefreshAfter time.Duration
   105  
   106  		// CacheMaxLifetime 限制 FeedbackGood 可推迟缓存刷新的最长期限,从首次解析时起算(默认:30min)
   107  		CacheMaxLifetime time.Duration
   108  	}
   109  
   110  	resolverCacheValueIP struct {
   111  		IP        net.IP    `json:"ip"`
   112  		ExpiredAt time.Time `json:"expired_at"`
   113  	}
   114  
   115  	resolverCacheValue struct {
   116  		mu           sync.RWMutex
   117  		IPs          []resolverCacheValueIP `json:"ips"`
   118  		RefreshAfter time.Time              `json:"refresh_after"`
   119  		ExpiredAt    time.Time              `json:"expired_at"`
   120  		CreatedAt    time.Time              `json:"created_at"`
   121  	}
   122  )
   123  
   124  const cacheFileName = "resolver_01.cache.json"
   125  
   126  var (
   127  	persistentCaches      map[uint64]*cache.Cache
   128  	persistentCachesLock  sync.Mutex
   129  	staticDefaultResolver Resolver = &defaultResolver{}
   130  )
   131  
   132  // NewCacheResolver 创建带缓存功能的域名解析器
   133  func NewCacheResolver(resolver Resolver, opts *CacheResolverConfig) (Resolver, error) {
   134  	if opts == nil {
   135  		opts = &CacheResolverConfig{}
   136  	}
   137  	compactInterval := opts.CompactInterval
   138  	if compactInterval == time.Duration(0) {
   139  		compactInterval = 60 * time.Second
   140  	}
   141  	persistentFilePath := opts.PersistentFilePath
   142  	if persistentFilePath == "" {
   143  		persistentFilePath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", cacheFileName)
   144  	}
   145  	persistentDuration := opts.PersistentDuration
   146  	if persistentDuration == time.Duration(0) {
   147  		persistentDuration = 60 * time.Second
   148  	}
   149  	cacheLifetime := opts.CacheLifetime
   150  	if cacheLifetime == time.Duration(0) {
   151  		cacheLifetime = 120 * time.Second
   152  	}
   153  	cacheRefreshAfter := opts.CacheRefreshAfter
   154  	if cacheRefreshAfter == time.Duration(0) {
   155  		cacheRefreshAfter = 80 * time.Second
   156  	}
   157  	cacheMaxLifetime := opts.CacheMaxLifetime
   158  	if cacheMaxLifetime == time.Duration(0) {
   159  		cacheMaxLifetime = 30 * time.Minute
   160  	}
   161  	if resolver == nil {
   162  		resolver = staticDefaultResolver
   163  	}
   164  
   165  	persistentCache, err := getPersistentCache(persistentFilePath, compactInterval, persistentDuration)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	return &cacheResolver{
   170  		cache:             persistentCache,
   171  		resolver:          resolver,
   172  		cacheLifetime:     cacheLifetime,
   173  		cacheRefreshAfter: cacheRefreshAfter,
   174  		cacheMaxLifetime:  cacheMaxLifetime,
   175  	}, nil
   176  }
   177  
   178  func getPersistentCache(persistentFilePath string, compactInterval, persistentDuration time.Duration) (*cache.Cache, error) {
   179  	var (
   180  		persistentCache *cache.Cache
   181  		ok              bool
   182  		err             error
   183  	)
   184  
   185  	crc64Value := calcPersistentCacheCrc64(persistentFilePath, compactInterval, persistentDuration)
   186  	persistentCachesLock.Lock()
   187  	defer persistentCachesLock.Unlock()
   188  
   189  	if persistentCaches == nil {
   190  		persistentCaches = make(map[uint64]*cache.Cache)
   191  	}
   192  	if persistentCache, ok = persistentCaches[crc64Value]; !ok {
   193  		persistentCache, err = cache.NewPersistentCache(
   194  			reflect.TypeOf(&resolverCacheValue{}),
   195  			persistentFilePath,
   196  			compactInterval,
   197  			persistentDuration,
   198  			func(err error) {
   199  				log.Warn(fmt.Sprintf("BucketRegionsQuery persist error: %s", err))
   200  			})
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		persistentCaches[crc64Value] = persistentCache
   205  	}
   206  	return persistentCache, nil
   207  }
   208  
   209  func (resolver *cacheResolver) resolve(ctx context.Context, host string) (*resolverCacheValue, error) {
   210  	if ips, err := resolver.resolver.Resolve(ctx, host); err != nil {
   211  		return nil, err
   212  	} else {
   213  		now := time.Now()
   214  		cacheValueIPs := make([]resolverCacheValueIP, len(ips))
   215  		for i, ip := range ips {
   216  			cacheValueIPs[i] = resolverCacheValueIP{IP: ip, ExpiredAt: now.Add(resolver.cacheLifetime)}
   217  		}
   218  		return &resolverCacheValue{
   219  			IPs:          cacheValueIPs,
   220  			RefreshAfter: now.Add(resolver.cacheRefreshAfter),
   221  			ExpiredAt:    now.Add(resolver.cacheLifetime),
   222  			CreatedAt:    now,
   223  		}, nil
   224  	}
   225  }
   226  
   227  func (resolver *cacheResolver) Resolve(ctx context.Context, host string) ([]net.IP, error) {
   228  	lip, err := resolver.localIp()
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	cacheKey := lip + ":" + host
   233  	var rcv *resolverCacheValue
   234  	if shouldByPassResolveCache(ctx) {
   235  		if rcv, err = resolver.resolve(ctx, host); err != nil {
   236  			return nil, err
   237  		}
   238  	} else {
   239  		cacheValue, status := resolver.cache.Get(cacheKey, func() (cache.CacheValue, error) {
   240  			var cacheValue cache.CacheValue
   241  			cacheValue, err = resolver.resolve(ctx, host)
   242  			return cacheValue, err
   243  		})
   244  		if status == cache.NoResultGot || status == cache.GetResultFromInvalidCache {
   245  			return nil, err
   246  		}
   247  		rcv = cacheValue.(*resolverCacheValue)
   248  	}
   249  	now := time.Now()
   250  	rcv.mu.RLock()
   251  	ips := make([]net.IP, 0, len(rcv.IPs))
   252  	expiredCount := 0
   253  	for _, cacheValueIP := range rcv.IPs {
   254  		if cacheValueIP.ExpiredAt.After(now) {
   255  			ips = append(ips, cacheValueIP.IP)
   256  		} else {
   257  			expiredCount++
   258  		}
   259  	}
   260  	rcv.mu.RUnlock()
   261  
   262  	// If some IPs expired, clean them up
   263  	if expiredCount > 0 {
   264  		rcv.mu.Lock()
   265  		// Re-check with fresh timestamp after acquiring write lock
   266  		nowLocked := time.Now()
   267  		validIPs := make([]resolverCacheValueIP, 0, len(rcv.IPs)-expiredCount)
   268  		for _, cacheValueIP := range rcv.IPs {
   269  			if cacheValueIP.ExpiredAt.After(nowLocked) {
   270  				validIPs = append(validIPs, cacheValueIP)
   271  			}
   272  		}
   273  		rcv.IPs = validIPs
   274  		rcv.mu.Unlock()
   275  	}
   276  
   277  	return ips, nil
   278  }
   279  
   280  func (resolver cacheResolver) FeedbackGood(ctx context.Context, host string, ips []net.IP) {
   281  	lip, err := resolver.localIp()
   282  	if err != nil {
   283  		return
   284  	}
   285  	cacheKey := lip + ":" + host
   286  	cacheValue, status := resolver.cache.Get(cacheKey, func() (cache.CacheValue, error) {
   287  		return nil, context.Canceled
   288  	})
   289  	if status == cache.GetResultFromCache || status == cache.GetResultFromCacheAndRefreshAsync {
   290  		rcv := cacheValue.(*resolverCacheValue)
   291  		now := time.Now()
   292  
   293  		rcv.mu.Lock()
   294  		defer rcv.mu.Unlock()
   295  
   296  		anyIPLiveLonger := false
   297  		for i := range rcv.IPs {
   298  			if isIPContains(ips, rcv.IPs[i].IP) {
   299  				rcv.IPs[i].ExpiredAt = now.Add(resolver.cacheLifetime)
   300  				anyIPLiveLonger = true
   301  			}
   302  		}
   303  		if anyIPLiveLonger {
   304  			refreshAfter := now.Add(resolver.cacheRefreshAfter)
   305  			maxRefreshDeadline := rcv.CreatedAt.Add(resolver.cacheMaxLifetime)
   306  			if refreshAfter.After(maxRefreshDeadline) {
   307  				refreshAfter = maxRefreshDeadline
   308  			}
   309  			rcv.RefreshAfter = refreshAfter
   310  			rcv.ExpiredAt = now.Add(resolver.cacheLifetime)
   311  		}
   312  	}
   313  }
   314  
   315  func (resolver cacheResolver) FeedbackBad(context.Context, string, []net.IP) {}
   316  
   317  func (left *resolverCacheValue) IsEqual(rightValue cache.CacheValue) bool {
   318  	if right, ok := rightValue.(*resolverCacheValue); ok {
   319  		// Avoid deadlock by locking in consistent order based on pointer address
   320  		first, second := left, right
   321  		if uintptr(unsafe.Pointer(left)) > uintptr(unsafe.Pointer(right)) {
   322  			first, second = right, left
   323  		}
   324  
   325  		first.mu.RLock()
   326  		defer first.mu.RUnlock()
   327  		second.mu.RLock()
   328  		defer second.mu.RUnlock()
   329  
   330  		if len(left.IPs) != len(right.IPs) {
   331  			return false
   332  		}
   333  		for idx := range left.IPs {
   334  			if !left.IPs[idx].IP.Equal(right.IPs[idx].IP) || !left.IPs[idx].ExpiredAt.Equal(right.IPs[idx].ExpiredAt) {
   335  				return false
   336  			}
   337  		}
   338  		return true
   339  	}
   340  	return false
   341  }
   342  
   343  func (left *resolverCacheValue) IsValid() bool {
   344  	left.mu.RLock()
   345  	defer left.mu.RUnlock()
   346  	return time.Now().Before(left.ExpiredAt)
   347  }
   348  
   349  func (left *resolverCacheValue) ShouldRefresh() bool {
   350  	left.mu.RLock()
   351  	defer left.mu.RUnlock()
   352  	return time.Now().After(left.RefreshAfter)
   353  }
   354  
   355  func (*cacheResolver) localIp() (string, error) {
   356  	conn, err := net.Dial("udp", "223.5.5.5:80")
   357  	if err != nil {
   358  		return "", err
   359  	}
   360  	defer conn.Close()
   361  
   362  	return conn.LocalAddr().(*net.UDPAddr).IP.String(), nil
   363  }
   364  
   365  type (
   366  	byPassResolverCacheContextKey   struct{}
   367  	byPassResolverCacheContextValue struct{}
   368  )
   369  
   370  // WithByPassResolverCache 设置 Context 绕过 Resolver 内部缓存
   371  func WithByPassResolverCache(ctx context.Context) context.Context {
   372  	return context.WithValue(ctx, byPassResolverCacheContextKey{}, byPassResolverCacheContextValue{})
   373  }
   374  
   375  func shouldByPassResolveCache(ctx context.Context) bool {
   376  	_, ok := ctx.Value(byPassResolverCacheContextKey{}).(byPassResolverCacheContextValue)
   377  	return ok
   378  }
   379  
   380  func calcPersistentCacheCrc64(persistentFilePath string, compactInterval, persistentDuration time.Duration) uint64 {
   381  	bytes := make([]byte, 0, 1024)
   382  	bytes = strconv.AppendInt(bytes, int64(compactInterval), 36)
   383  	bytes = strconv.AppendInt(bytes, int64(persistentDuration), 36)
   384  	bytes = append(bytes, []byte(persistentFilePath)...)
   385  	bytes = append(bytes, byte(0))
   386  	return crc64.Checksum(bytes, crc64.MakeTable(crc64.ISO))
   387  }
   388  
   389  func isIPContains(t []net.IP, i net.IP) bool {
   390  	for _, tt := range t {
   391  		if tt.Equal(i) {
   392  			return true
   393  		}
   394  	}
   395  	return false
   396  }