动手写分布式缓存 6

发布于:2025-02-11 ⋅ 阅读:(34) ⋅ 点赞:(0)

在我们之前的实现里,向服务端发送几次请求,很可能也向数据库发送几次请求,容易造成缓存击穿和穿透。
缓存击穿:对于一个热点数据key,缓存中不再缓存时,并发的多个请求都在请求数据库,数据库瞬时请求量大,压力骤增。
缓存穿透:并发的多个请求在请求一个不存在的key,每次都绕过缓存去请求数据库也会导致数据库瞬间压力过大。

singleflight包

package singleflight

import "sync"
// call类型,表示正在进行的请求
type call struct {
	wg  sync.WaitGroup
	val interface{}
	err error
}
// 用于管理请求的结构
type Group struct {
	mu sync.Mutex       // protects m
	m  map[string]*call
}

DO方法

// 管理请求的方法,无论do调用多少次查询key,只会调用依次fn
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
	// 如果请求已经存在于map中,让其等待处理后一起返回值
	if c, ok := g.m[key]; ok {
		g.mu.Unlock()
		c.wg.Wait()
		return c.val, c.err
	}
	// 不存在,创建一个新请求
	c := new(call)
	c.wg.Add(1)
	// 添加到map里便于查找
	g.m[key] = c
	g.mu.Unlock()
	// 只有第一个协程调用函数,其余都在map里找到后wait中
	c.val, c.err = fn()
	c.wg.Done()
	// 对于控制请求的map处理都要加锁
	g.mu.Lock()
	// 删除这次请求
	delete(g.m, key)
	g.mu.Unlock()

	return c.val, c.err
}

singleflight 的使用

缓存池中使用

// 添加了Group对象,
type Group struct {
	name      string
	getter    Getter
	mainCache cache
	peers     PeerPicker
	// use singleflight.Group to make sure that
	// each key is only fetched once
	loader *singleflight.Group
}
// 初始化
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
    // ...
	g := &Group{
        // ...
		loader:    &singleflight.Group{},
	}
	return g
}
// 
func (g *Group) load(key string) (value ByteView, err error) {
	// 对于每个key请求,调用多次do函数,但是只会调用一次,从远程获取数据的函数
	viewi, err := g.loader.Do(key, func() (interface{}, error) {
		if g.peers != nil {
			if peer, ok := g.peers.PickPeer(key); ok {
				if value, err = g.getFromPeer(peer, key); err == nil {
					return value, nil
				}
				log.Println("[GeeCache] Failed to get from peer", err)
			}
		}

		return g.getLocally(key)
	})
	if err == nil {
		return viewi.(ByteView), nil
	}
	return
}

网站公告

今日签到

点亮在社区的每一天
去签到