缓存穿透,缓存雪崩,缓存击穿

一,缓存穿透

原因:一个请求来访问某个数据,发现缓存中没有,直接去DB中访问。此种情况就是穿透。(正常情况下缓存跟数据库中数据都是存在,异常情况下会导致)

特点:因传递了非法的key,导致缓存跟数据库中都无法查询

方法:

1.添加参数校验,校验传递的值是否合法,再决定是否处理该请求。

2.设置缓存空值,为访问的key缓存中设置对应的nil值,这样当访问过来的发现key的value是空值值,就直接返回nil了。并设置过期时间。

3,布隆过滤器,就是利用高效的数据结构,在请求跟缓存之间设置一个布隆过滤器,请求来的时候,直接判断当前的key是否存在DB中。也能很好防止发生缓存穿透。不存在直接返回,存在的话,直接访问数据库,在刷新缓存即可。

package main

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"sync"

	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	_ "github.com/go-sql-driver/mysql"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	ID       int64
	Username string `gorm:"column:username"`
	Password string `gorm:"column:password"`
	// 时间戳
	CreateTime int64 `gorm:"column:createtime"`
}

// 数据库操作
var DB *sql.DB

func InitDB() error {
	// 初始化数据库
	db, err := sql.Open("mysql",
		"root:password@tcp(127.0.0.1:3306)/my_sql?charset=utf8")
	if err != nil {
		log.Fatal(err.Error())
		return err
	}
	DB = db
	return nil
}

// redis
var client *redis.Client

func InitRedis() {
	cl := redis.NewClient(
		&redis.Options{
			Addr:     "127.0.0.1:6379",
			Password: "password",
		})
	client = cl
}

var OrmDB *gorm.DB

// gorm
func InitOrmDB() {

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
		"root", "password", "127.0.0.1", 3306, "my_sql")
	// 连接 mysql
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("数据库连接失败, error" + err.Error())
	}
	OrmDB = db
}

// 布隆过滤器,高可用访问kv
var userMap sync.Map

func main() {

	InitOrmDB()
	InitRedis()

	r := gin.Default()
	router := r.Group("/user")
	router.POST("/add", addUser)
	router.GET("/get", getUser)

	fmt.Println("Server is running ...")
	r.Run(":8080")
}

// add user
func addUser(c *gin.Context) {
	name := c.Request.FormValue("name")
	password := c.Request.FormValue("password")

	fmt.Println("++=", name, password)
	// 检查表是是否存在
	if !OrmDB.Migrator().HasTable("users") {
		fmt.Println("user is not exist")
		OrmDB.Migrator().CreateTable(&User{})
	}
	// 构建数据
	ur := &User{
		Username:   name,
		Password:   password,
		CreateTime: time.Now().Unix(),
	}

	if err := OrmDB.Create(ur).Error; err != nil {
		fmt.Println("插入失败", err)
		return
	}

	// 将数据添加到布隆过滤器
	userMap.Store(name, ur)
	c.JSON(200, gin.H{"message": "success to add a user info"})
}

func getUser(c *gin.Context) {
	// 缓存中查询
	id := c.Request.FormValue("id")
	name := c.Request.FormValue("name")

	// 添加一个布隆过滤器
	if _, ok := userMap.Load(name); !ok {
		// 不存在该用户,说明非法参数,直接返回,可以避免缓存穿透
		fmt.Println("illlage user", name)
		return
	}

	// 缓存穿透,从数据库中查询,因为使用了非法参数,一般情况下,缓存跟数据库都是有数据的
	key := id + "_" + name
	ret := client.Get(key)
	if ret.Err() == nil {
		fmt.Println("cache get")
		c.JSON(200, gin.H{"user": ret.String()})
		return
	}

	fmt.Println("cache is not data,will go to db find ")

	// 数据库查询
	us := &User{}
	result := OrmDB.Where("username = ? AND id = ?", name, id).First(&us)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		// 为当前的非法key设置nil,直接返回,或者使用布隆过滤器
		client.Set(key, nil, time.Second*10) // 临时设置一个nil,避免后续请求大量访问db
		fmt.Println("找不到记录")
		return
	} else {
		retUser, _ := json.Marshal(us)
		// 将数据重新写入到缓存
		retSet := client.Set(key, retUser, 0) // 设置都是不过期,仅仅测试使用
		if retSet.Err() != nil {
			fmt.Println("set cache is failed")
			return
		}
		c.JSON(200, gin.H{"user": retUser})
	}
}

二,缓存雪崩

原因:因大量的数据在同一时间内因为过期失效了,与此同时大量的请求到来,发现缓存中没有数据,都奔向DB,结果导致DB承受不了大规模的请求处理导致服务崩溃。(当然还有可能是缓存宕机)

特点:大量不同数据在缓存同一时间同时失效,并被大量请求访问至DB导致

方法:按照具体的实际情况来解决处理。

a,缓存宕机导致,那么就采用集群模式,并设置主从 + 哨兵模式,确保容灾的发生能及时供给提供服务。另外再开启持久化,宕机服务重启之后重新将数据加载出来。

b,数据过期导致,那么就采用在设置数据过期时间每个key添加随机数,确保不会再某一时间,大量的key同时失效。在这针对热数据,可以设置永不过期处理,有更新进行更新处理就可。

c.如果事先没考虑到雪崩问题,但是已经发生了。

处理方法:可采取限流 + 本地缓存进行补救,但是还得看具体情况具体对待。

三,缓存击穿

原因:因某个数据过期了,刚好此时大量请求都请求到DB上情况导致。

特点:某一数据因过期,导致访问缓存拿不到数据,击穿缓存去DB拿取。

该种情景跟雪崩有点类似。但是也有差异,雪崩是大面积缓存失效导致。击穿是一个热key在不停的被访问(刚好失效)。就会造成某一时候数据库的压力过大。

方法:

a.可以根据具体情况设置热数据不过期,定期刷新数据即可。

b,访问数据库加锁,第一个请求访问加锁,其余来访问,会被阻塞,一直到锁被释放,当后面的请求访问时,发现已经有缓存了,(因为前面访问过DB会将数据刷新到缓存中)就直接访问缓存了。但是该方法会导致性能,从而降低了吞吐量。所有要结合业务场景思考权衡利弊来做处理。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值