前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:你可以写一个通用的Redis缓存”装饰器“么?

面试官:你可以写一个通用的Redis缓存”装饰器“么?

作者头像
小锟哥哥
发布2022-05-10 09:04:51
3710
发布2022-05-10 09:04:51
举报
文章被收录于专栏:GoLang全栈

今天是小年,先祝大家小年快乐!

所以我得送一篇技术文章庆祝一下,

请看今天我们咋用”装饰器模式“搞定Redis的缓存。

啥是装饰器模式?

首先得先搞懂啥是装饰器,学过 Java 或者 Python 的同学应该不会陌生,比如这样:

代码语言:javascript
复制
public class Hello implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Hello");
   }
}

里面的那个 @Override 就是装饰器,具体咋实现的呢?

请询问资深 Java 工程师去。

为啥叫装饰器呢?

个人觉得可能看他在方法的上面,像头饰吧,具体是不是这原因,我也不知道,不对别打我哈。

其实,你可以理解他就是一个闭包方法,要调用被修饰的方法之前就需要先经过他,有点像拦路虎

听着是不是很像中间件,其实是差不太多的逻辑啦。

但是为啥我们不直接用中间件来搞缓存呢?

中间件他一般是挂在某个路由组下面的,但是呢,我们要做缓存的又不可能整个路由组都需要做。

于是就想着用装饰器的思路去搞定这个缓存,我可以在我需要的某个方法之前戴一个装饰器就可以了。

先实现一个传统的API

我们这里使用 Gin 框架来搭建:

代码语言:javascript
复制
func UserListHandler() gin.HandlerFunc {
 return func(c *gin.Context) {
  list := db.GetUserListFromMySQL()
  res := gin.H{
   "list": list,
  }
  c.JSON(200, res)
 }
}

func UserDetailHandler() gin.HandlerFunc {
 return func(c *gin.Context) {
  user := db.GetUserDetailListFromMySQL()
  res := gin.H{
   "user": user,
  }
  c.JSON(200, res)
 }
}

func main() {

 r := gin.Default()
 r.GET("/user/list/:type", UserListHandler())
 r.GET("/user/detail/:id", UserDetailHandler())
 r.Run()

}

我们 db 部分我们就写一个模拟方法,去模拟从数据库里面读取数据:

代码语言:javascript
复制
package db

import "fmt"

type User struct {
 Id   int64
 Name  string
}

func GetUserListFromMySQL() *[]User {
 fmt.Println("模拟从数据库获取数据...")
 list := make([]User,2)
 list[0] = User{
  Id:   1,
  Name: "张三",
 }
 list[1] = User{
  Id:   2,
  Name: "李四",
 }
 return &list
}

func GetUserDetailListFromMySQL() *User {
 fmt.Println("模拟从数据库获取数据...")
 return &User{
  Id:   2,
  Name: "李四",
 }
}

这样以来就能跑起来了。

预热下 Redis

我们使用的库是:

代码语言:javascript
复制
github.com/gomodule/redigo/redis

如果不知道怎么使用的,请参考我们往期 redis 的教程文章!

这里我粘贴下关键代码:

代码语言:javascript
复制
package k_redis

import (
 "github.com/gomodule/redigo/redis"
 "time"
)

var RedisDefaultPool *redis.Pool
func newPool(addr string) *redis.Pool  {
 return &redis.Pool{
  MaxIdle:         3,
  IdleTimeout:     240*time.Second,
  Dial: func() (redis.Conn, error) {
   return redis.Dial("tcp", addr, redis.DialPassword("密码"))
  },
 }
}

func init()  {
 RedisDefaultPool = newPool("IP:端口")
}

接下来我们就可以使用 Redis 了:

代码语言:javascript
复制
// 读
conn := k_redis.RedisDefaultPool.Get()
defer conn.Close()
res, err := redis.String(conn.Do("get", redisKey))
fmt.Println(res)

// 写
conn.Do("setex", redisKey, 20, resData)

编写装饰器

我们的装饰器咋加呢?

需要在路由方法做手脚,也就是这里:

代码语言:javascript
复制
r.GET("/user/list/:type", UserListHandler())

我们只需要在 UserListHandler 这个方法外面再套一个方法,这个方法就是装饰器!

这个方法我们需要满足:传入的是 gin.HandlerFunc 方法,传出的也是 gin.HandlerFunc 这个即可!

但是为了通用性,我们需要加三个入参:

1、Redis里面的key规则参数 redisKeyPattern

2、Redis里面的key关键字参数 param

3、返回回去的数据参数 empty

开干,代码如下:

代码语言:javascript
复制
func Decorator(h gin.HandlerFunc, param string, redisKeyPattern string, empty interface{}) gin.HandlerFunc {
 return func(c *gin.Context) {
  
  // 取Redis里面的key关键字参数
  getId := c.Param(param)
  // 根据Redis里面key的规则,生成RedisKey
  redisKey := fmt.Sprintf(redisKeyPattern, getId)
  // 从Redis里面读取数据
  conn := k_redis.RedisDefaultPool.Get()
  defer conn.Close()
  res, err := redis.String(conn.Do("get", redisKey))

  if err != nil { //缓存没有
   log.Println("从数据库取...",err)
   // 执行下一部分
   h(c)
   dbRes,exists := c.Get("Result")
   if !exists {
    dbRes = empty
   }
   // 存缓存 转成字节流存
   resData,_ := json.Marshal(dbRes)
   conn.Do("setex", redisKey, 20, resData)

   c.JSON(200, dbRes)
  }else{
   log.Println("从缓存库取...")

   json.Unmarshal(res, &empty)
   c.JSON(200, empty)
  }
 }
}

里面有很多 error 我给忽略了,读者可自行根据需要处理!

这个装饰器比较关键的点在 c.Get("Result") 这个逻辑,我们之前的两个控制器方法就需要改造了!

代码语言:javascript
复制
func UserListHandler() gin.HandlerFunc {
 return func(c *gin.Context) {
  list := db.GetUserListFromMySQL()
  res := gin.H{
   "list": list,
  }
  //c.JSON(200, res)
  c.Set("Result", res)
 }
}

func UserDetailHandler() gin.HandlerFunc {
 return func(c *gin.Context) {
  user := db.GetUserDetailListFromMySQL()
  res := gin.H{
   "user": user,
  }
  //c.JSON(200, res)
  c.Set("Result", res)
 }
}

我们不能在这里面返回 json 数据了,而是通过 gin 的上下文进行值传递。

依次传递到装饰器里面。

所以在装饰器里面才可以通过 c.Get("Result") 来获取到值!

如果你对这段代码有疑问的,可以在我们的公众号回复 加群 到我们的交流群里面进行提问讨论。

上装饰器

接下来就是上装饰器:

代码语言:javascript
复制
func main() {

 r := gin.Default()
 r.GET("/user/list/:type", Decorator(UserListHandler(), "type", "user_list_%s", []db.User{}))
 r.GET("/user/detail/:id", Decorator(UserDetailHandler(), "id", "user_%s", db.User{}))
 r.Run()

}

你只需要在需要做缓存的方法外面套一个 Decorator 方法就可以完成缓存处理了!

我们这个装饰器,可能不像 Java 那么像头饰,但是也能用,不是吗?

你学废了么?

赶快去动手实现一个吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GoLang全栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 啥是装饰器模式?
  • 先实现一个传统的API
  • 预热下 Redis
  • 编写装饰器
  • 上装饰器
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档