6. Gin集成redis

文章目录

  • 一:连接Redis
  • 二:基本使用
  • 三:字符串
  • 四:列表
  • 五:哈希
  • 六:Set
  • 七:管道
  • 八、事务
  • 九:示例

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/14-go-redis

作为后端研发,Redis是无处不在的,那么go操作Redis也是每位后端研发应该掌握的基本技能。

go-redis官方文档 https://redis.uptrace.dev/guide/

一:连接Redis

首先在本地启动Redis服务端,监听6379端口
在这里插入图片描述

当然,也可以使用docker启动rediswindowsdocker的相关操作可参考:56.windows docker 安装ES、Go操作ES(github.com/olivere/elastic/v7库)

在这里插入图片描述
注意: 此处的版本、容器名和端口号可以根据自己需要设置。

启动一个 redis-cli 连接上面的 redis server

docker run -it --network host --rm redis:5.0.7 redis-cli

执行go get github.com/go-redis/redis/v8导入依赖包,编写代码

package mainimport ("context""fmt""github.com/go-redis/redis/v8"
)var redisClient *redis.Client
var ctx = context.Background()func init() {config := &redis.Options{Addr:         "localhost:6379",Password:     "",DB:           0, // 使用默认DBPoolSize:     15,MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。//超时//DialTimeout:  5 * time.Second, //连接建立超时时间,默认5秒。//ReadTimeout:  3 * time.Second, //读超时,默认3秒, -1表示取消读超时//WriteTimeout: 3 * time.Second, //写超时,默认等于读超时//PoolTimeout:  4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。}redisClient = redis.NewClient(config)
}func main() {redisClient.Set(ctx, "name", "zhangsan", 0)val, err := redisClient.Get(ctx, "name").Result()if err != nil {fmt.Println("读取错误", err)}fmt.Println(fmt.Sprintf("key:name,val:%s", val))
}

执行上述代码,可见终端输出
在这里插入图片描述
当然,也可以打开Redis客户端工具,查到对应的key
在这里插入图片描述

二:基本使用

包括设置值、取值、设置过期时间、判断key是否存在、key不存在时才设置值、删除等操作

func Test_Base(t *testing.T) {//  添加key//0表示没有过期时间redisClient.Set(ctx, "testKey", "xxx", 0)//  获取值val, err := redisClient.Get(ctx, "testKey").Result()if err != nil {fmt.Println("错误", err)}fmt.Println("值:", val)//  设置key过期时间 成功trueredisClient.Expire(ctx, "testKey", time.Second*60)//  存在返回1redisClient.Exists(ctx, "testKey")//  key不存在时设置值redisClient.SetNX(ctx, "unkey", "val", 0)redisClient.Set(ctx, "testKey2", "xxx", 0)//  删除key 可删除多个redisClient.Del(ctx, "testKey2", "testKey")
}

三:字符串

包括设置、读取、加、减、获取过期时间、模糊查询key,遍历模糊查询结果等

func Test_String(t *testing.T) {//  设置值redisClient.Set(ctx, "strKey", 100, 0)redisClient.Set(ctx, "straey", 100, 0)//  key自增1redisClient.Incr(ctx, "strKey")//  增加 66redisClient.IncrBy(ctx, "straey", 66)//  -1redisClient.Decr(ctx, "straey")//  -5redisClient.DecrBy(ctx, "straey", 5)//  过期时间redisClient.TTL(ctx, "strKey")//  str*ey      : *为任意字符串//  str[kKac]ey : 匹配[] 内的单个字符 strkey,strKey,straey,strcey//  str?ey      : ? 任意单个字符//  扫描keyiter := redisClient.Scan(ctx, 0, "str?ey", 0).Iterator()for iter.Next(ctx) {fmt.Println("keys", iter.Val(), ": val", redisClient.Get(ctx, iter.Val()).Val())}if err := iter.Err(); err != nil {panic(any(err))}
}

在这里插入图片描述

四:列表

func Test_List(t *testing.T) {//  添加redisClient.LPush(ctx, "listKey1", 111, 222, 333, 444)redisClient.RPush(ctx, "listKey1", 5555)//  不存在不添加redisClient.LPushX(ctx, "unlistKey", 111)var intf []int//  根据索引获取 绑定到数组redisClient.LRange(ctx, "listKey1", 0, 10).ScanSlice(&intf)fmt.Println(intf)var i int//  弹出redisClient.LPop(ctx, "listKey1").Scan(&i)fmt.Println(i)//....
}

在这里插入图片描述

五:哈希

func Test_Hash(t *testing.T) {redisClient.HMSet(ctx, "hkey1", "name", "shushan", "age", 99, "b", true)all := redisClient.HGetAll(ctx, "hkey1")fmt.Printf(" %v \n ", all)
}

在这里插入图片描述

六:Set

func Test_Set(t *testing.T) {//  添加redisClient.SAdd(ctx, "setKey1", "m1", "onlyk1")redisClient.SAdd(ctx, "setKey2", "m2", "xca")sl, _ := redisClient.SDiff(ctx, "setKey1", "setKey2").Result()fmt.Println(sl)// onlyk1,m1//随机移除var val stringredisClient.SPop(ctx, "setKey1").Scan(&val)fmt.Println(val)// .....
}

在这里插入图片描述

七:管道

管道即一次打包多个命令,一次性发给服务端执行,能够节省命令传输时间。比如10个命令,不使用管道时,得发送10次,并接收10次响应。使用管道时,则是把10个命令打包一次性发送,并一次性接收10个响应。

  • 使用redis客户端的Pipeline方法获得管道
  • 之后使用获得的管道pipe去编写命令
  • 最后使用管道的Exec方法提交打包后的多个命令
func Test_Pipe(t *testing.T) {pipe := redisClient.Pipeline()incr := pipe.Set(ctx, "pip_test", "bt", 0)pipe.Expire(ctx, "pip_test", time.Hour)//  提交cmds, err := pipe.Exec(ctx)if err != nil {fmt.Println(err)}for _, cmd := range cmds {fmt.Println(cmd.String())}// 该值得Exec提交后有效fmt.Println(incr.Val())
}

在这里插入图片描述

八、事务

MULTI/EXEC
Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是,MULTI/EXEC能够确保在MULTI/EXEC两个语句的命令之间没有其他客户端正在执行命令。

在这种场景我们需要使用TxPipelineTxPipeline总体上类似于上面的Pipeline,但是它内部会使用MULTI/EXEC包裹排队的命令。例如:

pipe := rdb.TxPipeline()incr := pipe.Incr("tx_pipeline_counter")
pipe.Expire("tx_pipeline_counter", time.Hour)_, err := pipe.Exec()
fmt.Println(incr.Val(), err)

上面代码相当于在一个RTT(往返时间)下执行了下面的redis命令:

MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

还有一个与上文类似的·TxPipelined·方法,使用方法如下:

var incr *redis.IntCmd
_, err := rdb.TxPipelined(func(pipe redis.Pipeliner) error {incr = pipe.Incr("tx_pipelined_counter")pipe.Expire("tx_pipelined_counter", time.Hour)return nil
})
fmt.Println(incr.Val(), err)

Watch
在某些场景下,我们除了要使用MULTI/EXEC命令外,还需要配合使用WATCH命令。在用户使用WATCH命令监视某个键之后,直到该用户执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的键进行了替换、更新、删除等操作,那么当用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。

Watch(fn func(*Tx) error, keys ...string) error

Watch方法接收一个函数和一个或多个key作为参数。基本使用示例如下:

// 监视watch_count的值,并在值不变的前提下将其值+1
key := "watch_count"
err = client.Watch(func(tx *redis.Tx) error {n, err := tx.Get(key).Int()if err != nil && err != redis.Nil {return err}_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {pipe.Set(key, n+1, 0)return nil})return err
}, key) // 在执行事务时,如果这个key发生了变化(如被其他客户端修改了),则上面Watch方法中的事务会执行失败

九:示例

go-redis实现接口IP限流,IP黑名单,IP白名单的示例

package Middlewares
import ("github.com/gin-gonic/gin""strconv""time""voteapi/pkg/app/response""voteapi/pkg/gredis""voteapi/pkg/util"
)
const IP_LIMIT_NUM_KEY = "ipLimit:ipLimitNum"
const IP_BLACK_LIST_KEY = "ipLimit:ipBlackList"
var prefix = "{gateway}"
var delaySeconds int64 = 60  // 观察时间跨度,秒
var maxAttempts int64 = 10000 // 限制请求数
var blackSeconds int64 = 0  // 封禁时长,秒,0-不封禁
func GateWayPlus() gin.HandlerFunc {return func(c *gin.Context) {path := c.FullPath()clientIp := c.ClientIP()// redis配置集群时必须param := make(map[string]string)param["path"] = pathparam["clientIp"] = clientIpif !main(param) {c.Abort()response.JsonResponseError(c, "当前IP请求过于频繁,暂时被封禁~")}}
}
func main(param map[string]string) bool {// 预知的IP黑名单var blackList []stringif util.InStringArray(param["clientIp"], blackList) {return false}// 预知的IP白名单var whiteList []stringif util.InStringArray(param["clientIp"], whiteList) {return false}blackKey := prefix + ":" + IP_BLACK_LIST_KEYlimitKey := prefix + ":" + IP_LIMIT_NUM_KEYcurr := time.Now().Unix()item := util.Md5(param["path"] + "|" + param["clientIp"])return normal(blackKey, limitKey, item, curr)
}
// 普通模式
func normal(blackKey string, limitKey string, item string, time int64) (res bool) {if blackSeconds > 0 {timeout, _ := gredis.RawCommand("HGET", blackKey, item)if timeout != nil {to, _ := strconv.Atoi(string(timeout.([]uint8)))if int64(to) > time {// 未解封return false}// 已解封,移除黑名单gredis.RawCommand("HDEL", blackKey, item)}}l, _ := gredis.RawCommand("HGET", limitKey, item)if l != nil {last, _ := strconv.Atoi(string(l.([]uint8)))if int64(last) >= maxAttempts {return false}}num, _ := gredis.RawCommand("HINCRBY", limitKey, item, 1)if ttl, _ := gredis.TTLKey(limitKey); ttl == int64(-1) {gredis.Expire(limitKey, int64(delaySeconds))}if num.(int64) >= maxAttempts && blackSeconds > 0 {// 加入黑名单gredis.RawCommand("HSET", blackKey, item, time+blackSeconds)// 删除记录gredis.RawCommand("HDEL", limitKey, item)}return true
}
// LUA脚本模式
// 支持redis集群部署
func luaScript(blackKey string, limitKey string, item string, time int64) (res bool) {script := `
local blackSeconds = tonumber(ARGV[5])
if(blackSeconds > 0)
thenlocal timeout = redis.call('hget', KEYS[1], ARGV[1])if(timeout ~= false)thenif(tonumber(timeout) > tonumber(ARGV[2]))thenreturn falseendredis.call('hdel', KEYS[1], ARGV[1])end
end
local last = redis.call('hget', KEYS[2], ARGV[1])
if(last ~= false and tonumber(last) >= tonumber(ARGV[3]))
thenreturn false
end
local num = redis.call('hincrby', KEYS[2], ARGV[1], 1)
local ttl = redis.call('ttl', KEYS[2])
if(ttl == -1)
thenredis.call('expire', KEYS[2], ARGV[4])
end
if(tonumber(num) >= tonumber(ARGV[3]) and blackSeconds > 0)
then redis.call('hset', KEYS[1], ARGV[1], ARGV[2] + ARGV[5])redis.call('hdel', KEYS[2], ARGV[1])
end
return true
`result, err := gredis.RawCommand("EVAL", script, 2, blackKey, limitKey, item, time, maxAttempts, delaySeconds, blackSeconds)if err != nil {return false}if result == int64(1) {return true} else {return false}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/734036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux多线程之线程控制

(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 目录 一、pthread_crate 二、pthread_join 三、p…

腾讯面经学习笔记

💖 前言 👩‍🏫 参考地址 💖 操作系统 1. 进程和线程的区别 本质区别 进程是操作系统资源分配的基本单位线程是任务调度和执行的基本单位 开销方面 每个进程都有独立的代码和数据空间(程序上下文)&#…

使用大型语言模型进行实体提取

原文地址:Using A Large Language Model For Entity Extraction LLM 能否比传统 NLP 方法更好地提取实体? 2022 年 7 月 12 日 Large Language Models for Generative Information Extraction: A Survey 实体简介 使用Co:here大型语言模型。 实体可以被视…

pytorch_retinaface训练Resnet50_Final.pth过程+无图版安装Nvidia+CUDA驱动GPU

背景 当前处于人脸检测分支,项目就是retinaface官方的代码加上数据集目录结构,目的是训练出最后的模型文件Resnet50_Final.pth 代码 https://gitee.com/congminglst/pytorch_-retinaface.git 项目结构与设计 图片数据集采用widerface, 前…

如何通过隐藏服务器真实IP来防御DDOS攻击

我们知道,服务器对外提供服务,基本上都是放置在公网上的。所以说服务器放置在公网上会面临很多攻击,如果不做好必要的防护措施,服务器被人攻击只是时间上的问题。 而我们面临的众多攻击中,DDoS攻击是最常见同时也是影响…

【Flink】Flink 的八种分区策略(源码解读)

Flink 的八种分区策略(源码解读) 1.继承关系图1.1 接口:ChannelSelector1.2 抽象类:StreamPartitioner1.3 继承关系图 2.分区策略2.1 GlobalPartitioner2.2 ShufflePartitioner2.3 BroadcastPartitioner2.4 RebalancePartitioner2…

树莓派4B Ubuntu20.04 Python3.9安装ROS踩坑记录

问题描述 在使用sudo apt-get update命令更新时发现无法引入apt-pkg,使用python3 -c "import apt_pkg"发现无法引入,应该是因为:20.04的系统默认python是3.8,但是我换成了3.9所以没有编译文件,于是使用sudo update-alte…

three.js如何实现简易3D机房?(四)点击事件+呼吸灯效果

接上一篇: three.js如何实现简易3D机房?(三)显示信息弹框/标签:http://t.csdnimg.cn/5W2wA 目录 八、点击事件 1.实现效果 2.获取相交点 3.呼吸灯效果 4.添加点击事件 5.问题解决 八、点击事件 1.实现效果 2.…

蓝桥杯大赛软件python赛道真题:蛇形填数

真题链接:https://www.lanqiao.cn/problems/594/learning/ 题目描述: 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 如下图所示,小明用从1开始的正整数“蛇形”填充无限大的矩阵。 1 2 6 …

【打工日常】使用docker部署个人实时在线文档协助编辑器

一、Etherpad介绍 Etherpad是一个高度可定制的开源在线编辑器,提供真正实时的协作编辑。放在自己的服务器里面,可以更大程度的保护自己工作的隐私,并且Etherpad允许您实时协作编辑文档,就像在浏览器中运行的实时多人编辑器一样这样…

SpringBoot轻松搞定接口防抖(防重复提交)

大家好,我是月夜枫,作为一名合格的码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用&…

基于Java的高校实验室管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实验管理模块2.4 实验设备模块2.5 实验订单模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示五、样例代码5.1 查询实验室设备5.2 实验放号5.3 实验预定 六、免责说明 一、摘…

【STM32】HAL库 CubeMX 教程 --- 通用定时器 TIM2 定时

实验目标: 通过CUbeMXHAL,配置TIM2,1s中断一次,闪烁LED。 一、常用型号的TIM时钟频率 1. STM32F103系列: 所有 TIM 的时钟频率都是72MHz;F103C8不带基本定时器,F103RC及以上才带基本定时器。…

[BUG] docker运行Java程序时配置代理-Dhttp.proxyHost后启动报错

[BUG] docker运行Java程序时配置代理-Dhttp.proxyHost后启动报错 bug现象描述 版本:2.0.4(客户端和服务端都是) 环境:私有云环境,只有少量跳板机器可以访问公网,其他机器均通过配置代理方式访问公网 bug现…

9、Linux-安装JDK、Tomcat和MySql

目录 一、安装JDK 1、传输JDK文件(.tar.gz) 2、解压 3、备份环境变量 4、配置环境变量 5、重新加载环境变量 6、验证(java -version) 二、安装Tomcat 1、传输文件,解压到/usr/local 2、进入Tomcat的bin目录 …

[PyQt5]PyQt5连接MYSQL时显示Driver not loaded解决方案

在第一次用PyQt5的 QSqlDatabase.addDatabase 连接mysql的时候,可能会出现Driver not loaded的情况,如下: from PyQt5.QtSql import QSqlQuery, QSqlDatabase from PyQt5.QtWidgets import QApplication import sysapp QApplication(sys.ar…

GO语言接入支付宝

GO语言接入支付宝 今天就go语言接入支付宝写一个教程 使用如下库,各种接口较为齐全 "github.com/smartwalle/alipay/v3"先简单介绍下加密: 试想,当用户向支付宝付款时,若不进行任何加密,那么黑客就可以任…

【杂记】IDEA和Eclipse如何查看GC日志

1.Eclipse查看GC日志 1.1 右击代码编辑区 -> Run As -> Run Configurations 1.2 点击Arguments栏 -> VM arguments:区域填写XX参数 -> Run 1.3 控制台输出GC详细日志 2.IDEA查看GC日志 2.1 鼠标右击代码编辑器空白区域,选择Edit 项目名.main()... 2.…

模型驱动软件开发

MDE 模型驱动工程(MDE, Model-Driven Engineering)是软件工程的一个分支,它将模型与建模拓展到软件开发的所有方面,形成一个多维建模空间,从而将工程活动建立在这些模型的映射和转换之上。[1] MDE的基本原则是将模型视…

实验二(一):IPV4编址及IPV4路由基础实验

一实验介绍 1.关于本实验 IPv4( Internet Protocol Version 4)是 TCP/IP 协议族中最为核心的协议之一。 它工作在 TCP/IP参考模型的网际互联层,该层与 OSI参考模型的网络层相对应。 网络层提供了无连接数据传输服务,即网络在发送分组时不需要先建立连…