go语言web开发系列之九:gin框架中用bigcache做进程内缓存

一,安装用到的库

1,安装go-redis

liuhongdi@ku:~$ go get -u github.com/go-redis/redis
 

2,安装bigcache

liuhongdi@ku:~$ go get -u github.com/allegro/bigcache

说明:刘宏缔的go森林是一个专注golang的博客,
          地址:https://blog.csdn.net/weixin_43881017

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/digv10

2,项目功能:演示redis+bigcache两级缓存,

                    通过订阅redis消息更新进程内缓存bigcache

3,项目结构:如图:

三,go代码说明

1,config/config.yaml

  1.  
    Database:
  2.  
    DBType: mysql
  3.  
    UserName: root
  4.  
    Password: password
  5.  
    Host: 127.0.0.1:3306
  6.  
    DBName: dig
  7.  
    Charset: utf8
  8.  
    ParseTime: True
  9.  
    MaxIdleConns: 10
  10.  
    MaxOpenConns: 30
  11.  
    Server:
  12.  
    RunMode: debug
  13.  
    HttpPort: 8000
  14.  
    ReadTimeout: 60
  15.  
    WriteTimeout: 60
  16.  
    Redis:
  17.  
    Addr: 127.0.0.1:6379
  18.  
    Password:

2,controller/setController.go

  1.  
    package controller
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    "github.com/liuhongdi/digv10/global"
  6.  
    "github.com/liuhongdi/digv10/pkg/result"
  7.  
    )
  8.  
     
  9.  
    type SetController struct{}
  10.  
     
  11.  
    //new
  12.  
    func NewSetController() SetController {
  13.  
    return SetController{}
  14.  
    }
  15.  
     
  16.  
    //发布一条消息到redis
  17.  
    func (u *SetController) Pub(c *gin.Context) {
  18.  
    resultRes := result.NewResult(c)
  19.  
    data:=c.Query("id")
  20.  
     
  21.  
    err := global.RedisDb.Publish("articleMsg", data).Err()
  22.  
    if err != nil {
  23.  
    resultRes.Error(400,err.Error())
  24.  
    return
  25.  
    } else {
  26.  
    resultRes.Success("发送成功")
  27.  
    }
  28.  
    }

3,bigcache/article.go

  1.  
    package bigcache
  2.  
     
  3.  
    import (
  4.  
    "encoding/json"
  5.  
    "fmt"
  6.  
    "github.com/liuhongdi/digv10/global"
  7.  
    "github.com/liuhongdi/digv10/model"
  8.  
    "strconv"
  9.  
    )
  10.  
     
  11.  
    //bigcache中索引的名字
  12.  
    func getArticleCacheName(articleId uint64) (string) {
  13.  
    return "article_"+strconv.FormatUint(articleId,10)
  14.  
    }
  15.  
     
  16.  
    //从bigcache得到一篇文章
  17.  
    func GetOneArticleBigCache(articleId uint64) (*model.Article,error) {
  18.  
    fmt.Println("bigcache:GetOneArticleBigCache")
  19.  
    key := getArticleCacheName(articleId);
  20.  
    val,err := global.BigCache.Get(key)
  21.  
    if (err != nil) {
  22.  
    return nil,err
  23.  
    } else {
  24.  
    article := model.Article{}
  25.  
    if err := json.Unmarshal([]byte(val), &article); err != nil {
  26.  
    return nil,err
  27.  
    }
  28.  
    return &article,nil
  29.  
    }
  30.  
    }
  31.  
    //向bigcache保存一篇文章
  32.  
    func SetOneArticleBigCache(articleId uint64,article *model.Article) (error) {
  33.  
    key := getArticleCacheName(articleId);
  34.  
    content,err := json.Marshal(article)
  35.  
    if (err != nil){
  36.  
    fmt.Println(err)
  37.  
    return err;
  38.  
    }
  39.  
    errSet := global.BigCache.Set(key,[]byte(content))
  40.  
    if (errSet != nil) {
  41.  
    return errSet
  42.  
    }
  43.  
    return nil
  44.  
    }

4,rediscache/article.go

  1.  
    package rediscache
  2.  
     
  3.  
    import (
  4.  
    "encoding/json"
  5.  
    "fmt"
  6.  
    "github.com/go-redis/redis"
  7.  
    "github.com/liuhongdi/digv10/global"
  8.  
    "github.com/liuhongdi/digv10/model"
  9.  
    "strconv"
  10.  
    "time"
  11.  
    )
  12.  
    //cache的过期时长
  13.  
    const ArticleDuration = time.Minute * 10
  14.  
     
  15.  
    //cache的名字
  16.  
    func getArticleCacheName(articleId uint64) (string) {
  17.  
    return "article_"+strconv.FormatUint(articleId,10)
  18.  
    }
  19.  
     
  20.  
    //从redis cache得到一篇文章
  21.  
    func GetOneArticleRedisCache(articleId uint64) (*model.Article,error) {
  22.  
    fmt.Println("redis:GetOneArticleRedisCache")
  23.  
    key := getArticleCacheName(articleId);
  24.  
    val, err := global.RedisDb.Get(key).Result()
  25.  
     
  26.  
    if (err == redis.Nil || err != nil) {
  27.  
    return nil,err
  28.  
    } else {
  29.  
    article := model.Article{}
  30.  
    if err := json.Unmarshal([]byte(val), &article); err != nil {
  31.  
    //t.Error(target)
  32.  
    return nil,err
  33.  
    }
  34.  
    return &article,nil
  35.  
    }
  36.  
    }
  37.  
    //向redis cache保存一篇文章
  38.  
    func SetOneArticleRedisCache(articleId uint64,article *model.Article) (error) {
  39.  
    key := getArticleCacheName(articleId);
  40.  
    content,err := json.Marshal(article)
  41.  
    if (err != nil){
  42.  
    fmt.Println(err)
  43.  
    return err;
  44.  
    }
  45.  
    errSet := global.RedisDb.Set(key, content, ArticleDuration).Err()
  46.  
    if (errSet != nil) {
  47.  
    return errSet
  48.  
    }
  49.  
    return nil
  50.  
    }

5,service/article.go

  1.  
    package service
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "github.com/liuhongdi/digv10/bigcache"
  6.  
    "github.com/liuhongdi/digv10/dao"
  7.  
    "github.com/liuhongdi/digv10/global"
  8.  
    "github.com/liuhongdi/digv10/model"
  9.  
    "github.com/liuhongdi/digv10/rediscache"
  10.  
    "strconv"
  11.  
    )
  12.  
     
  13.  
    //得到一篇文章的详情
  14.  
    func GetOneArticle(articleId uint64) (*model.Article, error) {
  15.  
    //get from bigcache
  16.  
    article,err := bigcache.GetOneArticleBigCache(articleId);
  17.  
    if ( err != nil) {
  18.  
    //get from redis
  19.  
    article,errSel := rediscache.GetOneArticleRedisCache(articleId)
  20.  
    if (errSel != nil) {
  21.  
    //get from mysql
  22.  
    article,errSel := dao.SelectOneArticle(articleId);
  23.  
    if (errSel != nil) {
  24.  
    return nil,errSel
  25.  
    } else {
  26.  
    //set redis cache
  27.  
    errSetR := rediscache.SetOneArticleRedisCache(articleId,article)
  28.  
    if (errSetR != nil){
  29.  
    fmt.Println(errSetR)
  30.  
    }
  31.  
    //set bigcache
  32.  
    errSetB := bigcache.SetOneArticleBigCache(articleId,article)
  33.  
    if (errSetB != nil){
  34.  
    fmt.Println(errSetB)
  35.  
    }
  36.  
    return article,nil
  37.  
    }
  38.  
    //return nil,errSel
  39.  
    } else {
  40.  
    //set bigcache
  41.  
    errSet := bigcache.SetOneArticleBigCache(articleId,article)
  42.  
    if (errSet != nil){
  43.  
    return nil,errSet
  44.  
    } else {
  45.  
    return article,errSel
  46.  
    }
  47.  
    }
  48.  
     
  49.  
    } else {
  50.  
    return article,err
  51.  
    }
  52.  
    }
  53.  
     
  54.  
    func GetArticleSum() (int, error) {
  55.  
    return dao.SelectcountAll()
  56.  
    }
  57.  
     
  58.  
    //得到多篇文章,按分页返回
  59.  
    func GetArticleList(page int ,pageSize int) ([]*model.Article,error) {
  60.  
    articles, err := dao.SelectAllArticle(page,pageSize)
  61.  
    if err != nil {
  62.  
    return nil,err
  63.  
    } else {
  64.  
    return articles,nil
  65.  
    }
  66.  
    }
  67.  
     
  68.  
    //从redis更新bigcache
  69.  
    func UpdateArticleBigcache(articleId uint64) (error) {
  70.  
    //get from redis
  71.  
    article,errSel := rediscache.GetOneArticleRedisCache(articleId)
  72.  
    if (errSel != nil) {
  73.  
     
  74.  
    return errSel
  75.  
    } else {
  76.  
    errSetB := bigcache.SetOneArticleBigCache(articleId, article)
  77.  
    if (errSetB != nil) {
  78.  
    fmt.Println(errSetB)
  79.  
    return errSetB
  80.  
    }
  81.  
    return nil
  82.  
    }
  83.  
    }
  84.  
     
  85.  
    //订阅redis消息
  86.  
    func SubMessage(channel string) {
  87.  
    pubsub := global.RedisDb.Subscribe(channel)
  88.  
    fmt.Println("subscribe begin Receive")
  89.  
    _, err := pubsub.Receive()
  90.  
    if err != nil {
  91.  
    return
  92.  
    }
  93.  
    fmt.Println("subscribe begin channel")
  94.  
    ch := pubsub.Channel()
  95.  
    for msg := range ch {
  96.  
    fmt.Println("message:")
  97.  
    fmt.Println( msg.Channel, msg.Payload, " ")
  98.  
    //把字符串转articleid
  99.  
    articleId,errUint := strconv.ParseUint(msg.Payload, 0, 64)
  100.  
    if (errUint != nil) {
  101.  
    fmt.Println(errUint)
  102.  
    } else {
  103.  
    //更新bigcache
  104.  
    errB := UpdateArticleBigcache(articleId)
  105.  
    if (errB != nil){
  106.  
    fmt.Println(errB)
  107.  
    }
  108.  
    }
  109.  
    }
  110.  
    }

6,main.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    _ "github.com/jinzhu/gorm/dialects/mysql"
  6.  
    "github.com/liuhongdi/digv10/global"
  7.  
    "github.com/liuhongdi/digv10/router"
  8.  
    "github.com/liuhongdi/digv10/service"
  9.  
    "log"
  10.  
    )
  11.  
     
  12.  
    //init
  13.  
    func init() {
  14.  
    //setting
  15.  
    err := global.SetupSetting()
  16.  
    if err != nil {
  17.  
    log.Fatalf("init.setupSetting err: %v", err)
  18.  
    }
  19.  
    //mysql
  20.  
    err = global.SetupDBLink()
  21.  
    if err != nil {
  22.  
    log.Fatalf("init.setupDBEngine err: %v", err)
  23.  
    }
  24.  
    //redis
  25.  
    err = global.SetupRedisDb()
  26.  
    if err != nil {
  27.  
    log.Fatalf("init.SetupRedisDb err: %v", err)
  28.  
    }
  29.  
    //bigcache
  30.  
    err = global.SetupBigCache()
  31.  
    if err != nil {
  32.  
    log.Fatalf("init.SetupGlobalCache err: %v", err)
  33.  
    }
  34.  
     
  35.  
    //redis sub
  36.  
    go service.SubMessage("articleMsg")
  37.  
    }
  38.  
     
  39.  
     
  40.  
    func main() {
  41.  
    //设置运行模式
  42.  
    gin.SetMode(global.ServerSetting.RunMode)
  43.  
    //引入路由
  44.  
    r := router.Router()
  45.  
    //run
  46.  
    r.Run(":"+global.ServerSetting.HttpPort)
  47.  
    }

7,global/bigcache.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "github.com/allegro/bigcache"
  5.  
    "log"
  6.  
    "time"
  7.  
    )
  8.  
    //定义一个全局的bigcache
  9.  
    var (
  10.  
    BigCache *bigcache.BigCache
  11.  
    )
  12.  
     
  13.  
    //创建一个全局的bigcache
  14.  
    func SetupBigCache() (error) {
  15.  
    config := bigcache.Config {
  16.  
    Shards: 1024, // 存储的条目数量,值必须是2的幂
  17.  
    LifeWindow: 3*time.Minute, // 超时后条目被处理
  18.  
    CleanWindow: 2*time.Minute, //处理超时条目的时间范围
  19.  
    MaxEntriesInWindow: 0, // 在 Life Window 中的最大数量,
  20.  
    MaxEntrySize: 0, // 条目最大尺寸,以字节为单位
  21.  
    HardMaxCacheSize: 0, // 设置缓存最大值,以MB为单位,超过了不在分配内存。0表示无限制分配
  22.  
    }
  23.  
    var initErr error
  24.  
    BigCache, initErr = bigcache.NewBigCache(config)
  25.  
    if initErr != nil {
  26.  
    log.Fatal(initErr)
  27.  
    return initErr
  28.  
    }
  29.  
    //BigCache.Stats().
  30.  
    return nil
  31.  
    }

8,global/redisDb.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "github.com/go-redis/redis"
  5.  
    )
  6.  
     
  7.  
    var (
  8.  
    RedisDb *redis.Client
  9.  
    )
  10.  
     
  11.  
    //创建redis链接
  12.  
    func SetupRedisDb() (error) {
  13.  
     
  14.  
    RedisDb = redis.NewClient(&redis.Options{
  15.  
    Addr: RedisSetting.Addr,
  16.  
    Password: RedisSetting.Password, // no password set
  17.  
    DB: 0, // use default DB
  18.  
    })
  19.  
     
  20.  
    _, err := RedisDb.Ping().Result()
  21.  
    if err != nil {
  22.  
    return err
  23.  
    }
  24.  
    return nil
  25.  
    }

9,global/setting.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "github.com/liuhongdi/digv10/pkg/setting"
  5.  
    "time"
  6.  
    )
  7.  
    //服务器配置
  8.  
    type ServerSettingS struct {
  9.  
    RunMode string
  10.  
    HttpPort string
  11.  
    ReadTimeout time.Duration
  12.  
    WriteTimeout time.Duration
  13.  
    }
  14.  
    //数据库配置
  15.  
    type DatabaseSettingS struct {
  16.  
    DBType string
  17.  
    UserName string
  18.  
    Password string
  19.  
    Host string
  20.  
    DBName string
  21.  
    Charset string
  22.  
    ParseTime bool
  23.  
    MaxIdleConns int
  24.  
    MaxOpenConns int
  25.  
    }
  26.  
    //redis配置
  27.  
    type RedisSettingS struct {
  28.  
    Addr string
  29.  
    Password string
  30.  
    }
  31.  
    //定义全局变量
  32.  
    var (
  33.  
    ServerSetting *ServerSettingS
  34.  
    DatabaseSetting *DatabaseSettingS
  35.  
    RedisSetting *RedisSettingS
  36.  
    )
  37.  
     
  38.  
    //读取配置到全局变量
  39.  
    func SetupSetting() error {
  40.  
    s, err := setting.NewSetting()
  41.  
    if err != nil {
  42.  
    return err
  43.  
    }
  44.  
    err = s.ReadSection("Database", &DatabaseSetting)
  45.  
    if err != nil {
  46.  
    return err
  47.  
    }
  48.  
     
  49.  
    err = s.ReadSection("Server", &ServerSetting)
  50.  
    if err != nil {
  51.  
    return err
  52.  
    }
  53.  
     
  54.  
    err = s.ReadSection("Redis", &RedisSetting)
  55.  
    if err != nil {
  56.  
    return err
  57.  
    }
  58.  
    return nil
  59.  
    }

10,其他相关代码可访问github

四,测试效果

1,查看两级缓存的效果

访问url:

http://127.0.0.1:8000/article/getone/2

返回:

查看控制台:

  1.  
    bigcache:GetOneArticleBigCache
  2.  
    redis:GetOneArticleRedisCache
  3.  
    mysql:SelectOneArticle
  4.  
     
  5.  
    (/data/liuhongdi/digv10/dao/article.go:13)
  6.  
    [2020-12-22 17:06:12] [99.30ms] SELECT articleId, subject, url FROM `article` WHERE (articleId=2) LIMIT 1
  7.  
    [1 rows affected or returned ]
  8.  
    len: 1
  9.  
    Capacity: 350
  10.  
    [GIN] 2020/12/22 - 17:06:12 | 200 | 156.31128ms | 127.0.0.1 | GET "/article/getone/2"
  11.  
    bigcache:GetOneArticleBigCache
  12.  
    [GIN] 2020/12/22 - 17:06:16 | 200 | 116.705µs | 127.0.0.1 | GET "/article/getone/2"
  13.  
    bigcache:GetOneArticleBigCache
  14.  
    [GIN] 2020/12/22 - 17:06:18 | 200 | 82.248µs | 127.0.0.1 | GET "/article/getone/2"
  15.  
    bigcache:GetOneArticleBigCache
  16.  
    [GIN] 2020/12/22 - 17:06:20 | 200 | 90.045µs | 127.0.0.1 | GET "/article/getone/2"

可以看到,第一次从数据库查询时用了100ms以上,

而从bigcache查询时,时间在100µs左右

重启一次应用,因为重启后bigcache已丢失,应用会从redis中读取数据

查看控制台:

  1.  
    bigcache:GetOneArticleBigCache
  2.  
    redis:GetOneArticleRedisCache
  3.  
    len: 1
  4.  
    Capacity: 350
  5.  
    [GIN] 2020/12/22 - 17:08:28 | 200 | 535.077µs | 127.0.0.1 | GET "/article/getone/2"
  6.  
    bigcache:GetOneArticleBigCache
  7.  
    [GIN] 2020/12/22 - 17:08:30 | 200 | 121.435µs | 127.0.0.1 | GET "/article/getone/2"
  8.  
    bigcache:GetOneArticleBigCache
  9.  
    [GIN] 2020/12/22 - 17:08:37 | 200 | 96.567µs | 127.0.0.1 | GET "/article/getone/2"
  10.  
    bigcache:GetOneArticleBigCache
  11.  
    [GIN] 2020/12/22 - 17:08:39 | 200 | 84.293µs | 127.0.0.1 | GET "/article/getone/2"

可以看到从redis中查询时,用时在500µs以上

2,通过订阅redis消息更新进程内缓存:

访问url:

http://127.0.0.1:8000/article/getone/2

返回:

我们手动更新redis,

然后通过push消息让本地的应用内缓存得到及时更新:

访问:

http://127.0.0.1:8000/set/pub?id=2

再次访问文章地址:

五,查看库的版本

  1.  
    module github.com/liuhongdi/digv10
  2.  
     
  3.  
    go 1.15
  4.  
     
  5.  
    require (
  6.  
    github.com/gin-gonic/gin v1.6.3
  7.  
    github.com/go-playground/universal-translator v0.17.0
  8.  
    github.com/go-playground/validator/v10 v10.2.0
  9.  
    github.com/jinzhu/gorm v1.9.16
  10.  
    github.com/magiconair/properties v1.8.4 // indirect
  11.  
    github.com/mitchellh/mapstructure v1.3.3 // indirect
  12.  
    github.com/pelletier/go-toml v1.8.1 // indirect
  13.  
    github.com/spf13/afero v1.4.1 // indirect
  14.  
    github.com/spf13/cast v1.3.1 // indirect
  15.  
    github.com/spf13/jwalterweatherman v1.1.0 // indirect
  16.  
    github.com/spf13/pflag v1.0.5 // indirect
  17.  
    github.com/spf13/viper v1.7.1
  18.  
    github.com/allegro/bigcache v1.2.1
  19.  
    github.com/go-redis/redis v6.15.9+incompatible
  20.  
    golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
  21.  
    golang.org/x/text v0.3.4 // indirect
  22.  
    gopkg.in/ini.v1 v1.62.0 // indirect
  23.  
    gopkg.in/yaml.v2 v2.3.0 // indirect
  24.  
    )
原文地址:https://www.cnblogs.com/ExMan/p/14312235.html