当前位置: 首页 > 科技观察

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

时间:2023-03-21 17:34:07 科技观察

本文转载自微信公众号《GoLang全栈》,作者肖鲲。转载本文请联系GoLang全栈公众号。今天是新年,祝大家新年快乐!所以不得不发一篇技术文章来庆祝一下,请看我们今天是如何使用“装饰器模式”修复Redis缓存的。什么是装饰者模式?首先你要明白什么是装饰器。学过Java或者Python的同学应该不陌生,比如:publicclassHelloimplementsShape{@Overridepublicvoiddraw(){System.out.println("Hello");}}里面的@Override是一个装饰器,它是怎么实现的呢?请教资深Java工程师。为什么叫装饰器呢?个人觉得可能看出来是在方法上面,像个头饰。不知道是不是因为这个,说错了不要打我。其实你可以理解为闭包方法,调用修改后的方法之前需要过一遍,有点像路障。是不是听起来很像中间件,其实并没有太多的逻辑。但是我们为什么不直接使用中间件来做缓存呢?中间件通常挂在某个路由组下,但是我们不可能对整个路由组做缓存。于是想到了用装饰器的思路来获取这个缓存。我可以在我需要的某种方法之前穿一个装饰器。首先实现一个传统的API。这里我们使用Gin框架构建:funcUserListHandler()gin.HandlerFunc{returnfunc(c*gin.Context){list:=db.GetUserListFromMySQL()res:=gin.H{"list":list,}c.JSON(200,res)}}funcUserDetailHandler()gin.HandlerFunc{returnfunc(c*gin.Context){user:=db.GetUserDetailListFromMySQL()res:=gin.H{"user":user,}c.JSON(200,res)}}funcmain(){r:=gin.Default()r.GET("/user/list/:type",UserListHandler())r.GET("/user/detail/:id",UserDetailHandler())r.Run()}在db部分,我们写了一个模拟方法来模拟从数据库读取数据:packagedbimport"fmt"typeUserstruct{idint64Namestring}funcGetUserListFromMySQL()*[]User{fmt.Println("模拟从数据库获取数据...")list:=make([]User,2)list[0]=User{id:1,Name:"张三",}list[1]=User{id:2,Name:"李四",}return&list}funcGetUserDetailListFromMySQL()*User{fmt.Println("模拟从数据库获取数据...")return&User{id:2,name:"Lisi",}}可以运行自那以后。我们预热Redis使用的库是:github.com/gomodule/redigo/redis如果不知道如何使用,请参考我们之前的redis教程文章!这里我粘贴关键代码:packagek_redisimport("github.com/gomodule/redigo/redis""time")varRedisDefaultPool*redis.PoolfuncnewPool(addrstring)*redis.Pool{return&redis.Pool{MaxIdle:3,IdleTimeout:240*time.Second,Dial:func()(redis.Conn,error){returnredis.Dial("tcp",addr,redis.DialPassword("password"))},}}funcinit(){}RedisDefaultPool=newPool("IP:port")}接下来我们可以使用Redis://readconn:=k_redis.RedisDefaultPool.Get()deferconn.Close()res,err:=redis.String(conn.Do("get",redisKey))fmt.Println(res)//写conn.Do("setex",redisKey,20,resData)写装饰器。我们如何添加装饰器?我们需要在路由方法中做一些事情,就是这里:r.GET("/user/list/:type",UserListHandler())我们只需要在UserListHandler方法之外再设置一个方法,这个方法就是一个装饰器!我们需要遇到的这个方法:传入的是gin.HandlerFunc方法,传出也是gin.HandlerFunc方法就可以了!但是为了通用性,我们需要增加三个入参:1.Redis中的key规则参数redisKeyPattern2,Redis中的key关键字参数param3,返回的data参数为空。代码如下:funcDecorator(hgin.HandlerFunc,paramstring,redisKeyPatternstring,emptyinterface{})gin.HandlerFunc{returnfunc(c*gin.Context){//获取Redis中的key关键字参数getId:=c.Param(param)//根据Redis里面的关键规则生成RedisKeyredisKey:=fmt.Sprintf(redisKeyPattern,getId)//从Redis读取数据conn:=k_redis.RedisDefaultPool.Get()deferconn.Close()res,err:=redis.String(conn.Do("get",redisKey))iferr!=nil{//没有缓存log.Println("Getfromthedatabase...",err)//执行下一部分h(c)dbRes,exists:=c.Get("Result")if!exists{dbRes=empty}//将缓存存入字节流storeresData,_:=json.Marshal(dbRes)conn.Do("setex",redisKey,20,resData)c.JSON(200,dbRes)}else{log.Println("Fromthecachelibrary...")json.Unmarshal(res,&empty)c.JSON(200,empty)}}}里面有很多错误本人忽略,读者可根据需要自行处理!这个装饰器的重点是c.Get("Result")的逻辑,我们之前的两个controller方法需要修改!funcUserListHandler()杜松子酒。HandlerFunc{returnfunc(c*gin.Context){list:=db.GetUserListFromMySQL()res:=gin.H{"list":list,}//c.JSON(200,res)c.Set("Result",res)}}funcUserDetailHandler()gin.HandlerFunc{returnfunc(c*gin.Context){user:=db.GetUserDetailListFromMySQL()res:=gin.H{"user":user,}//c.JSON(200,res)c.Set("Result",res)}}我们这里不能返回json数据,而是通过gin的context依次将值传递给装饰器.所以在装饰器中,可以通过c.Get("Result")获取值!