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

     1  package region
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"fmt"
     7  	"hash/crc64"
     8  	"math"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/qiniu/go-sdk/v7/auth"
    21  	"github.com/qiniu/go-sdk/v7/internal/cache"
    22  	"github.com/qiniu/go-sdk/v7/internal/clientv2"
    23  	"github.com/qiniu/go-sdk/v7/internal/hostprovider"
    24  	"github.com/qiniu/go-sdk/v7/internal/log"
    25  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    26  	"github.com/qiniu/go-sdk/v7/storagev2/chooser"
    27  	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
    28  	"github.com/qiniu/go-sdk/v7/storagev2/resolver"
    29  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    30  )
    31  
    32  type (
    33  	// BucketRegionsQuery 空间区域查询器
    34  	BucketRegionsQuery interface {
    35  		Query(accessKey, bucketName string) RegionsProvider
    36  	}
    37  
    38  	bucketRegionsQuery struct {
    39  		bucketHosts         Endpoints
    40  		cache               *cache.Cache
    41  		client              clientv2.Client
    42  		useHttps            bool
    43  		accelerateUploading bool
    44  	}
    45  
    46  	// BucketRegionsQueryOptions 空间区域查询器选项
    47  	BucketRegionsQueryOptions struct {
    48  		// 使用 HTTP 协议
    49  		UseInsecureProtocol bool
    50  
    51  		// 是否加速上传
    52  		AccelerateUploading bool
    53  
    54  		// 压缩周期(默认:60s)
    55  		CompactInterval time.Duration
    56  
    57  		// 持久化路径(默认:$TMPDIR/qiniu-golang-sdk/query_v4_01.cache.json)
    58  		PersistentFilePath string
    59  
    60  		// 持久化周期(默认:60s)
    61  		PersistentDuration time.Duration
    62  
    63  		// 单域名重试次数(默认:2)
    64  		RetryMax int
    65  
    66  		// 主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 RetryMax 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。
    67  		HostFreezeDuration time.Duration
    68  
    69  		// HTTP 客户端,如果不配置则使用默认的 HTTP 客户端
    70  		Client clientv2.Client
    71  
    72  		// 域名解析器,如果不配置则使用默认的域名解析器
    73  		Resolver resolver.Resolver
    74  
    75  		// 域名选择器,如果不配置则使用默认的域名选择器
    76  		Chooser chooser.Chooser
    77  
    78  		// 退避器,如果不配置则使用默认的退避器
    79  		Backoff backoff.Backoff
    80  
    81  		// 域名解析前回调函数
    82  		BeforeResolve func(*http.Request)
    83  
    84  		// 域名解析后回调函数
    85  		AfterResolve func(*http.Request, []net.IP)
    86  
    87  		// 域名解析错误回调函数
    88  		ResolveError func(*http.Request, error)
    89  
    90  		// 退避前回调函数
    91  		BeforeBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
    92  
    93  		// 退避后回调函数
    94  		AfterBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
    95  
    96  		// 请求前回调函数
    97  		BeforeRequest func(*http.Request, *retrier.RetrierOptions)
    98  
    99  		// 请求后回调函数
   100  		AfterResponse func(*http.Response, *retrier.RetrierOptions, error)
   101  	}
   102  
   103  	bucketRegionsProvider struct {
   104  		accessKey           string
   105  		bucketName          string
   106  		cacheKey            string
   107  		query               *bucketRegionsQuery
   108  		accelerateUploading bool
   109  	}
   110  
   111  	v4QueryCacheValue struct {
   112  		Regions      []*Region `json:"regions"`
   113  		RefreshAfter time.Time `json:"refresh_after"`
   114  		ExpiredAt    time.Time `json:"expired_at"`
   115  	}
   116  
   117  	v4QueryServiceHosts struct {
   118  		Domains    []string `json:"domains"`
   119  		Old        []string `json:"old"`
   120  		AccDomains []string `json:"acc_domains"`
   121  	}
   122  
   123  	v4QueryRegion struct {
   124  		RegionId string              `json:"region"`
   125  		Ttl      int64               `json:"ttl"`
   126  		Io       v4QueryServiceHosts `json:"io"`
   127  		IoSrc    v4QueryServiceHosts `json:"io_src"`
   128  		Up       v4QueryServiceHosts `json:"up"`
   129  		Rs       v4QueryServiceHosts `json:"rs"`
   130  		Rsf      v4QueryServiceHosts `json:"rsf"`
   131  		Api      v4QueryServiceHosts `json:"api"`
   132  		Uc       v4QueryServiceHosts `json:"uc"`
   133  	}
   134  
   135  	v4QueryResponse struct {
   136  		Hosts []v4QueryRegion `json:"hosts"`
   137  	}
   138  )
   139  
   140  const bucketRegionsQueryCacheFileName = "query_v4_01.cache.json"
   141  
   142  var (
   143  	persistentCaches     map[uint64]*cache.Cache
   144  	persistentCachesLock sync.Mutex
   145  )
   146  
   147  // NewBucketRegionsQuery 创建空间区域查询器
   148  func NewBucketRegionsQuery(bucketHosts Endpoints, opts *BucketRegionsQueryOptions) (BucketRegionsQuery, error) {
   149  	if opts == nil {
   150  		opts = &BucketRegionsQueryOptions{}
   151  	}
   152  	retryMax := opts.RetryMax
   153  	if retryMax <= 0 {
   154  		retryMax = 2
   155  	}
   156  	compactInterval := opts.CompactInterval
   157  	if compactInterval == time.Duration(0) {
   158  		compactInterval = time.Minute
   159  	}
   160  	persistentFilePath := opts.PersistentFilePath
   161  	if persistentFilePath == "" {
   162  		persistentFilePath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", bucketRegionsQueryCacheFileName)
   163  	}
   164  	persistentDuration := opts.PersistentDuration
   165  	if persistentDuration == time.Duration(0) {
   166  		persistentDuration = time.Minute
   167  	}
   168  
   169  	persistentCache, err := getPersistentCache(persistentFilePath, compactInterval, persistentDuration)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	return &bucketRegionsQuery{
   175  		bucketHosts: bucketHosts,
   176  		cache:       persistentCache,
   177  		client: makeBucketQueryClient(
   178  			opts.Client, nil,
   179  			bucketHosts,
   180  			!opts.UseInsecureProtocol,
   181  			retryMax,
   182  			opts.HostFreezeDuration,
   183  			opts.Resolver,
   184  			opts.Chooser,
   185  			opts.Backoff,
   186  			opts.BeforeResolve,
   187  			opts.AfterResolve,
   188  			opts.ResolveError,
   189  			opts.BeforeBackoff,
   190  			opts.AfterBackoff,
   191  			opts.BeforeRequest,
   192  			opts.AfterResponse,
   193  			nil, nil, nil,
   194  		),
   195  		useHttps:            !opts.UseInsecureProtocol,
   196  		accelerateUploading: opts.AccelerateUploading,
   197  	}, nil
   198  }
   199  
   200  func getPersistentCache(persistentFilePath string, compactInterval, persistentDuration time.Duration) (*cache.Cache, error) {
   201  	var (
   202  		persistentCache *cache.Cache
   203  		ok              bool
   204  		err             error
   205  	)
   206  
   207  	crc64Value := calcPersistentCacheCrc64(persistentFilePath, compactInterval, persistentDuration)
   208  	persistentCachesLock.Lock()
   209  	defer persistentCachesLock.Unlock()
   210  
   211  	if persistentCaches == nil {
   212  		persistentCaches = make(map[uint64]*cache.Cache)
   213  	}
   214  	if persistentCache, ok = persistentCaches[crc64Value]; !ok {
   215  		persistentCache, err = cache.NewPersistentCache(
   216  			reflect.TypeOf(&v4QueryCacheValue{}),
   217  			persistentFilePath,
   218  			compactInterval,
   219  			persistentDuration,
   220  			func(err error) {
   221  				log.Warn(fmt.Sprintf("BucketRegionsQuery persist error: %s", err))
   222  			})
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		persistentCaches[crc64Value] = persistentCache
   227  	}
   228  	return persistentCache, nil
   229  }
   230  
   231  // Query 查询空间区域,返回 region.RegionsProvider
   232  func (query *bucketRegionsQuery) Query(accessKey, bucketName string) RegionsProvider {
   233  	return &bucketRegionsProvider{
   234  		accessKey:           accessKey,
   235  		bucketName:          bucketName,
   236  		query:               query,
   237  		cacheKey:            makeRegionCacheKey(accessKey, bucketName, query.accelerateUploading, query.bucketHosts),
   238  		accelerateUploading: query.accelerateUploading,
   239  	}
   240  }
   241  
   242  func (provider *bucketRegionsProvider) GetRegions(ctx context.Context) ([]*Region, error) {
   243  	var err error
   244  	cacheValue, status := provider.query.cache.Get(provider.cacheKey, func() (cache.CacheValue, error) {
   245  		var ret v4QueryResponse
   246  		url := fmt.Sprintf("%s/v4/query?ak=%s&bucket=%s", provider.query.bucketHosts.firstUrl(provider.query.useHttps), provider.accessKey, provider.bucketName)
   247  		if err = clientv2.DoAndDecodeJsonResponse(provider.query.client, clientv2.RequestParams{
   248  			Context:        ctx,
   249  			Method:         clientv2.RequestMethodGet,
   250  			Url:            url,
   251  			BufferResponse: true,
   252  		}, &ret); err != nil {
   253  			return nil, err
   254  		}
   255  		return ret.toCacheValue(provider.accelerateUploading), nil
   256  	})
   257  	if status == cache.NoResultGot {
   258  		return nil, err
   259  	}
   260  	return cacheValue.(*v4QueryCacheValue).Regions, nil
   261  }
   262  
   263  func (left *v4QueryCacheValue) IsEqual(rightValue cache.CacheValue) bool {
   264  	if right, ok := rightValue.(*v4QueryCacheValue); ok {
   265  		if len(left.Regions) != len(right.Regions) {
   266  			return false
   267  		}
   268  		for idx := range left.Regions {
   269  			if !left.Regions[idx].IsEqual(right.Regions[idx]) {
   270  				return false
   271  			}
   272  		}
   273  		return true
   274  	}
   275  	return false
   276  }
   277  
   278  func (left *v4QueryCacheValue) IsValid() bool {
   279  	return time.Now().Before(left.ExpiredAt)
   280  }
   281  
   282  func (left *v4QueryCacheValue) ShouldRefresh() bool {
   283  	return time.Now().After(left.RefreshAfter)
   284  }
   285  
   286  func (response *v4QueryResponse) toCacheValue(accelerateUploading bool) *v4QueryCacheValue {
   287  	var (
   288  		minTtl  = int64(math.MaxInt64)
   289  		regions = make([]*Region, 0, len(response.Hosts))
   290  	)
   291  	for _, host := range response.Hosts {
   292  		regions = append(regions, host.toCacheValue(accelerateUploading))
   293  		if host.Ttl < minTtl {
   294  			minTtl = host.Ttl
   295  		}
   296  	}
   297  	now := time.Now()
   298  	return &v4QueryCacheValue{
   299  		Regions:      regions,
   300  		RefreshAfter: now.Add(time.Duration(minTtl) * time.Second / 2),
   301  		ExpiredAt:    now.Add(time.Duration(minTtl) * time.Second),
   302  	}
   303  }
   304  
   305  func (response *v4QueryRegion) toCacheValue(accelerateUploading bool) *Region {
   306  	region := Region{
   307  		RegionID: response.RegionId,
   308  		Up:       response.Up.toCacheValue(),
   309  		Io:       response.Io.toCacheValue(),
   310  		IoSrc:    response.IoSrc.toCacheValue(),
   311  		Rs:       response.Rs.toCacheValue(),
   312  		Rsf:      response.Rsf.toCacheValue(),
   313  		Api:      response.Api.toCacheValue(),
   314  		Bucket:   response.Uc.toCacheValue(),
   315  	}
   316  	if !accelerateUploading {
   317  		region.Up.Accelerated = nil
   318  	}
   319  
   320  	return &region
   321  }
   322  
   323  func (response *v4QueryServiceHosts) toCacheValue() Endpoints {
   324  	return Endpoints{
   325  		Accelerated: response.AccDomains,
   326  		Preferred:   response.Domains,
   327  		Alternative: response.Old,
   328  	}
   329  }
   330  
   331  func makeRegionCacheKey(accessKey, bucketName string, accelerateUploading bool, bucketHosts Endpoints) string {
   332  	enableAcceleration := uint8(0)
   333  	if accelerateUploading {
   334  		enableAcceleration = 1
   335  	}
   336  	return fmt.Sprintf("%s:%s:%d:%s", accessKey, bucketName, enableAcceleration, makeBucketHostsCacheKey(bucketHosts))
   337  }
   338  
   339  func makeBucketHostsCacheKey(serviceHosts Endpoints) string {
   340  	return fmt.Sprintf("%s:%s:%s", makeHostsCacheKey(serviceHosts.Preferred), makeHostsCacheKey(serviceHosts.Alternative), makeHostsCacheKey(serviceHosts.Accelerated))
   341  }
   342  
   343  func makeHostsCacheKey(hosts []string) string {
   344  	sortedHosts := append(make([]string, 0, len(hosts)), hosts...)
   345  	sort.StringSlice(sortedHosts).Sort()
   346  	return fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(sortedHosts, ","))))
   347  }
   348  
   349  func makeBucketQueryClient(
   350  	client clientv2.Client,
   351  	credentials credentials.CredentialsProvider,
   352  	bucketHosts Endpoints,
   353  	useHttps bool,
   354  	retryMax int,
   355  	hostFreezeDuration time.Duration,
   356  	r resolver.Resolver,
   357  	cs chooser.Chooser,
   358  	bf backoff.Backoff,
   359  	beforeResolve func(*http.Request),
   360  	afterResolve func(*http.Request, []net.IP),
   361  	resolveError func(*http.Request, error),
   362  	beforeBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration),
   363  	afterBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration),
   364  	beforeRequest func(*http.Request, *retrier.RetrierOptions),
   365  	afterResponse func(*http.Response, *retrier.RetrierOptions, error),
   366  	beforeSign, afterSign func(*http.Request),
   367  	signError func(*http.Request, error),
   368  ) clientv2.Client {
   369  	is := []clientv2.Interceptor{
   370  		clientv2.NewAntiHijackingInterceptor(),
   371  		clientv2.NewHostsRetryInterceptor(clientv2.HostsRetryConfig{
   372  			RetryMax:           bucketHosts.HostsLength(),
   373  			HostFreezeDuration: hostFreezeDuration,
   374  			HostProvider:       hostprovider.NewWithHosts(bucketHosts.allUrls(useHttps)),
   375  		}),
   376  		clientv2.NewSimpleRetryInterceptor(clientv2.SimpleRetryConfig{
   377  			RetryMax:      retryMax,
   378  			Backoff:       bf,
   379  			Resolver:      r,
   380  			Chooser:       cs,
   381  			BeforeResolve: beforeResolve,
   382  			AfterResolve:  afterResolve,
   383  			ResolveError:  resolveError,
   384  			BeforeBackoff: beforeBackoff,
   385  			AfterBackoff:  afterBackoff,
   386  			BeforeRequest: beforeRequest,
   387  			AfterResponse: afterResponse,
   388  		}),
   389  		clientv2.NewBufferResponseInterceptor(),
   390  	}
   391  	if credentials != nil {
   392  		is = append(is, clientv2.NewAuthInterceptor(clientv2.AuthConfig{
   393  			Credentials: credentials,
   394  			TokenType:   auth.TokenQiniu,
   395  			BeforeSign:  beforeSign,
   396  			AfterSign:   afterSign,
   397  			SignError:   signError,
   398  		}))
   399  	}
   400  	return clientv2.NewClient(client, is...)
   401  }
   402  
   403  func calcPersistentCacheCrc64(persistentFilePath string, compactInterval, persistentDuration time.Duration) uint64 {
   404  	bytes := make([]byte, 0, 1024)
   405  	bytes = strconv.AppendInt(bytes, int64(compactInterval), 36)
   406  	bytes = append(bytes, []byte(persistentFilePath)...)
   407  	bytes = append(bytes, byte(0))
   408  	bytes = strconv.AppendInt(bytes, int64(persistentDuration), 36)
   409  	return crc64.Checksum(bytes, crc64.MakeTable(crc64.ISO))
   410  }