Feature: implemented a strategy similar to optimistic DNS (#647)
This commit is contained in:
parent
b085addbb0
commit
b979ff0bc2
4 changed files with 121 additions and 45 deletions
83
common/cache/lrucache.go
vendored
83
common/cache/lrucache.go
vendored
|
@ -42,6 +42,14 @@ func WithSize(maxSize int) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStale decide whether Stale return is enabled.
|
||||||
|
// If this feature is enabled, element will not get Evicted according to `WithAge`.
|
||||||
|
func WithStale(stale bool) Option {
|
||||||
|
return func(l *LruCache) {
|
||||||
|
l.staleReturn = stale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LruCache is a thread-safe, in-memory lru-cache that evicts the
|
// LruCache is a thread-safe, in-memory lru-cache that evicts the
|
||||||
// least recently used entries from memory when (if set) the entries are
|
// least recently used entries from memory when (if set) the entries are
|
||||||
// older than maxAge (in seconds). Use the New constructor to create one.
|
// older than maxAge (in seconds). Use the New constructor to create one.
|
||||||
|
@ -52,6 +60,7 @@ type LruCache struct {
|
||||||
cache map[interface{}]*list.Element
|
cache map[interface{}]*list.Element
|
||||||
lru *list.List // Front is least-recent
|
lru *list.List // Front is least-recent
|
||||||
updateAgeOnGet bool
|
updateAgeOnGet bool
|
||||||
|
staleReturn bool
|
||||||
onEvict EvictCallback
|
onEvict EvictCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,31 +81,28 @@ func NewLRUCache(options ...Option) *LruCache {
|
||||||
// Get returns the interface{} representation of a cached response and a bool
|
// Get returns the interface{} representation of a cached response and a bool
|
||||||
// set to true if the key was found.
|
// set to true if the key was found.
|
||||||
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
|
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
|
||||||
c.mu.Lock()
|
entry := c.get(key)
|
||||||
defer c.mu.Unlock()
|
if entry == nil {
|
||||||
|
|
||||||
le, ok := c.cache[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
|
|
||||||
c.deleteElement(le)
|
|
||||||
c.maybeDeleteOldest()
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lru.MoveToBack(le)
|
|
||||||
entry := le.Value.(*entry)
|
|
||||||
if c.maxAge > 0 && c.updateAgeOnGet {
|
|
||||||
entry.expires = time.Now().Unix() + c.maxAge
|
|
||||||
}
|
|
||||||
value := entry.value
|
value := entry.value
|
||||||
|
|
||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWithExpire returns the interface{} representation of a cached response,
|
||||||
|
// a time.Time Give expected expires,
|
||||||
|
// and a bool set to true if the key was found.
|
||||||
|
// This method will NOT check the maxAge of element and will NOT update the expires.
|
||||||
|
func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) {
|
||||||
|
entry := c.get(key)
|
||||||
|
if entry == nil {
|
||||||
|
return nil, time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.value, time.Unix(entry.expires, 0), true
|
||||||
|
}
|
||||||
|
|
||||||
// Exist returns if key exist in cache but not put item to the head of linked list
|
// Exist returns if key exist in cache but not put item to the head of linked list
|
||||||
func (c *LruCache) Exist(key interface{}) bool {
|
func (c *LruCache) Exist(key interface{}) bool {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
@ -108,21 +114,26 @@ func (c *LruCache) Exist(key interface{}) bool {
|
||||||
|
|
||||||
// Set stores the interface{} representation of a response for a given key.
|
// Set stores the interface{} representation of a response for a given key.
|
||||||
func (c *LruCache) Set(key interface{}, value interface{}) {
|
func (c *LruCache) Set(key interface{}, value interface{}) {
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
expires := int64(0)
|
expires := int64(0)
|
||||||
if c.maxAge > 0 {
|
if c.maxAge > 0 {
|
||||||
expires = time.Now().Unix() + c.maxAge
|
expires = time.Now().Unix() + c.maxAge
|
||||||
}
|
}
|
||||||
|
c.SetWithExpire(key, value, time.Unix(expires, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWithExpire stores the interface{} representation of a response for a given key and given exires.
|
||||||
|
// The expires time will round to second.
|
||||||
|
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if le, ok := c.cache[key]; ok {
|
if le, ok := c.cache[key]; ok {
|
||||||
c.lru.MoveToBack(le)
|
c.lru.MoveToBack(le)
|
||||||
e := le.Value.(*entry)
|
e := le.Value.(*entry)
|
||||||
e.value = value
|
e.value = value
|
||||||
e.expires = expires
|
e.expires = expires.Unix()
|
||||||
} else {
|
} else {
|
||||||
e := &entry{key: key, value: value, expires: expires}
|
e := &entry{key: key, value: value, expires: expires.Unix()}
|
||||||
c.cache[key] = c.lru.PushBack(e)
|
c.cache[key] = c.lru.PushBack(e)
|
||||||
|
|
||||||
if c.maxSize > 0 {
|
if c.maxSize > 0 {
|
||||||
|
@ -135,6 +146,30 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
|
||||||
c.maybeDeleteOldest()
|
c.maybeDeleteOldest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *LruCache) get(key interface{}) *entry {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
le, ok := c.cache[key]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
|
||||||
|
c.deleteElement(le)
|
||||||
|
c.maybeDeleteOldest()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lru.MoveToBack(le)
|
||||||
|
entry := le.Value.(*entry)
|
||||||
|
if c.maxAge > 0 && c.updateAgeOnGet {
|
||||||
|
entry.expires = time.Now().Unix() + c.maxAge
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes the value associated with a key.
|
// Delete removes the value associated with a key.
|
||||||
func (c *LruCache) Delete(key string) {
|
func (c *LruCache) Delete(key string) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
@ -147,7 +182,7 @@ func (c *LruCache) Delete(key string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LruCache) maybeDeleteOldest() {
|
func (c *LruCache) maybeDeleteOldest() {
|
||||||
if c.maxAge > 0 {
|
if !c.staleReturn && c.maxAge > 0 {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
|
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
|
||||||
c.deleteElement(le)
|
c.deleteElement(le)
|
||||||
|
|
28
common/cache/lrucache_test.go
vendored
28
common/cache/lrucache_test.go
vendored
|
@ -136,3 +136,31 @@ func TestEvict(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, temp, 3)
|
assert.Equal(t, temp, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetWithExpire(t *testing.T) {
|
||||||
|
c := NewLRUCache(WithAge(1))
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
|
c.SetWithExpire(1, 2, tenSecBefore)
|
||||||
|
|
||||||
|
// res is expected not to exist, and expires should be empty time.Time
|
||||||
|
res, expires, exist := c.GetWithExpire(1)
|
||||||
|
assert.Equal(t, nil, res)
|
||||||
|
assert.Equal(t, time.Time{}, expires)
|
||||||
|
assert.Equal(t, false, exist)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStale(t *testing.T) {
|
||||||
|
c := NewLRUCache(WithAge(1), WithStale(true))
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
|
c.SetWithExpire(1, 2, tenSecBefore)
|
||||||
|
|
||||||
|
res, expires, exist := c.GetWithExpire(1)
|
||||||
|
assert.Equal(t, 2, res)
|
||||||
|
assert.Equal(t, tenSecBefore, expires)
|
||||||
|
assert.Equal(t, true, exist)
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ type Resolver struct {
|
||||||
fallback []dnsClient
|
fallback []dnsClient
|
||||||
fallbackFilters []fallbackFilter
|
fallbackFilters []fallbackFilter
|
||||||
group singleflight.Group
|
group singleflight.Group
|
||||||
cache *cache.Cache
|
lruCache *cache.LruCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||||
|
@ -96,22 +96,35 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
q := m.Question[0]
|
q := m.Question[0]
|
||||||
cache, expireTime := r.cache.GetWithExpire(q.String())
|
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
|
||||||
if cache != nil {
|
if hit {
|
||||||
|
now := time.Now()
|
||||||
msg = cache.(*D.Msg).Copy()
|
msg = cache.(*D.Msg).Copy()
|
||||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
if expireTime.Before(now) {
|
||||||
|
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||||
|
go r.exchangeWithoutCache(m)
|
||||||
|
} else {
|
||||||
|
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return r.exchangeWithoutCache(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
|
||||||
|
func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
q := m.Question[0]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putMsgToCache(r.cache, q.String(), msg)
|
putMsgToCache(r.lruCache, q.String(), msg)
|
||||||
if r.mapping {
|
if r.mapping {
|
||||||
ips := r.msgToIP(msg)
|
ips := r.msgToIP(msg)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
putMsgToCache(r.cache, ip.String(), msg)
|
putMsgToCache(r.lruCache, ip.String(), msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -141,7 +154,7 @@ func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||||
return r.pool.LookBack(ip)
|
return r.pool.LookBack(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
cache := r.cache.Get(ip.String())
|
cache, _ := r.lruCache.Get(ip.String())
|
||||||
if cache == nil {
|
if cache == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
@ -294,17 +307,17 @@ type Config struct {
|
||||||
|
|
||||||
func New(config Config) *Resolver {
|
func New(config Config) *Resolver {
|
||||||
defaultResolver := &Resolver{
|
defaultResolver := &Resolver{
|
||||||
main: transform(config.Default, nil),
|
main: transform(config.Default, nil),
|
||||||
cache: cache.New(time.Second * 60),
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main, defaultResolver),
|
main: transform(config.Main, defaultResolver),
|
||||||
cache: cache.New(time.Second * 60),
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
mapping: config.EnhancedMode == MAPPING,
|
mapping: config.EnhancedMode == MAPPING,
|
||||||
fakeip: config.EnhancedMode == FAKEIP,
|
fakeip: config.EnhancedMode == FAKEIP,
|
||||||
pool: config.Pool,
|
pool: config.Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
|
|
12
dns/util.go
12
dns/util.go
|
@ -79,21 +79,21 @@ func (e EnhancedMode) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) {
|
func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
||||||
var ttl time.Duration
|
var ttl uint32
|
||||||
switch {
|
switch {
|
||||||
case len(msg.Answer) != 0:
|
case len(msg.Answer) != 0:
|
||||||
ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second
|
ttl = msg.Answer[0].Header().Ttl
|
||||||
case len(msg.Ns) != 0:
|
case len(msg.Ns) != 0:
|
||||||
ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second
|
ttl = msg.Ns[0].Header().Ttl
|
||||||
case len(msg.Extra) != 0:
|
case len(msg.Extra) != 0:
|
||||||
ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second
|
ttl = msg.Extra[0].Header().Ttl
|
||||||
default:
|
default:
|
||||||
log.Debugln("[DNS] response msg error: %#v", msg)
|
log.Debugln("[DNS] response msg error: %#v", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Put(key, msg.Copy(), ttl)
|
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||||
|
|
Loading…
Reference in a new issue