概念定义
CDN 各边缘独立缓存:同一长尾对象在每个 POP 都 miss 一遍。护盾横亘在所有边缘与源站之间,对象只回源一次,此后各 POP 都从护盾取——源站从几十个客户端变成一个。
工作机制
边缘被配置为向护盾回源;护盾维护大容量缓存、拥有独立 TTL 与陈旧策略,通常在回源点施加请求合并。护盾区域应靠近源站而非用户,因为全部边缘填充流量都经过它。
使用场景
带限速或计算昂贵的源站、各 POP 缓存重度 miss 的长尾目录、以及想要单一源站保护闸门的 DR 场景。源站故障期间护盾还是集中提供陈旧服务的那个点。
常见误区
“护盾给每个请求加延迟”不对:只有边缘 miss 才多一跳,边缘命中不受影响。“每大洲一个护盾”反而重建了它本该收拢的扇入——每源站恰一个护盾点才是要义。护盾是新的单点:它故障时边缘填充直冲源站或请求失败;动态不可缓存流量必须绕开护盾,否则单区域跳数把一切串行化。
代码示例
import threading
from collections import OrderedDict
from collections.abc import Callable, Hashable
from functools import wraps
from typing import Generic, TypeVar
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class LRUCache(Generic[K, V]):
def __init__(self, capacity: int) -> None:
if capacity < 1:
raise ValueError("capacity must be at least 1")
self.capacity = capacity
self._items: OrderedDict[K, V] = OrderedDict()
self._lock = threading.RLock()
self.hits = 0
self.misses = 0
def get(self, key: K) -> V | None:
with self._lock:
if key not in self._items:
self.misses += 1
return None
self.hits += 1
self._items.move_to_end(key)
return self._items[key]
def put(self, key: K, value: V) -> None:
with self._lock:
self._items[key] = value
self._items.move_to_end(key)
while len(self._items) > self.capacity:
self._items.popitem(last=False)
def stats(self) -> dict[str, int | float]:
total = self.hits + self.misses
return {
"size": len(self._items),
"hits": self.hits,
"misses": self.misses,
"hit_rate": round(self.hits / total, 3) if total else 0.0,
}
def memoize(
cache: LRUCache[K, V],
) -> Callable[[Callable[[K], V]], Callable[[K], V]]:
def decorator(func: Callable[[K], V]) -> Callable[[K], V]:
@wraps(func)
def wrapper(key: K) -> V:
cached = cache.get(key)
if cached is not None:
return cached
value = func(key)
cache.put(key, value)
return value
return wrapper
return decorator
def main() -> None:
cache: LRUCache[str, int] = LRUCache(capacity=2)
calls = 0
@memoize(cache)
def slow_square(text: str) -> int:
nonlocal calls
calls += 1
return int(text) ** 2
print(slow_square("4"), slow_square("4"))
print(slow_square("5"), slow_square("6"))
print(slow_square("4"))
print(cache.stats(), "calls:", calls)
if __name__ == "__main__":
main()
package main
import (
"context"
"fmt"
"sync"
"time"
"golang.org/x/sync/singleflight"
)
type Store struct {
mu sync.RWMutex
cache map[string]entry
group singleflight.Group
}
type entry struct {
value string
expires time.Time
}
func NewStore(ttl time.Duration) *Store {
return &Store{cache: make(map[string]entry)}
}
func (s *Store) Get(ctx context.Context, key string, ttl time.Duration) (string, error) {
s.mu.RLock()
e, ok := s.cache[key]
s.mu.RUnlock()
if ok && time.Now().Before(e.expires) {
return e.value, nil // 命中:新鲜数据直接返回
}
v, err, _ := s.group.Do(key, func() (interface{}, error) {
value, loadErr := slowLoad(ctx, key) // 同 key 并发只执行一次
if loadErr != nil {
return nil, loadErr
}
s.mu.Lock()
s.cache[key] = entry{value: value, expires: time.Now().Add(ttl)}
s.mu.Unlock()
return value, nil
})
if err != nil {
if ok {
return e.value, nil // 回源失败时降级旧值
}
return "", err
}
return v.(string), nil
}
func slowLoad(_ context.Context, key string) (string, error) {
time.Sleep(50 * time.Millisecond)
return "value-for-" + key, nil
}
func main() {
store := NewStore(time.Minute)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
v, _ := store.Get(context.Background(), "config", time.Minute)
fmt.Println(v)
}()
}
wg.Wait()
}