11

go防止缓存穿透

 4 years ago
source link: https://studygolang.com/articles/27101
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

当线上接口请求量比较大时,如果恰好遇到缓存失效,会造成大量的请求直接打到数据库,导致数据库压力过大、甚至崩溃。如果缓存的数据实时性要求不那么高,可以试试 do-once-while-concurrent

https://github.com/abusizhish...

do-once-while-concurrent 中有三个主要方法,

Req 方法
Wait 方法
Release 方法

下面是一个简单的示例

我们的实际项目中有 两级缓存 ,一级 本地缓存 ,一级 redis ,如果都查询不到才会 读取mysql调用中台接口 ,本次只模拟 本地缓存失效 时, do-once-while-concurrent 对防止 缓存穿透 的处理(实际叫 重复资源过滤 更合理)

1.缓存失效时, 所有请求该缓存的请求会先调用 Req方法 对具有相同标签的重复请求进行拦截

2.只有第一个请求会 获取锁 ,执行读取redis操作

3.所有其他的线程 获取锁 失败,调用 Wait 方法,等待第一个线程 执行结束

4.第一个线程读取到用户信息,写入本地缓存,通过 close(chan) 事件来 广播消息

5.其他线程收到消息,结束 等待 ,读取本地缓存,返回用户信息

package main  
  
import (  
   "errors"  
   "fmt" 
   "github.com/abusizhishen/do-once-while-concurrent/src" 
   "log" 
   "sync" 
   "time"
)  
  
func main() {  
   //并发do something  
   for i := 0; i < 5; i++ {  
      go doSomeThing()  
   }  
  
   //避免程序直接退出
   time.Sleep(time.Second * 5)  
}  
  
var once src.DoOnce  
  
//模拟获取用户信息  
func doSomeThing() {  
   var userId = 12345  
   var user, err = getUserInfo(userId)  
   fmt.Println(user, err)  
}  
  
//example for usage  
// 演示获取用户详情的过程,先从本地缓存读取用户,如果本地缓存不存在,就从redis读取  
var keyUser = "user_%d"  
  
func getUserInfo(userId int) (user UserInfo, err error) {  
   user, err = userCache.GetUser(userId)  
   if err == nil {  
      return  
  }  
  
   log.Println(err)  
   var requestTag = fmt.Sprintf(keyUser, userId)  
   if !once.Req(requestTag) {  
      log.Println("没抢到锁,等待抢到锁的线程执行结束。。。")  
      once.Wait(requestTag)  
      log.Println("等待结束:", requestTag)  
      return userCache.GetUser(userId)  
   }  
  
   //得到资源后释放锁  
   defer once.Release(requestTag)  
   log.Println(requestTag, "获得锁,let's Go")  
  
   //为演示效果,sleep  
  time.Sleep(time.Second * 3)  
  
   //redis读取用户信息  
  log.Println("redis读取用户信息:", userId)  
  user, err = getUserInfoFromRedis(userId)  
  if err != nil {  
     return  
  }  
  
   //用户写入缓存  
  log.Println("用户写入缓存:", userId)  
  userCache.setUser(user)  
  return  
}  
  
//用户信息缓存  
type UserCache struct {  
   Users map[int]UserInfo  
   sync.RWMutex  
}  
  
type UserInfo struct {  
  Id   int  
  Name string  
  Age  int  
}  
  
var userCache UserCache  
var errUserNotFound = errors.New("user not found in cache")  
  
func (c *UserCache) GetUser(id int) (user UserInfo, err error) {  
   c.RLock()  
   defer c.RUnlock()  
   var ok bool  
   user, ok = userCache.Users[id]  
   if ok {  
      return  
   }  
  
   return user, errUserNotFound  
}  
  
func (c *UserCache) setUser(user UserInfo) {  
   c.Lock()  
   defer c.Unlock()  
   if c.Users == nil {  
      c.Users = make(map[int]UserInfo)  
   }  
  
   c.Users[user.Id] = user  
   return  
}  
  
func getUserInfoFromRedis(id int) (user UserInfo, err error) {  
   user = UserInfo{  
      Id:   12345,  
      Name: "abusizhishen",  
      Age:  18,  
  }  
   return  
}

输出

2020/03/09 20:11:39 user not found in cache
2020/03/09 20:11:39 user_12345 获得锁,let's Go
2020/03/09 20:11:39 user not found in cache
2020/03/09 20:11:39 没抢到锁,等待抢到锁的线程执行结束。。。
2020/03/09 20:11:39 user not found in cache
2020/03/09 20:11:39 没抢到锁,等待抢到锁的线程执行结束。。。
2020/03/09 20:11:39 user not found in cache
2020/03/09 20:11:39 user not found in cache
2020/03/09 20:11:39 没抢到锁,等待抢到锁的线程执行结束。。。
2020/03/09 20:11:39 没抢到锁,等待抢到锁的线程执行结束。。。
2020/03/09 20:11:42 redis读取用户信息: 12345
2020/03/09 20:11:42 用户写入缓存: 12345
2020/03/09 20:11:42 等待结束: user_12345
2020/03/09 20:11:42 等待结束: user_12345
{12345 abusizhishen 18} <nil>
{12345 abusizhishen 18} <nil>
{12345 abusizhishen 18} <nil>
2020/03/09 20:11:42 等待结束: user_12345
{12345 abusizhishen 18} <nil>
2020/03/09 20:11:42 等待结束: user_12345
{12345 abusizhishen 18} <nil>

可以看到,当第一个线程 获取锁 后,其他线程全部处于 等待状态 ,直到第一个线程 执行结果释放锁 ,其他线程 获取到数据 ,返回结果

事实上不止于防止 缓存穿透 , do-once-while-concurrent 更准确的定位是 重复资源过滤 ,,在某讲座业务中,使用 do-once-while-concurrent 来避免同一时刻同一用户id 重复解析 、列表页 重复检索排序 等,减少了资源竞争,提高了整体的 qps稳定性


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK