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

     1  package chooser
     2  
     3  import (
     4  	"bytes"
     5  	"container/heap"
     6  	"context"
     7  	"fmt"
     8  	"net"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  type (
    14  	blackItem struct {
    15  		index     int
    16  		domain    string
    17  		ip        net.IP
    18  		expiredAt time.Time
    19  	}
    20  
    21  	blackheap struct {
    22  		m     map[string]*blackItem
    23  		items []*blackItem
    24  	}
    25  
    26  	ipChooser struct {
    27  		blackheap      blackheap
    28  		blackheapMutex sync.Mutex
    29  		freezeDuration time.Duration
    30  	}
    31  
    32  	// IPChooserConfig IP 选择器的选项
    33  	IPChooserConfig struct {
    34  		// FreezeDuration IP 冻结时长(默认:600s)
    35  		FreezeDuration time.Duration
    36  	}
    37  )
    38  
    39  // NewIPChooser 创建 IP 选择器
    40  func NewIPChooser(options *IPChooserConfig) Chooser {
    41  	if options == nil {
    42  		options = &IPChooserConfig{}
    43  	}
    44  	freezeDuration := options.FreezeDuration
    45  	if freezeDuration == 0 {
    46  		freezeDuration = 10 * time.Minute
    47  	}
    48  	return &ipChooser{
    49  		blackheap: blackheap{
    50  			m:     make(map[string]*blackItem, 1024),
    51  			items: make([]*blackItem, 0, 1024),
    52  		},
    53  		freezeDuration: freezeDuration,
    54  	}
    55  }
    56  
    57  func (chooser *ipChooser) Choose(_ context.Context, ips []net.IP, options *ChooseOptions) []net.IP {
    58  	if len(ips) == 0 {
    59  		return nil
    60  	}
    61  	if options == nil {
    62  		options = &ChooseOptions{}
    63  	}
    64  
    65  	chooser.blackheapMutex.Lock()
    66  	defer chooser.blackheapMutex.Unlock()
    67  
    68  	chosenIPs := make([]net.IP, 0, chooser.blackheap.Len())
    69  	for _, ip := range ips {
    70  		if item := chooser.blackheap.FindByDomainAndIp(options.Domain, ip); item == nil {
    71  			chosenIPs = append(chosenIPs, ip)
    72  		}
    73  	}
    74  	if len(chosenIPs) > 0 || options.FailFast {
    75  		return chosenIPs
    76  	}
    77  
    78  	var chosenExpiredAt time.Time
    79  	toFind := makeSet(ips, options.Domain, makeMapKey)
    80  	backups := make([]*blackItem, 0, chooser.blackheap.Len())
    81  	for chooser.blackheap.Len() > 0 {
    82  		firstChosen := heap.Pop(&chooser.blackheap).(*blackItem)
    83  		backups = append(backups, firstChosen)
    84  		key := makeMapKey(firstChosen.domain, firstChosen.ip)
    85  		if _, ok := toFind[key]; ok {
    86  			chosenExpiredAt = firstChosen.expiredAt
    87  			chosenIPs = append(chosenIPs, firstChosen.ip)
    88  			delete(toFind, key)
    89  			break
    90  		}
    91  	}
    92  	if chosenExpiredAt.IsZero() {
    93  		panic("chosenExpiredAt should not be empty")
    94  	}
    95  	for chooser.blackheap.Len() > 0 {
    96  		item := heap.Pop(&chooser.blackheap).(*blackItem)
    97  		backups = append(backups, item)
    98  		if chosenExpiredAt.Equal(item.expiredAt) {
    99  			key := makeMapKey(item.domain, item.ip)
   100  			if _, ok := toFind[key]; ok {
   101  				chosenIPs = append(chosenIPs, item.ip)
   102  				delete(toFind, key)
   103  			}
   104  		} else {
   105  			break
   106  		}
   107  	}
   108  	for _, backup := range backups {
   109  		if backup.expiredAt.After(time.Now()) {
   110  			heap.Push(&chooser.blackheap, backup)
   111  		}
   112  	}
   113  	return chosenIPs
   114  }
   115  
   116  func (chooser *ipChooser) FeedbackGood(_ context.Context, ips []net.IP, options *FeedbackOptions) {
   117  	if len(ips) == 0 {
   118  		return
   119  	}
   120  	if options == nil {
   121  		options = &FeedbackOptions{}
   122  	}
   123  
   124  	chooser.blackheapMutex.Lock()
   125  	defer chooser.blackheapMutex.Unlock()
   126  
   127  	for _, ip := range ips {
   128  		if item := chooser.blackheap.FindByDomainAndIp(options.Domain, ip); item != nil {
   129  			heap.Remove(&chooser.blackheap, item.index)
   130  		}
   131  	}
   132  }
   133  
   134  func (chooser *ipChooser) FeedbackBad(_ context.Context, ips []net.IP, options *FeedbackOptions) {
   135  	if len(ips) == 0 {
   136  		return
   137  	}
   138  	if options == nil {
   139  		options = &FeedbackOptions{}
   140  	}
   141  
   142  	chooser.blackheapMutex.Lock()
   143  	defer chooser.blackheapMutex.Unlock()
   144  
   145  	newExpiredAt := time.Now().Add(chooser.freezeDuration)
   146  	for _, ip := range ips {
   147  		if item := chooser.blackheap.FindByDomainAndIp(options.Domain, ip); item != nil {
   148  			chooser.blackheap.items[item.index].expiredAt = newExpiredAt
   149  			heap.Fix(&chooser.blackheap, item.index)
   150  		} else {
   151  			heap.Push(&chooser.blackheap, &blackItem{
   152  				domain:    options.Domain,
   153  				ip:        ip,
   154  				expiredAt: newExpiredAt,
   155  			})
   156  		}
   157  	}
   158  }
   159  
   160  func (h *blackheap) Len() int {
   161  	return len(h.items)
   162  }
   163  
   164  func (h *blackheap) Less(i, j int) bool {
   165  	return h.items[i].expiredAt.Before(h.items[j].expiredAt)
   166  }
   167  
   168  func (h *blackheap) Swap(i, j int) {
   169  	if i == j {
   170  		return
   171  	}
   172  	h.items[i].domain, h.items[j].domain = h.items[j].domain, h.items[i].domain
   173  	h.items[i].ip, h.items[j].ip = h.items[j].ip, h.items[i].ip
   174  	h.items[i].expiredAt, h.items[j].expiredAt = h.items[j].expiredAt, h.items[i].expiredAt
   175  	h.m[makeMapKey(h.items[i].domain, h.items[i].ip)] = h.items[i]
   176  	h.m[makeMapKey(h.items[j].domain, h.items[j].ip)] = h.items[j]
   177  }
   178  
   179  func (h *blackheap) Push(x interface{}) {
   180  	item := x.(*blackItem)
   181  	item.index = len(h.items)
   182  	h.items = append(h.items, item)
   183  	h.m[makeMapKey(item.domain, item.ip)] = item
   184  }
   185  
   186  func (h *blackheap) Pop() interface{} {
   187  	n := len(h.items)
   188  	last := h.items[n-1]
   189  	h.items = h.items[0 : n-1]
   190  	delete(h.m, makeMapKey(last.domain, last.ip))
   191  	return last
   192  }
   193  
   194  func (h *blackheap) FindByDomainAndIp(domain string, ip net.IP) *blackItem {
   195  	key := makeMapKey(domain, ip)
   196  	if item, ok := h.m[key]; ok {
   197  		if item.expiredAt.Before(time.Now()) {
   198  			heap.Remove(h, item.index)
   199  			return nil
   200  		}
   201  		return item
   202  	}
   203  	return nil
   204  }
   205  
   206  func (h *blackheap) String() string {
   207  	var buf bytes.Buffer
   208  
   209  	buf.WriteString("&blackheap{ items: [")
   210  	for _, item := range h.items {
   211  		buf.WriteString(item.String())
   212  		buf.WriteString(", ")
   213  	}
   214  	buf.WriteString("], m: {")
   215  	for k, v := range h.m {
   216  		buf.WriteString(k)
   217  		buf.WriteString(": ")
   218  		buf.WriteString(v.String())
   219  		buf.WriteString(", ")
   220  	}
   221  	buf.WriteString("}")
   222  
   223  	return buf.String()
   224  }
   225  
   226  func (item *blackItem) String() string {
   227  	var buf bytes.Buffer
   228  
   229  	buf.WriteString("&blackItem{ index: ")
   230  	buf.WriteString(fmt.Sprintf("%d", item.index))
   231  	buf.WriteString(", domain: ")
   232  	buf.WriteString(item.domain)
   233  	buf.WriteString(", ip: ")
   234  	buf.WriteString(item.ip.String())
   235  	buf.WriteString(", expiredAt: ")
   236  	buf.WriteString(item.expiredAt.String())
   237  	buf.WriteString("}")
   238  
   239  	return buf.String()
   240  }
   241  
   242  func makeMapKey(domain string, ip net.IP) string {
   243  	return ip.String() + "|" + domain
   244  }