写点什么

从 2 开始,在 Go 语言后端业务系统中引入缓存

作者:Barry Yan
  • 2022-11-03
    北京
  • 本文字数:2984 字

    阅读完需:约 10 分钟

从2开始,在Go语言后端业务系统中引入缓存

本次我们接着上两篇文章进行讲解《从0开始,用Go语言搭建一个简单的后端业务系统》《从1开始,扩展Go语言后端业务系统的RPC功能》,如题,需求就是为了应对查询时的高 qps,我们引入 Redis 缓存,让查询数据时不直接将请求发送到数据库,而是先通过一层缓存来抵挡 qps,下面我们开始今天的分享:

1 逻辑设计


如图,本次缓存设计的逻辑就是在查询时首先查询缓存,如果查询不到则查询数据库(实际中不建议,会发生缓存穿透),在增删改时会先改数据库,再改缓存。

2 代码

2.1 项目结构

2.2 下载依赖

go get github.com/go-redis/redis/v8
复制代码

2.3 具体代码和配置

配置:


package config
import ( "fmt" "github.com/go-redis/redis/v8" "github.com/spf13/viper")
var RDB *redis.Client
func init() { var err error viper.SetConfigName("app") viper.SetConfigType("properties") viper.AddConfigPath("./") err = viper.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %w \n", err)) } if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { fmt.Println("No file ...") } else { fmt.Println("Find file but have err ...") } } add := viper.GetString("redis.url") pwd := viper.GetString("redis.password") db := viper.GetInt("redis.db") RDB = redis.NewClient(&redis.Options{ Addr: add, Password: pwd, DB: db, })}
复制代码


Cache 层:


package cache
import ( "context" "count_num/pkg/config" "count_num/pkg/entity" "encoding/json" "github.com/go-redis/redis/v8" "time")
type CountNumCacheDAOImpl struct { db *redis.Client}
type CountNumCacheDAO interface { // set一个 SetNumInfo(ctx context.Context, key string, info entity.NumInfo, t time.Duration) bool // 根据ID获取一个 GetNumInfoById(ctx context.Context, key string) entity.NumInfo}
func NewCountNumCacheDAOImpl() *CountNumCacheDAOImpl { return &CountNumCacheDAOImpl{db: config.RDB}}
func (impl CountNumCacheDAOImpl) SetNumInfo(ctx context.Context, key string, info entity.NumInfo, t time.Duration) bool { res := impl.db.Set(ctx, key, info, t) result, _ := res.Result() if result != "OK" { return false } return true}
func (impl CountNumCacheDAOImpl) GetNumInfoById(ctx context.Context, key string) entity.NumInfo { res := impl.db.Get(ctx, key) var info entity.NumInfo j := res.Val() json.Unmarshal([]byte(j), &info) return info}
复制代码


DAO 层实现类:


package impl
import ( "context" "count_num/pkg/cache" "count_num/pkg/config" "count_num/pkg/entity" "fmt" "gorm.io/gorm" "time")
var cacheTime = time.Second * 3600
type CountNumDAOImpl struct { db *gorm.DB cache *cache.CountNumCacheDAOImpl}
func NewCountNumDAOImpl() *CountNumDAOImpl { return &CountNumDAOImpl{db: config.DB, cache: cache.NewCountNumCacheDAOImpl()}}
func (impl CountNumDAOImpl) AddNumInfo(ctx context.Context, info entity.NumInfo) bool { var in entity.NumInfo impl.db.First(&in, "info_key", info.InfoKey) if in.InfoKey == info.InfoKey { //去重 return false } impl.db.Save(&info) //要使用指针,Id可以回显 impl.cache.SetNumInfo(ctx, string(info.Id), info, cacheTime) return true}
func (impl CountNumDAOImpl) GetNumInfoByKey(ctx context.Context, key string) entity.NumInfo { var info entity.NumInfo impl.db.First(&info, "info_key", key) return info}
func (impl CountNumDAOImpl) FindAllNumInfo(ctx context.Context) []entity.NumInfo { var infos []entity.NumInfo impl.db.Find(&infos) return infos}
func (impl CountNumDAOImpl) UpdateNumInfoByKey(ctx context.Context, info entity.NumInfo) bool { impl.db.Model(&entity.NumInfo{}).Where("info_key = ?", info.InfoKey).Update("info_num", info.InfoNum) return true}
func (impl CountNumDAOImpl) DeleteNumInfoById(ctx context.Context, id int64) bool { impl.db.Delete(&entity.NumInfo{}, id) impl.cache.SetNumInfo(ctx, string(info.Id), "", cacheTime) return true}
func (impl CountNumDAOImpl) GetNumInfoById(ctx context.Context, id int64) entity.NumInfo { var info entity.NumInfo numInfoById := impl.cache.GetNumInfoById(ctx, string(id)) if numInfoById.InfoKey != "" { return numInfoById } impl.db.First(&info, "id", id) return info}
func (impl CountNumDAOImpl) UpdateNumInfoById(ctx context.Context, info entity.NumInfo) bool { impl.db.Model(&entity.NumInfo{}).Where("id", info.Id).Updates(entity.NumInfo{Name: info.Name, InfoKey: info.InfoKey, InfoNum: info.InfoNum}) impl.cache.SetNumInfo(ctx, string(info.Id), info, cacheTime) return true}
复制代码


实体类:


package entity
import "encoding/json"
type NumInfo struct { Id int64 `json:"id"` Name string `json:"name"` InfoKey string `json:"info_key"` InfoNum int64 `json:"info_num"`}
func (stu NumInfo) TableName() string { return "num_info"}
func (info NumInfo) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "id": info.Id, "name": info.Name, "info_key": info.InfoKey, "info_num": info.InfoNum, })}
//Redis类似序列化操作func (info NumInfo) MarshalBinary() ([]byte, error) { return json.Marshal(info)}
func (info NumInfo) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, &info)}
复制代码


配置文件:


server.port=9888server.rpc.port=6666db.driver=mysqldb.url=127.0.0.1:3306db.databases=testdb.username=rootdb.password=12345
redis.url=127.0.0.1:6379redis.db=1redis.password=
复制代码

3 遇见问题及解决

出现问题,根据提示我们大约能理解是 Go 语言中结构体类似序列化的问题:



解决—结构体实现接口:


//Redis类似序列化操作func (info NumInfo) MarshalBinary() ([]byte, error) {   return json.Marshal(info)}
func (info NumInfo) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, &info)}
复制代码

4 总结

引入 Redis 缓存是后端业务中应对高并发查询比较常见的一个做法,在软件工程学中有一句话叫做:计算机的所有问题都可以用加一层来解决。


在本次项目中可以说缓存设计的相对简单,针对 Key 的查询并没有增加缓存,当然也是为了方便演示。


今天的分享就到这里。

发布于: 刚刚阅读数: 4
用户头像

Barry Yan

关注

做兴趣使然的Hero 2021-01-14 加入

Just do it.

评论

发布
暂无评论
从2开始,在Go语言后端业务系统中引入缓存_Go_Barry Yan_InfoQ写作社区