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 }