首页 > 其他分享 >如何防止缓存被击穿

如何防止缓存被击穿

时间:2023-05-26 14:34:28浏览次数:32  
标签:wg 返回 缓存 防止 击穿 value call key 请求

在什么情况下缓存会被击穿

高并发应用场景中,当大量请求同时请求同个key,这个key便会失效了,这就使得数据库被超量的请求直接访问。此现象就是缓存击穿,其后果会导致数据库压力陡增。

使用singleflight阻止同时请求

请求1、2、3同时请求相同的key,singleflight机制只会让请求1访问DB,请求1返回的value不仅返回给客户端1,也作为请求2、请求3的结果返回给客户端。这里的多请求理解为多个gotoutine并发执行。

示例代码

package main
​
import (
   "golang.org/x/sync/singleflight"
   "log"
   "sync"
   "time"
)
​
func main() {
   var group singleflight.Group
   var wg sync.WaitGroup
   gonum := 5
   wg.Add(gonum)
   key := "requestKey"
   for i := 0; i < gonum; i++ { //模拟多个协程同时请求
      go func(requestID int) {
         defer wg.Done()
         value, _ := mainproc(&group, requestID, key)
         log.Printf("request %v 获取结果: %v", requestID, value)
      }(i)
   }
   wg.Wait()
}
​
func mainproc(group *singleflight.Group, requestID int, key string) (string, error) {
   log.Printf("request %v 发起请求", requestID)
   value, _, _ := group.Do(key, func() (ret interface{}, err error) { //do的入参key,可以直接使用缓存的key,这样同一个缓存,只有一个协程会去读DB
      log.Printf("request %v 正在运行", requestID)
      time.Sleep(3 * time.Second)
      log.Printf("request %v 完成", requestID)
      return "RESULT", nil
   })
   return value.(string), nil
}

源码解析

1. 数据结构

Group:实现singleflight机制的对象,多个请求共用一个group,其中的mu字段保证并发安全,m字段存请求(key)和对应的call对象(value),多个请求访问同一个key时,m就保证了每个key只有一个call对象。

type Group struct {
   mu sync.Mutex       // 锁,保证m的并发安全
   m  map[string]*call // 存请求(key)和对应的调用信息(value)包括返回结果等。
}
Call:调用信息,包括结果和一些统计字段。多个请求的key相同,只会有一个call
type call struct {
   // 通过wg的机制可以保证阻塞相同key的其他请求。
   wg sync.WaitGroup
​
   // 请求返回结果,保证在wg done之前只写入一次,且在wg done之后才会读
   val interface{}
   err error
​
   // 当前key是否调用了Forget方法
   forgotten bool
​
   // 统计相同key的次数
   dups  int
   // 请求返回结果,但是DoChan方法调用,用channel进行通知。
   chans []chan<- Result
}
Result:请求的返回结果
type Result struct {
   // 返回值
   Val interface{}
   Err error
   // 是否共享(多个相同key的请求等待)
   Shared bool
}

2. 方法

Do方法:singleflight的核心方法,执行给定的函数,并返回结果,一个key返回一次,重复的key会等待第一个返回后,返回相同的结果。
DoCall方法:与Do方法作用一样,区别在于执行函数非阻塞,所有的结果通过chan传给各个请求。

/*
Do 执行给定的函数,并返回结果,一个key返回一次,重复的key会等待第一个返回后,返回相同的结果。
入参:key 请求标识,用于区分是否是相同的请求;fn 要执行的函数
返回值:v 返回结果;err 错误信息;shared 是否是共享的结果,是否将v提供给多个请求
*/
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
   // 相当于给map加锁
   g.mu.Lock()
   // 懒加载,如果g中还没有map,就初始化一个map
   if g.m == nil {
      g.m = make(map[string]*call)
   }
   // key有对应的value,说明有相同的key只在执行,当前的请求需要等待。
   if c, ok := g.m[key]; ok {
      c.dups++      // 相同的请求数+1
      g.mu.Unlock() // 不需要写入,直接释放锁
      c.wg.Wait()   // 等待
​
      // 省略一些错误逻辑处理。。。
      ......
      return c.val, c.err, true
   }
   // 当前的key没有对应value
   c := new(call) // 新建当前key的call实例
   c.wg.Add(1)    // 只有1个请求执行,只需要Add(1)
   g.m[key] = c   // 写入map
   g.mu.Unlock()  // 写入完成释放锁
​
   g.doCall(c, key, fn)            // 执行
   return c.val, c.err, c.dups > 0 // >0 表示当前值需要共享给其他正在等待的请求。
}
​
/*
DoChan 与Do方法作用相同,区别是返回的是chan,可以在有数据时直接填入chan中,避免阻塞。
*/
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
   ch := make(chan Result, 1)
   ......
   if c, ok := g.m[key]; ok {
      c.dups++
      // 等待的请求将自己的ch添加到call实例中的chans列表中,方便有结果时返回
      c.chans = append(c.chans, ch)
      // 因为结果通过ch传递,所以不需要c.wg.Wait()
      ......
      return ch
   }
   c := &call{chans: []chan<- Result{ch}}
   ......
​
   // 因为使用chan传输数据,是非阻塞式的,可以使用其他的goroutine执行处理函数。
   go g.doCall(c, key, fn)
​
   return ch
}
c

作者:陈双寅

标签:wg,返回,缓存,防止,击穿,value,call,key,请求
From: https://www.cnblogs.com/DTCLOUD/p/17434629.html

相关文章

  • 跟我一起探索 HTTP-HTTP缓存
    概览HTTP缓存会存储与请求关联的响应,并将存储的响应复用于后续请求。可复用性有几个优点。首先,由于不需要将请求传递到源服务器,因此客户端和缓存越近,响应速度就越快。最典型的例子是浏览器本身为浏览器请求存储缓存。此外,当响应可复用时,源服务器不需要处理请求——因为它不需要解......
  • Redis中的缓存穿透|缓存击穿|缓存雪崩
    Redis是一种内存数据库,也就是说,它是一种存储在内存中的数据库.相当于Redis是提供一种缓存服务,提供这种缓存服务的有很多种,包括Redis,MongoDB等,其中国内用的最多的最常见的可能就是Redis既然是缓存服务,那么就可能存在缓存穿透,缓存击穿和缓存雪崩的现象存在,现在我们来分别详细描......
  • 3种分页列表缓存方式,速收藏~
    摘要:本文介绍了实现分页列表缓存的三种方式。本文分享自华为云社区《分页列表缓存,你真的会吗》,作者:勇哥java实战分享。1直接缓存分页列表结果显而易见,这是最简单易懂的方式。我们按照不同的分页条件来缓存分页结果,伪代码如下:publicList<Product>getPageList(String......
  • 把yum安装的rpm包缓存成离线包
    1.编辑yum配置文件保证缓存在/etc/yum.conf文件中,将keepcache=0改为keepcache=12.以安装nginx为例yuminstallnginx-y3.安装过程中可以得到下载仓库位置和依赖包信息4.在指定仓库中提取nginx和依赖包5.为确保得到的软件包正常,每次安装软件前先把之前缓存的软件......
  • 2023-05-24:为什么要使用Redis做缓存?
    2023-05-24:为什么要使用Redis做缓存?答案2023-05-24:缓存的好处买啤酒和喝啤酒的例子可以帮助我们理解缓存的好处。假设你在超市里买了一箱啤酒,如果你需要每次想喝啤酒就去超市购买,无疑会浪费很多时间和精力。而如果你将一部分啤酒放在家中的冰箱里,每次想喝啤酒时就从冰箱里取出来,那......
  • 2023-05-24:为什么要使用Redis做缓存?
    2023-05-24:为什么要使用Redis做缓存?答案2023-05-24:缓存的好处买啤酒和喝啤酒的例子可以帮助我们理解缓存的好处。假设你在超市里买了一箱啤酒,如果你需要每次想喝啤酒就去超市购买,无疑会浪费很多时间和精力。而如果你将一部分啤酒放在家中的冰箱里,每次想喝啤酒时就从冰箱里取出......
  • 防止Cannot allocate memory(无法分配内存)
    防止Cannotallocatememory(无法分配内存)值为不超过总内存的1%即可,我这里设置的是512M,min_free_kbytes表示强制Linux系统最低保留的空闲内存(Kbytes),如果系统可用内存低于设定的min_free_kbytes值,则默认系统启动oom-killer或强制重启。具体行为由内核参数vm.panic_on_oo......
  • iOS OpenGL ES FBO 帧缓存区 渲染缓存区详解
    原文地址:https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html#//apple_ref/doc/uid/TP40008793-CH103-SW6绘制到其他渲染目的地Framebuffer对象是渲染命令的目标。......
  • http缓存相关文章推荐
    HTTP缓存MemoryCache与DiskCache介绍......
  • 分页列表缓存,你真的会吗
    开源中国的红薯哥写了很多关于缓存的文章,其中多级缓存思路,分页列表缓存这些知识点给了我很大的启发性。写这篇文章,我们聊聊分页列表缓存,希望能帮助大家提升缓存技术认知。1直接缓存分页列表结果显而易见,这是最简单易懂的方式。我们按照不同的分页条件来缓存分页结果,伪代码......