在Gin框架中简单使用redis做缓存系统

Gin使用redis缓存

概述

在Web应用中随着访问量的逐渐提高,使用关系型数据库的站点会遇到一些性能上的瓶颈——源头一般是磁盘的I/O。如今对网站的需求主要体现在低延迟的读写速度、支持海量的数据和流量、大规模集群的管理和运营成本的考量。而Redis在内存中运行,性能高效、支持分布式可扩展性强、键值对存储这些特征使其成为当前很最欢迎的NoSQL数据库之一。
redis的应用场景丰富,如缓存系统(热点数据:高频读、低频写)、消息队列、实时投票系统等。这篇笔记将在Gin框架中简单将redis作为缓存系统。这种场景读写redis一般步骤是查询redis是否存在某个key,如果存在则直接返回结果;如果redis中不存在某个key则在Mysql中查询数据,并写进redis缓存中并设定一个过期时间。当Mysql数据更新或删除数据时则将redis中对应的key-value修改掉。

场景介绍(缓存系统)

这里写的是一个很简单的场景,有一张叫post的数据表,用来存储文章id、标题、内容、作者id等这些信息。写好model、logic、controller层的代码能正常运行后,在本地环境运行时。下面是目录结构

.
├── controller
│   ├── post.go
│   ├── response.go
│   └── user.go
├── go.mod
├── go.sum
├── gredis
│   ├── keys.go
│   ├── post.go
│   ├── redis.go
│   └── user.go
├── logic
│   ├── post.go
│   └── user.go
├── main.go
├── middleware
│   └── cache.go
├── model
│   ├── post.go
│   └── user.go
└── mysql
    ├── mysql.go
    ├── post.go
    └── user.go

在Gin框架中简单使用redis做缓存系统

这里实现缓存的思路大致是这样:在gredis目录的keys.go文件中写常量,在gin处理请求时先从redis获取数据,如果不存在再去查询数据库,然后通过不同的key设置到redis中。

创建Redis客户端

var (
    rdb *redis.Client
    ctx = context.Background()
)

func InitRedis() (err error) {
    rdb = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: ""// no password set
        DB:       0,  // use default DB
    })

    _, err = rdb.Ping(ctx).Result()
    if err != nil {
        return err
    }
    return nil
}

创建redis中key常量

gredis目录中创建一个叫key.go的文件,用来存放一些常量。

package gredis

const (
    KeyUserIdSet = "CACHE/users/"
    KeyPostIdSet = "CACHE/posts/"
    CACHE_POSTS  = "CACHE/all-posts"
    CACHE_USERS  = "CACHE/all-users"
)

判断redis中key存在

判断redis数据库中是否存在key,用exists key来判断key是否存在。这里使用ExistUserKey函数来判断,如果返回的n == 0则说明key不存在。

// 查询key是否存在
func ExistKey(key stringbool {
    n, err := rdb.Exists(ctx, key).Result()
    if err != nil {
        log.Println("find exist user Key error :", err)
    }
    if n == 0 {
        log.Println(key, "key no exist")
        return false
    }
    log.Println(key, "key exist")
    return true
}

设置和获取缓存

// 根据文章Id获取缓存
func GetCachePostById(key string) (data *model.Post, err error) {
    res, err := rdb.Get(ctx, key).Result()
    if err != nil {
        log.Println("GET redis post error:", err)
        return nil, err
    }
    err = json.Unmarshal([]byte(res), &data)
    return data, nil
}

// 根据文章Id设置缓存
func SetCachePostById(data *model.Post, postid string) (err error) {
    strdata, _ := json.Marshal(data)
    key := fmt.Sprintf("%s%s", KeyPostIdSet, postid)
    err = rdb.Set(ctx, key, strdata, 10*time.Second).Err()
  // 设置key 10秒钟的过期时间
    if err != nil {
        log.Println("SET redis ERROR:", err)
        return err
    }
    return nil
}

设置为中间件

我们知道在gin框架中可以自定义中间件,如这样

func CacheMiddleware() gin.HandlerFunc {
    return func(c *gin.Context){
         // code ...
    }
}

我们在目录结果middleware/创建cache.go文件,用于判断redis中是否存在某个key。如果key是存在的,那么直接返回redis中的数据,并执行c.Abort;如果key不存在,那么执行c.Next(),往下查询数据库并给redis设置上值。

/*
1. 功能是判断redis中是否存在key,如果存在则取出缓存并返回数据;c.Abort
2. 如果redis中key不存在,则c.Next()继续查询数据库并设置上值
*/

func CacheMiddleware(key string) gin.HandlerFunc {
    return func(c *gin.Context) {
        isExists := gredis.ExistUserKey(key)
        if isExists == false { // 缓存不存在, 查询sql ,写入redis缓存
            c.Next()
        } else {
            // 取出缓存
            switch key {
            case gredis.CACHE_POSTS:
                data, _ := gredis.GetCacheAllPosts(key)
                controller.ResponseSuccess(c, data)
            case gredis.CACHE_USERS:
                data, _ := gredis.GetCacheAllUsers(key)
                controller.ResponseSuccess(c, data)
            }
            c.Abort()
        }
    }
}

在路由中加入缓存的中间件

r.GET("/getallpost", middleware.CacheMiddleware(gredis.CACHE_POSTS), controller.HandleGetAllPost)
r.GET("/getpostbypostid/:postid", middleware.CacheMiddleware(gredis.KeyPostIdSet), controller.HandleGetPostByPostId)

对比测试

做两次请求,第一次请求redis中不存在CACHE/all-posts这个key,第二次请求将结果已经缓存到redis当中。

在Gin框架中简单使用redis做缓存系统

在执行更新和删除操作时

当对数据库中某条数据修改或删除时,需要删除对应的缓存,或者设置缓存失效删除或更新缓存。实现有两种办法,一是更新完数据库后直接删除redis中对应的Key或设置Key过期时间;二是使用发布订阅实现异步缓存失效。
 第一种方法简单直接,但因为不是原子操作所以存在间隔时间窗口,可能导致短暂从缓存获取到旧数据的情况。第二种方法使用Redis的订阅发布功能,配合Go channel使用,根据接收的消息删除相关缓存。这种方式是原子操作,确保缓存与数据库同步。

使用删除和设置过期时间

// 删除某个key
func DeleteKey(key string) (err error) {
    if err = rdb.Del(ctx, key).Err(); err != nil {
        log.Println("Delete Key ERROR:", err)
        return err
    }
    return nil
}

// 设置key过期
func SetKeyExpired(key string) (err error) {
    err = rdb.ExpireAt(ctx, key, time.Now().Add(-10*time.Second)).Err()
    if err != nil {
        log.Println("Set Key Expired ERROR:", err)
        return err
    }
    return nil
}

使用发布订阅实现异步缓存失效

UpdatePost函数发布消息

// 更新文章缓存
func UpdatePost(key string) {
    rdb.Publish(ctx, "post_cache", key)
}

订阅对应的channel

// 订阅 channel
func SubChannel() {
    sub := rdb.Subscribe(ctx, "post_cache")
    ch := sub.Channel()
    go func() {
        for msg := range ch {
            if err := DeleteKey(msg.Payload); err != nil {
                log.Println("delete key ERROR:", err)
            }
        }
    }()
}

在controller中调用函数

func Handlexxx(c *gin.Context){
     ...
     
     gredis.UpdatePost(currentKey)
}

写在最后

本人是新手小白,如果这篇笔记中有任何错误或不准确之处,真诚地希望各位读者能够给予批评和指正。谢谢!

文中完整代码: https://github.com/FengZeHe/LearnRedis/tree/main/redis-cache-demo


原文始发于微信公众号(ProgrammerHe):在Gin框架中简单使用redis做缓存系统

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/207918.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!