欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 3.添加缓存和缓存更新策略

3.添加缓存和缓存更新策略

2024/10/26 8:45:08 来源:https://blog.csdn.net/m0_57408211/article/details/138240328  浏览:    关键词:3.添加缓存和缓存更新策略

项目地址:https://github.com/liwook/PublicReview

添加缓存

查询商铺缓存

我们查询商店的时候,通过接口查询到的数据有很多,我们希望在此用Redis缓存数据,提高查询速度

对于店铺的详细数据,这种数据变化比较大,店家可能会随时修改店铺的相关信息(比如宣传语,店铺名等),所以对于这类变动较为频繁的数据,我们是直接存入Redis中,并且设置合适的有效期。

在internal目录添加shopservice文件夹,添加shopservice.go文件。

  1. 从Redis中查询商铺缓存
  2. 若是Redis中没有,则从数据库中查询,若是数据库也没有就返回没有。
  3. 数据库有,则写入到Redis中,并返回数据。
const ShopKeyPriex = "cache:shop:"// 根据商店id查找商店缓存数据
// get /shop/:id
func QueryShopById(c *gin.Context) {id := c.Param("id") //获取定义的路由参数的值if id == "" {code.WriteResponse(c, code.ErrValidation, "id can not be empty")return}//1.从redis查询商铺缓存,是string类型的val, err := db.RedisClient.Get(context.Background(), ShopKeyPriex+id).Result()if err == nil { //若redis存在该缓存,直接返回var shop model.TbShopsonic.Unmarshal([]byte(val), &shop)code.WriteResponse(c, code.ErrSuccess, shop)} else if err == redis.Nil { //2.若是redis没有该缓存,从mysql中查询tbSop := query.TbShopidInt, _ := strconv.Atoi(id)shop, err := tbSop.Where(tbSop.ID.Eq(uint64(idInt))).First()if err == gorm.ErrRecordNotFound {//3.mysql若不存在该商铺,返回错误code.WriteResponse(c, code.ErrDatabase, "this shop not found")return}if err != nil {slog.Error("mysql find shop by id bad", "error", err)code.WriteResponse(c, code.ErrDatabase, nil)return}//4.找到商铺,写回redis,并发送给客户端//把shop进行序列化,不然写入redis会出错。序列化就是把该数据对象变成json,即是变成一个字符串v, _ := sonic.Marshal(shop) //这里使用github.com/bytedance/sonic_, err = db.RedisClient.Set(context.Background(), ShopKeyPriex+id, v, 0).Result()if err != nil {slog.Error("redis set val bad", "error", err)code.WriteResponse(c, code.ErrDatabase, nil)}code.WriteResponse(c, code.ErrSuccess, shop)} else {code.WriteResponse(c, code.ErrSuccess, val)}
}

查询商户类型缓存

软件首页的这块列表信息是不变动的,因此我们可以将它存入缓存中,避免每次访问时都去查询数据库

那么这里一个key就会有多个元素,那我们可以使用Redis的list类型来存储。

注意:sonic.Marshal()返回的是[]byte。要是使用[]byte,会报错redis: can't marshal [][]uint8,所以要转换成string

// 返回商铺类型的数据,给首页
// get /shop/type-list
func QueryShopTypeList(c *gin.Context) {//1.先从redis中查询// 获取List中的元素:起始索引~结束索引,当结束索引 > llen(list)或=-1时,取出全部数据val, err := db.RedisClient.LRange(context.Background(), ShopTypeKey, 0, -1).Result()if err == redis.Nil || len(val) == 0 {//2. 若是没有,从mysql中获取shopType := query.TbShopTypetypeList, err := shopType.Order(shopType.Sort).Find() //Find函数返回没有数据的话,err是nilif err != nil {slog.Error("shoptypelist mysql find bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}if len(typeList) == 0 {code.WriteResponse(c, code.ErrSuccess, "no data in database")return}//3.序列化,并往redis中添加//注意:要是使用[]byte,会报错redis: can't marshal [][]uint8,所以要转换成stringpipeline := db.RedisClient.Pipeline()for _, shop := range typeList {val, _ := sonic.Marshal(shop)pipeline.RPush(context.Background(), ShopTypeKey, string(val))}_, err = pipeline.Exec(context.Background())if err != nil {slog.Error("redis list push bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}code.WriteResponse(c, code.ErrSuccess, typeList)} else if err != nil {slog.Error("redis list find bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)} else {//从Redis中获取的数据是字符串格式,而不是JSON格式,所以需要反序列化var valList = make([]*model.TbShopType, len(val))for i, v := range val {_ = sonic.UnmarshalString(v, &valList[i])}code.WriteResponse(c, code.ErrSuccess, val[0])}
}

在router.go中添加路由:

func NewRouter() *gin.Engine {r := gin.Default()//在测试阶段,为了方便,就不使用jwt中间件// r.Use(middleware.JWT()) //使用jwt中间件r.GET("/shop/:id", shopservice.QueryShopById)     //添加根据id查询商铺的路由r.GET("/shoptype", shopservice.QueryShopTypeList) //添加商铺类型的链表路由return r
}

缓存更新策略

现在商铺信息存储在了缓存和数据库中。由于缓存和数据库是分开的,无法做到原子性的进行数据修改,可能出现缓存更新失败,或者数据库更新失败的情况,这时候会出现数据不一致,影响业务。那么如何解决数据库和缓存不一致问题?

大方向有三种:

  • Cache Aside Pattern 旁路缓存模式,也叫人工编码方式:需要程序员写代码 同时维系 DB 和 cache。也称作双写方案。
  • Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关系缓存一致性问题。但是维护这样一个服务很复杂,市面上也不容易找到一个这样现成的服务,开发成本高。
  • Write Behind Caching Pattern:调用者只操作缓存,其他线程异步去处理数据库,最终实现一致性。但是维护这样的一个异步任务比较复杂,需要实时监控缓存中的数据更新,而其他线程异步去更新数据库也可能不太及时,而且缓存服务器如果宕机,那么缓存的数据也就丢失了。

综上所述,在企业的实际应用中,还是Cache Aside Pattern方案最可靠。现在确定了该方案,但是需要程序员去调用缓存和数据库?那因为是两个应用,那操作就有先后顺序,那是应该先操作哪个呢?还有是更新缓存还是删除缓存呢?

可以分成4种情况:

  • 先更新缓存,再更新数据
  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

具体的分析可以查看该文章如何保证Redis双写一致性?

先更新数据库,再删除缓存

前面的两个函数是查询,不是更新。那现在添加更新的函数。

更新商铺和添加商铺和删除商铺。只有在更新商铺删除商铺时候才需要删除缓存

// 更新商铺
// post /shop/update
func UpdateShop(c *gin.Context) {var shop model.TbShoperr := c.BindJSON(&shop)if err != nil {slog.Error("bindjson bad", "err", err)code.WriteResponse(c, code.ErrBind, nil)return}update(c, &shop)
}func update(c *gin.Context, shop *model.TbShop) {//1.更新数据库//当通过 struct 更新时,GORM 只会更新非零字段。//若想确保指定字段被更新,应使用Select更新选定字段,或使用map来完成更新tbshop := query.TbShop_, err := tbshop.Where(tbshop.ID.Eq(shop.ID)).Updates(shop)if err != nil {slog.Error("update mysql bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}//2.删除缓存key := ShopKeyPriex + strconv.Itoa(int(shop.ID))db.RedisClient.Del(context.Background(), key)code.WriteResponse(c, code.ErrSuccess, nil)
}// 添加商铺
// post /shop/add
func AddShop(c *gin.Context) {var shop model.TbShoperr := c.BindJSON(&shop)if err != nil {slog.Error("bindjson bad", "err", err)code.WriteResponse(c, code.ErrBind, nil)return}err = query.TbShop.Create(&shop)if err != nil {slog.Error("mysql create shop err", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)} else {code.WriteResponse(c, code.ErrSuccess, nil)}
}// 删除商铺
// delet /shop/delete/:id
func DelShop(c *gin.Context) {id := c.Param("id")if id == "" {code.WriteResponse(c, code.ErrValidation, "id is null")return}val, _ := strconv.Atoi(id)shop := query.TbShop_, err := shop.Where(shop.ID.Eq(uint64(val))).Delete()if err != nil {code.WriteResponse(c, code.ErrDatabase, nil)}//删除缓存key := ShopKeyPriex + iddb.RedisClient.Del(context.Background(), key)code.WriteResponse(c, code.ErrSuccess, nil)
}

在router.go中添加对应的路由

func NewRouter() *gin.Engine {r := gin.Default()// r.Use(middleware.JWT()) //使用jwt中间件..............r.POST("/shop/update", shopservice.UpdateShop)r.POST("/shop/add", shopservice.AddShop)r.DELETE("/shop/delete/:id", shopservice.DelShop)
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com