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

简介: 从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=9888
server.rpc.port=6666
db.driver=mysql
db.url=127.0.0.1:3306
db.databases=test
db.username=root
db.password=12345
redis.url=127.0.0.1:6379
redis.db=1
redis.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的查询并没有增加缓存,当然也是为了方便演示。

今天的分享就到这里。

相关文章
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
1266 1
|
10月前
|
存储 前端开发 安全
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
389 5
|
11月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
2578 10
|
存储 缓存 监控
手动清除Ubuntu系统中的内存缓存的步骤
此外,只有系统管理员或具有适当权限的用户才能执行这些命令,因为这涉及到系统级的操作。普通用户尝试执行这些操作会因权限不足而失败。
2127 22
|
12月前
|
小程序 安全 关系型数据库
专业打造一款圈子源码软件系统 / 后端 PHP 搭建部署一样实现利益化
本教程详解基于PHP后端与Uni-app的小程序开发全流程,涵盖技术选型、环境搭建、源码导入、接口对接及功能实现。采用Laravel/Symfony框架,结合MySQL/PostgreSQL数据库,使用WebSocket实现实时通信,并集成IM SDK实现音视频聊天。前端使用Uni-app开发,支持跨平台运行。教程包含完整部署流程与安全优化方案,助力快速搭建高性能、安全稳定的小程序系统。
647 5
|
存储 消息中间件 前端开发
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
校园圈子系统校园论坛小程序采用uni-app前端框架,支持多端运行,结合PHP后端(如ThinkPHP/Laravel),实现用户认证、社交关系管理、动态发布与实时聊天功能。前端通过组件化开发和uni.request与后端交互,后端提供RESTful API处理业务逻辑并存储数据于MySQL。同时引入Redis缓存热点数据,RabbitMQ处理异步任务,优化系统性能。核心功能包括JWT身份验证、好友系统、WebSocket实时聊天及活动管理,确保高效稳定的用户体验。
756 4
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
|
11月前
|
缓存 监控 Ubuntu
Ubuntu操作系统下清除系统缓存与无用文件的方法
通过上述步骤断行综合性地对Ubuntu进行优化与整洁可显著改善其性能表现及响应速度。然而,请注意在执行某些操作前确保充分了解其潜在影响;例如,在移除旧内核之前确认新内核稳定运行无问题;而对于关键配置更改则需确保备份好相关设置以便恢复原状态。
2645 0
|
自然语言处理 JavaScript 前端开发
一夜获千星!已获 1.7k+,Art Design Pro:Vue3 + Vite + TypeScript 打造的高颜值管理系统模板,这个让后端小哥直呼救命的后台系统
Art Design Pro 是一款基于 Vue 3、Vite 和 TypeScript 的高颜值后台管理系统模板,已获 1.7k+ 星标。项目专注于用户体验与视觉设计,支持主题切换、多语言、权限管理及图表展示等功能,内置常用业务组件,便于快速搭建现代化管理界面。其技术栈先进,开发体验流畅,适配多设备,满足企业级应用需求。项目地址:[GitHub](https://github.com/Daymychen/art-design-pro)。
2326 11
|
SQL API 容器
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:后端项目结构说明
GoWind Admin|风行是一款企业级前后端一体中后台框架,后端采用Go语言,基于Protobuf定义API,支持Buf代码生成与Docker部署。项目结构清晰,含API定义、服务实现、数据库ORM、配置管理及自动化脚本,开箱即用,助力高效开发。
594 0
|
JSON Cloud Native Go
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:后端权限控制
GoWind Admin「风行」是企业级中后台框架,集成Casbin与OPA实现API权限控制。支持RBAC、ABAC等模型,开箱即用,助力高效构建安全的前后端一体化系统。
982 1

热门文章

最新文章