当前位置: 首页 > news >正文

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}
}

相关文章:

6. Gin集成redis

文章目录 一:连接Redis二:基本使用三:字符串四:列表五:哈希六:Set七:管道八、事务九:示例 代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/14-go-redi…...

DxO PureRAW:赋予RAW图像生命,打造非凡视觉体验 mac/win版

DxO PureRAW 是一款专为RAW图像处理而设计的软件,旨在帮助摄影师充分利用RAW格式的优势,实现更加纯净、细腻的图像效果。该软件凭借其强大的功能和易于使用的界面,成为了RAW图像处理领域的佼佼者。 DxO PureRAW 软件获取 首先,Dx…...

【MySQL | 第四篇】区分SQL语句的书写和执行顺序

文章目录 4.区分SQL语句的书写和执行顺序4.1书写顺序4.2执行顺序4.3总结4.4扩充&#xff1a;辨别having与where的异同&#xff1f;4.5聚合查询 4.区分SQL语句的书写和执行顺序 注意&#xff1a;SQL 语句的书写顺序与执行顺序不是一致的 4.1书写顺序 SELECT <字段名> …...

服务器又被挖矿记录

写在前面 23年11月的时候我写过一篇记录服务器被挖矿的情况&#xff0c;点我查看。当时是在桌面看到了bash进程CPU占用异常发现了服务器被挖矿。 而过了几个月没想到又被攻击&#xff0c;这次比上次攻击手段要更高明点&#xff0c;在这记录下吧。 发现过程 服务器用的是4090…...

嵌入式学习day34 网络

TCP包头: 1.序号:发送端发送数据包的编号 2.确认号:已经确认接收到的数据的编号(只有当ACK为1时,确认号才有用) TCP为什么安全可靠: 1.在通信前建立三次握手连接 SYN SYNACK ACK 2.在通信过程中通过序列号和确认号保障数据传输的完整性 本次发送序列号:上次…...

欧科云链:角力Web3.0,香港如何为合规设线?

在香港拥抱Web3.0的过程中,以欧科云链为代表的合规科技企业将凸显更大重要性。 ——据香港商报网报道 据香港明报、商报等媒体报道&#xff0c;港区全国政协兼香港选委界立法会议员吴杰庄在日前召开的全国两会上提出在大湾区建设国际中小企业创新Web3融资平台等提案&#xff0…...

Android SDK2 (实操三个小目标)

书接上回&#xff1a;Android SDK 1&#xff08;概览&#xff09;-CSDN博客 今天讲讲三个实际练手内容&#xff0c;用的是瑞星微的sdk。 1 实操编译Android.bp 首先还是感叹下&#xff0c;现在的系统真的越搞越复杂&#xff0c;最早只有gcc&#xff0c;后面多了make&#xf…...

数字编码与字符编码:解锁编程世界的基石

在计算机的世界里&#xff0c;一切信息都是以数字的形式存在。但是&#xff0c;你有没有想过&#xff0c;我们是如何在这个由0和1构成的数字世界中表示复杂的信息&#xff0c;如文本、图像和声音的呢&#xff1f;本篇文章将带你深入探索数字编码与字符编码的奥秘&#xff0c;它…...

C语言-写一个简单的Web服务器(一)

基于TCP的web服务器 概述 C语言可以干大事&#xff0c;我们基于C语言可以完成一个简易的Web服务器。当你能够自行完成web服务器&#xff0c;你会对C语言有更深入的理解。对于网络编程&#xff0c;字符串的使用&#xff0c;文件使用等等都会有很大的提高。 关于网络的TCP协议在…...

MySQL底层原理

1. 请解释MySQL的逻辑架构和物理架构。 MySQL的逻辑架构和物理架构涉及到多个层面&#xff0c;包括网络连接、服务处理、存储引擎以及数据存储等部分。具体如下&#xff1a; 逻辑架构&#xff1a; 连接层&#xff08;Connection Layer&#xff09;&#xff1a;客户端通过TCP…...

复盘-word

word-大学生网络创业交流会 设置段落&#xff0c;段后行距才有分 word-选中左边几行字进行操作 按住alt键进行选中 word复制excel随excel改变&#xff08;选择性粘贴&#xff09; 页边距为普通页边距定义 ##### word 在内容控件里面填文字&#xff08;调属性&#xff09…...

Vue中的组件:构建现代Web应用的基石

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

【从部署服务器到安装autodock vina】

注意&#xff1a;服务器 linux系统选用ubuntu 登录系统&#xff0c;如果没有图形化见面可以先安装图形化界面 可以参考该视频 --> linux安装图形化界面 非阿里云ubuntu 依次执行以下命令 sudo apt-get update sudo apt-get install gnome sudo reboot阿里云ubuntu 需多执…...

如何使用ArcGIS Pro进行坡度分析

坡度分析是地理信息系统中一种常见的空间分析方法&#xff0c;用于计算地表或地形的坡度&#xff0c;这里为大家介绍一下如何使用ArcGIS Pro进行坡度分析&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的DEM数据&#xff0c;除了DEM数据&…...

Vue3.2 + vue/cli-service 打包 chunk-vendors.js 文件过大导致页面加载缓慢解决方案

chunk-vendors.js 是/node_modules 目录下的所有模块打包成的包&#xff0c; 但是这包太大导致页面加载很慢&#xff08;我的都要3-4秒了&#xff09;&#xff0c; 这个时候就会出现白屏的情况 解决方案 1、compression-webpack-plugin 插件解决方案 1&#xff09;、安装 npm …...

Java学习笔记NO.18

T1.理工超市 &#xff08;1&#xff09;题目描述 编写一个程序&#xff0c;设计理工超市功能菜单并完成注册和登录功能的实现。显示完菜单后&#xff0c;提示用户输入菜单项序号。当用户输入<注册>和<登录>菜单序号时模拟完成注册和登录功能&#xff0c;最后提示…...

【JVM】聊聊垃圾回收之三色标记算法

在垃圾收集器 CMS中存在四个阶段&#xff0c;初始标记、并发标记、重新标记、并发清理。 那么在并发标记中由于没有STW&#xff0c;业务程序和GC程序是并发执行的&#xff0c;那么是如何实现对象的并发标记的。 并发垃圾回收 并发标记其实是一个宏观的过程&#xff0c;仍然需…...

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Image)

Image为图片组件&#xff0c;常用于在应用中显示图片。Image支持加载PixelMap、ResourceStr和DrawableDescriptor类型的数据源&#xff0c;支持png、jpg、jpeg、bmp、svg、webp和gif类型的图片格式。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&am…...

华为OD面试分享8(2024年)

个人情况&#xff1a; 23毕业&#xff0c;24一战跨考ustc&#xff0c;觉G。 211本&#xff0c;目标院校。 知识储备&#xff1a; 仅限408 用leetcode刷过408排序算法 0项目经验 时间线&#xff1a; 23.12.27 投的简历。当时啥也不会啥也不知道&#xff0c;听入职的同学说…...

Java的堆如何分代的?

前言&#xff1a; java对象一般都是朝生夕死&#xff0c;也有部分对象是一直存在的。两种对象如何都放在一起&#xff0c;进行垃圾回收的时候效率较低。所以通过将不同时期的对象放在不同的内存池中&#xff0c;节省垃圾回收的时间&#xff0c;提高性能。 组成&#xff1a; 新生…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...