golang分布式缓存项目 Day6 防止缓存击穿

该项目原作者:https://github.com/geektutu/7days-golang。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。

1 缓存雪崩、缓存击穿与缓存穿透

概念解析

  • 缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key设置了相同的过期时间等引起。
  • 缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
  • 缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。

2 singleflight介绍

singleflight 是一个非常有用的包,它提供了一种机制来抑制对某个函数的多次重复调用。这个包特别适用于避免在高并发场景下对同一资源的重复请求,比如在缓存击穿问题中,多个请求同时访问同一个资源时,singleflight 可以确保这些请求中只有一个实际执行,其他请求则等待这个结果,从而减少对后端服务的压力

核心概念

singleflight 包中定义了一个名为 Group 的结构体类型,它表示一类工作,并形成一个命名空间,在这个命名空间中,可以使用重复抑制来执行工作单元。当你调用 Do 方法时,它接收一个键(key)和一个函数(fn)。这个键是用来标识请求的唯一性,而函数则是实际要执行的操作。Do 方法首先会检查是否已经有相同的请求正在处理中。如果有,那么当前请求就会被放入一个等待队列,直到第一个请求完成并返回结果。这时,所有等待的请求都会收到相同的结果。

使用场景

singleflight 常用于以下场景:

  • 缓存击穿:在高并发的情况下,某个热点数据缓存失效后,大量的请求直接访问数据库,造成数据库的压力过大,甚至宕机的现象。使用singleflight 可以确保即使在缓存失效时,也只有一次数据库请求被执行,其他请求等待这次请求的结果 。
  • 避免重复计算:在需要进行复杂计算且结果可以被多个请求共享的场景中,singleflight 可以确保计算只执行一次,其他请求共享这个结果,从而提高效率。

基本使用

使用 singleflight 时,首先需要创建一个 Group 实例,然后通过调用 Do 方法来执行具体的函数。如果相同的键对应的函数已经

被调用,那么后续的调用将会等待第一个调用的结果,而不是重复执行函数

go 复制代码
var g singleflight.Group

// 模拟耗时操作
func getData(key string) (string, error) {
    // 模拟数据库查询耗时
    time.Sleep(2 * time.Second)
    return "data for " + key, nil
}

func main() {
    // 第一次调用
    result1, err, shared := g.Do("key", func() (interface{}, error) {
        return getData("key")
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Result1: %s, Shared: %v\n", result1, shared)

    // 第二次调用,将共享第一次调用的结果
    result2, err, shared := g.Do("key", func() (interface{}, error) {
        return getData("key")
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Result2: %s, Shared: %v\n", result2, shared)
}

在这个例子中,即使 getData 函数被调用了两次,但由于 singleflight 的机制,实际的数据库查询操作只执行了一次,两次调用共享了这个结果

3 运用singleflight到geecache中

go 复制代码
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) {
	// each key is only fetched once (either locally or remotely)
	// regardless of the number of concurrent callers.
	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
}
  • 修改 geecache.go 中的 Group,添加成员变量 loader,并更新构建函数 NewGroup。
  • 修改 load 函数,将原来的 load 的逻辑,使用 g.loader.Do 包裹起来即可,这样确保了并发场景下针对相同的 key,load 过程只会调用一次。
相关推荐
咖啡啡不加糖2 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
棠十一3 小时前
Rabbitmq
分布式·docker·rabbitmq
肥仔哥哥19303 小时前
springCloud2025+springBoot3.5.0+Nacos集成redis从nacos拉配置起服务
redis·缓存·最新boot3集成
Lansonli3 小时前
大数据Spark(六十一):Spark基于Standalone提交任务流程
大数据·分布式·spark
roman_日积跬步-终至千里4 小时前
【Go语言基础【14】】defer与异常处理(panic、recover)
golang
孔令飞5 小时前
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
ai·云原生·容器·golang·kubernetes
Theodore_10225 小时前
大数据(2) 大数据处理架构Hadoop
大数据·服务器·hadoop·分布式·ubuntu·架构
呼拉拉呼拉6 小时前
Redis故障转移
数据库·redis·缓存·高可用架构
篱笆院的狗9 小时前
如何使用 Redis 快速实现布隆过滤器?
数据库·redis·缓存
G探险者10 小时前
《深入理解 Nacos 集群与 Raft 协议》系列五:为什么集群未过半,系统就不可用?从 Raft 的投票机制说起
分布式·后端