6. Gin集成redis

news2024/11/25 20:35:14

文章目录

  • 一:连接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 main

import (
	"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, // 使用默认DB
		PoolSize:     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过期时间 成功true
	redisClient.Expire(ctx, "testKey", time.Second*60)
	//  存在返回1
	redisClient.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自增1
	redisClient.Incr(ctx, "strKey")
	//  增加 66
	redisClient.IncrBy(ctx, "straey", 66)
	//  -1
	redisClient.Decr(ctx, "straey")
	//  -5
	redisClient.DecrBy(ctx, "straey", 5)
	//  过期时间
	redisClient.TTL(ctx, "strKey")
	
	//  str*ey      : *为任意字符串
	//  str[kKac]ey : 匹配[] 内的单个字符 strkey,strKey,straey,strcey
	//  str?ey      : ? 任意单个字符
	//  扫描key
	iter := 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 string
	redisClient.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"] = path
		param["clientIp"] = clientIp
		if !main(param) {
			c.Abort()
			response.JsonResponseError(c, "当前IP请求过于频繁,暂时被封禁~")
		}
	}
}
func main(param map[string]string) bool {
	// 预知的IP黑名单
	var blackList []string
	if util.InStringArray(param["clientIp"], blackList) {
		return false
	}
	// 预知的IP白名单
	var whiteList []string
	if util.InStringArray(param["clientIp"], whiteList) {
		return false
	}
	blackKey := prefix + ":" + IP_BLACK_LIST_KEY
	limitKey := prefix + ":" + IP_LIMIT_NUM_KEY
	curr := 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)
then
  local timeout = redis.call('hget', KEYS[1], ARGV[1])
  if(timeout ~= false)
  then
    if(tonumber(timeout) > tonumber(ARGV[2]))
    then
      return false
    end
    redis.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]))
then
  return false
end
local num = redis.call('hincrby', KEYS[2], ARGV[1], 1)
local ttl = redis.call('ttl', KEYS[2])
if(ttl == -1)
then
  redis.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.coloradmin.cn/o/1494267.html

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

相关文章

Cookie 探秘:了解 Web 浏览器中的小甜饼

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

java开发工程师面试题,996页阿里Java面试真题解析火爆全网

开头 消息队列 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,…

b站小土堆pytorch学习记录—— P18-P22 神经网络+小实战

文章目录 一、卷积层 P181.卷积操作2.代码 二、池化层 P191.池化层简单介绍2.代码(1)池化操作中数字的变化(2)池化操作对图片的影响 三、非线性激活 P201.简要介绍2.代码 四、线性层及其他层介绍 P211.线性层2.代码 五、搭建小实战…

C++初阶:初识C++

目录 1. 前言:C 与 C语言2. C对于C语言语法的完善与补充2.1 命名冲突与命名空间2.1.1 命名空间的定义2.1.2 调用方式 2.3 补充:流的概念2.4 缺省参数2.4.1 缺省参数的使用 2.5 函数重载2.5.1 什么是函数重载2.5.2 函数重载的使用2.5.3 特殊情况&#xff…

七、链表问题(上)

160、相交链表(简单) 题目描述 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个…

如何采集京东搜索页面商品的销量、价格数据?

这段Python代码旨在从京东网站上获取商品信息,包括评论数量和评论的关键词,以便进行进一步的分析。该程序分析并模拟了京东的JavaScript请求,以获取动态加载的评论数据。 代码都测试验证过都能正常跑通,实现效果,由于…

图像处理与视觉感知---期末复习重点(1)

文章目录 一、概述二、图像处理基础2.1 视觉感知要素2.2 像素间的一些基本关系2.2.1 相邻像素2.2.2 连通性2.2.3 距离度量 2.3 基本坐标变换2.4 空间变换与灰度值 一、概述 1. 图像的概念及分类。  图像是用各种观测系统以不同形式和手段观测客观世界而获得的、可以直接或间接…

C++——string类

前言:哈喽小伙伴们,从这篇文章开始我们将进行若干个C中的重要的类容器的学习。本篇文章将讲解第一个类容器——string。 目录 一.什么是string类 二.string类常见接口 1.string类对象的常见构造 2.string类对象的容量操作 3. string类对象的访问及遍…

【DevSecOps】2024 年需要警惕的 10 大 Web 应用程序安全威胁

【DevSecOps】2024 年需要警惕的 10 大 Web 应用程序安全威胁 由于 2023 年出现了许多创新,我们之前所了解的许多内容都发生了巨大变化;随着其中一些重大变化,威胁格局也发生了转变,一些旧威胁减少了,一些新威胁增加了。 技术每天都在不断变化,当我们谈论技术和相关威胁…

MetaQTL:元分析基础教程

MetaQTL 基础知识 在遥远的海洋中,每个岛屿都藏着无尽的宝藏,而探险家们争相寻找地图,以期揭开宝藏的秘密。 现实世界中,我们的基因组就像那片广阔的海洋,而隐藏在其中的宝藏就是控制我们身高、健康、甚至是我们性格的…

Netty之WebSocket协议开发

一、WebSocket产生背景 在传统的Web通信中,浏览器是基于请求--响应模式。这种方式的缺点是,浏览器必须始终主动发起请求才能获取更新的数据,而且每次请求都需要经过HTTP的握手和头部信息的传输,造成了较大的网络开销。如果客户端…

git 命令怎么回退到某个特定的 commit 并将其推送到远程仓库?

问题 不小心把提交的名称写错提交上远程仓库了,这里应该是 【029】的,这个时候我们想回到【028】这一个提交记录,然后再重新提交【029】到远程仓库,该怎么处理。 解决 1、首先我们找到【028】这条记录的提交 hash,右…

微信小程序开发系列(八)·微信小程序页面的划分以及轮播图区域的绘制和图片的添加

目录 1. 划分页面结构 2. 轮播图区域绘制 3. 轮播图图片添加 1. 划分页面结构 最终我们想达到如下效果&#xff1a; 其页面分为四层结构&#xff0c;因此我们需要配置四块view&#xff0c;代码如下&#xff1a; <!-- view 小程序提供的容器组件&#xff0c;可以当成…

《ChatGPT原理与架构:大模型的预训练、迁移和中间件编程 》

OpenAI 在 2022 年 11 月推出了人工智能聊天应用—ChatGPT。它具有广泛的应用场景&#xff0c;在多项专业和学术基准测试中表现出的智力水平&#xff0c;不仅接近甚至有时超越了人类的平均水平。这使得 ChatGPT 在推出之初就受到广大用户的欢迎&#xff0c;被科技界誉为人工智能…

zabbix监控中间件服务

zabbix监控Nginx 自定义nginx访问量的监控项&#xff0c;首先要通过脚本将各种状态的值取出来&#xff0c;然后通过zabbix监控。找到自定义脚本上传到指定目录/etc/zabbix/script/ 在zbx-client客户端主机操作 #创建目录&#xff0c;然后将脚本上传到该目录mkdir /etc/zabbix/…

7,图像镜像变换

水平镜像就是x图像宽度-原来的x&#xff0c; 垂直镜像就是y图像高度-原来的y void CDib::Mirror_Horizontal() { //指向原图像指针 LPBYTE lpSrc; LPBYTE p_data GetData(); //指向复制区域的指针 LPBYTE lpDst; //图像的宽和高 LONG width GetWidth(); LONG height GetHei…

备战蓝桥杯————二分查找(二)

引言 在上一篇博客中&#xff0c;我们深入探讨了二分搜索算法及其在寻找数组左侧边界的应用。二分搜索作为一种高效的查找方法&#xff0c;其核心思想在于通过不断缩小搜索范围来定位目标值。在本文中&#xff0c;我们将继续这一主题&#xff0c;不仅会回顾二分搜索的基本原理&…

【C++专栏】C++入门 | 命名空间、输入输出、缺省参数

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;C专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家 点赞&#x1f44d;收藏⭐评论✍ C入门 | 命名空间、输入输出、缺省参数 文章编号&#xff1a;C入门 / 0…

Java agent技术的注入利用与避坑点

什么是Java agent技术&#xff1f; Java代理&#xff08;Java agent&#xff09;是一种Java技术&#xff0c;它允许开发人员在运行时以某种方式修改或增强Java应用程序的行为。Java代理通过在Java虚拟机&#xff08;JVM&#xff09;启动时以"代理"&#xff08;agent…

react native中如何使用webView调用腾讯地图选点组件

react native中如何使用webView调用腾讯地图选点组件 效果示例图代码示例备注 效果示例图 代码示例 import React, {useEffect, useRef, useState} from react; import {Modal, StyleSheet} from react-native; import {pxToPd} from ../../common/js/device; import {WebView…