• 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.  
      )
  • 相关阅读:
    未能从程序集 C:Program Files (x86)MSBuild14.0inMicrosoft.Data.Entity.Build.Tasks.dll 加载任务“EntityClean”
    asp.net mvc 4 项目升级到 asp.net mvc5
    SQL Server查看所有表大小,所占空间
    0x80072f8a未指定的错误
    vs2012 aps.net4.0/4.5尚未在web服务器上注册
    vsphere 出现“在主机的当前连接状况下不允许执行该操作”
    sql server 发布时提示'dbo.sysmergepublications'无效的解决办法
    sql server更改机器名后更改数据库机器名
    Ajax向后台传入File类型参数
    上传下载Azure Blob里的Excel文件。
  • 原文地址:https://www.cnblogs.com/ExMan/p/14312235.html
Copyright © 2020-2023  润新知