github.com/cloudwego/hertz@v0.9.3/pkg/app/client/loadbalance/lbcache.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package loadbalance 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/cloudwego/hertz/pkg/app/client/discovery" 27 "github.com/cloudwego/hertz/pkg/common/errors" 28 "github.com/cloudwego/hertz/pkg/common/hlog" 29 "github.com/cloudwego/hertz/pkg/protocol" 30 "golang.org/x/sync/singleflight" 31 ) 32 33 type cacheResult struct { 34 res atomic.Value // newest and previous discovery result 35 expire int32 // 0 = normal, 1 = expire and collect next ticker 36 serviceName string // service psm 37 } 38 39 var ( 40 balancerFactories sync.Map // key: resolver name + load-balancer name 41 balancerFactoriesSfg singleflight.Group 42 ) 43 44 func cacheKey(resolver, balancer string, opts Options) string { 45 return fmt.Sprintf("%s|%s|{%s %s}", resolver, balancer, opts.RefreshInterval, opts.ExpireInterval) 46 } 47 48 type BalancerFactory struct { 49 opts Options 50 cache sync.Map // key -> LoadBalancer 51 resolver discovery.Resolver 52 balancer Loadbalancer 53 sfg singleflight.Group 54 } 55 56 type Config struct { 57 Resolver discovery.Resolver 58 Balancer Loadbalancer 59 LbOpts Options 60 } 61 62 // NewBalancerFactory get or create a balancer with given target. 63 // If it has the same key(resolver.Target(target)), we will cache and reuse the Balance. 64 func NewBalancerFactory(config Config) *BalancerFactory { 65 config.LbOpts.Check() 66 uniqueKey := cacheKey(config.Resolver.Name(), config.Balancer.Name(), config.LbOpts) 67 val, ok := balancerFactories.Load(uniqueKey) 68 if ok { 69 return val.(*BalancerFactory) 70 } 71 val, _, _ = balancerFactoriesSfg.Do(uniqueKey, func() (interface{}, error) { 72 b := &BalancerFactory{ 73 opts: config.LbOpts, 74 resolver: config.Resolver, 75 balancer: config.Balancer, 76 } 77 go b.watcher() 78 go b.refresh() 79 balancerFactories.Store(uniqueKey, b) 80 return b, nil 81 }) 82 return val.(*BalancerFactory) 83 } 84 85 // watch expired balancer 86 func (b *BalancerFactory) watcher() { 87 for range time.Tick(b.opts.ExpireInterval) { 88 b.cache.Range(func(key, value interface{}) bool { 89 cache := value.(*cacheResult) 90 if atomic.CompareAndSwapInt32(&cache.expire, 0, 1) { 91 // 1. set expire flag 92 // 2. wait next ticker for collect, maybe the balancer is used again 93 // (avoid being immediate delete the balancer which had been created recently) 94 } else { 95 b.cache.Delete(key) 96 b.balancer.Delete(key.(string)) 97 } 98 return true 99 }) 100 } 101 } 102 103 // cache key with resolver name prefix avoid conflict for balancer 104 func renameResultCacheKey(res *discovery.Result, resolverName string) { 105 res.CacheKey = resolverName + ":" + res.CacheKey 106 } 107 108 // refresh is used to update service discovery information periodically. 109 func (b *BalancerFactory) refresh() { 110 for range time.Tick(b.opts.RefreshInterval) { 111 b.cache.Range(func(key, value interface{}) bool { 112 res, err := b.resolver.Resolve(context.Background(), key.(string)) 113 if err != nil { 114 hlog.SystemLogger().Warnf("resolver refresh failed, key=%s error=%s", key, err.Error()) 115 return true 116 } 117 renameResultCacheKey(&res, b.resolver.Name()) 118 cache := value.(*cacheResult) 119 cache.res.Store(res) 120 atomic.StoreInt32(&cache.expire, 0) 121 b.balancer.Rebalance(res) 122 return true 123 }) 124 } 125 } 126 127 func (b *BalancerFactory) GetInstance(ctx context.Context, req *protocol.Request) (discovery.Instance, error) { 128 cacheRes, err := b.getCacheResult(ctx, req) 129 if err != nil { 130 return nil, err 131 } 132 atomic.StoreInt32(&cacheRes.expire, 0) 133 ins := b.balancer.Pick(cacheRes.res.Load().(discovery.Result)) 134 if ins == nil { 135 hlog.SystemLogger().Errorf("null instance. serviceName: %s, options: %v", string(req.Host()), req.Options()) 136 return nil, errors.NewPublic("instance not found") 137 } 138 return ins, nil 139 } 140 141 func (b *BalancerFactory) getCacheResult(ctx context.Context, req *protocol.Request) (*cacheResult, error) { 142 target := b.resolver.Target(ctx, &discovery.TargetInfo{Host: string(req.Host()), Tags: req.Options().Tags()}) 143 cr, existed := b.cache.Load(target) 144 if existed { 145 return cr.(*cacheResult), nil 146 } 147 cr, err, _ := b.sfg.Do(target, func() (interface{}, error) { 148 cache := &cacheResult{ 149 serviceName: string(req.Host()), 150 } 151 res, err := b.resolver.Resolve(ctx, target) 152 if err != nil { 153 return cache, err 154 } 155 renameResultCacheKey(&res, b.resolver.Name()) 156 cache.res.Store(res) 157 atomic.StoreInt32(&cache.expire, 0) 158 b.balancer.Rebalance(res) 159 b.cache.Store(target, cache) 160 return cache, nil 161 }) 162 if err != nil { 163 return nil, err 164 } 165 return cr.(*cacheResult), nil 166 }