这篇是关于Redis的笔记。这里用的是 go-redis ,要先下载
go get github.com/go-redis/redis
使用 redis.NewClient()
创建连接,这个函数参数是一个叫 Options
的结构体,这个结构体的属性字段就是设置连接redis的相关参数;创建连接返回一个 Client
。
下面是 Options结构体 的属性及其作用:
Network string // 网络类型 tcp 或者 unix,默认是 tcp
Addr string // redis地址,格式 host:port
OnConnect func(*Conn) error // 新建一个redis连接的时候,会回调这个函数
Password string // redis密码,redis server没有设置可以为空。
DB int // redis数据库,序号从0开始,默认是0,可以不用设置
MaxRetries int // redis操作失败最大重试次数,默认不重试。
MinRetryBackoff time.Duration // 最小重试时间间隔,默认是 8ms,-1 表示关闭
MaxRetryBackoff time.Duration // 最大重试时间间隔,默认是 512ms,-1 表示关闭
DialTimeout time.Duration // redis连接超时时间,默认是 5 秒
ReadTimeout time.Duration // socket读取超时时间,默认 3 秒
WriteTimeout time.Duration // socket写超时时间
PoolSize int // redis连接池的最大连接数,默认连接池大小等于 cpu个数 * 10
MinIdleConns int // redis连接池最小空闲连接数
MaxConnAge time.Duration // redis连接最大的存活时间,默认不会关闭过时的连接
PoolTimeout time.Duration // 当你从redis连接池获取一个连接之后,连接池最多等待这个拿出去的连接多长时间。默认是等待 ReadTimeout + 1 秒
IdleTimeout time.Duration // redis连接池多久会关闭一个空闲连接,默认是 5 分钟,-1 则表示关闭这个配置项
IdleCheckFrequency time.Duration // 多长时间检测一下,空闲连接,默认是 1 分钟,-1 表示关闭空闲连接检测
readOnly bool // 只读设置,如果设置为true,redis只能查询缓存不能更新。
知道了连接redis的相关参数,我们创建一个连接:
// 连接到redis
conn := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "key#1234",
//DialTimeout: 10, // 连接超时(s),默认是5秒
})
fmt.Println("连接成功", conn)
这里我要说一下, DialTimeout: 10 这一句!!!自己手动设置连接超时的时间,这个设置太坑了!!!
我改成10秒、20秒,连接会出问题,经常出现连接超时的问题,还有就是我想设置某个key的过期时间都死活设置不了!!!
conn.Set("key", "value", time.Minute*60) 这种方式死活设置不了,只能设置成0,也就是永不过期,我还在想go-redis没办法设置过期吗???
conn.Expire("key", time.Minute*60) 用这种方式也一样,总是报错,说连接超时,我真的麻了,一直在想怎么会超时呢?怎么就会超时呢!!!
我翻源码、问百度也找不到原因,还有就是按照我的理解 DialTimeout 是可以设置成其他秒数的,我在Java中,连redis就是设置的10秒,所以我一直不觉得这里设置成10秒有问题。
这就导致我半天找不到是什么原因!!!后面我尝试把这句注释掉了,发现居然可以了!!!知道我当时有多无语吗!!!我真的会谢!!!
我都不知道为什么不能设置成其他秒数。
我也不知道为什么会有这个问题。
func 基本使用(conn *redis.Client) {
// 设置过期时间,要注意,它的单位不是秒,是毫秒
// 1、Set:向redis的一个key设置值
err := conn.Set("go-demo:name", "符华", time.Minute*60).Err()
if err != nil { // 检测设置是否成功
panic(err)
}
// 2、Get:读取一个key的值
s, err := conn.Get("go-demo:name").Result()
if err != nil { // 检测设置是否成功
panic(err)
}
fmt.Println("go-demo:name =", s)
// 3、GetSet:向redis的一个key设置值(新值),并返回这个key的旧值
oldVal, err := conn.GetSet("go-demo:name", "赤鸢").Result()
if err != nil {
panic(err)
}
fmt.Println("go-demo:name的旧值 =", oldVal) // 打印key的旧值
// 4、SetNX:如果一个key不存在,则设置这个key的值
err := conn.SetNX("go-demo:password", "123456", 0).Err()
if err != nil {
panic(err)
}
// 5、MSet:批量设置key的值
err := conn.MSet("go-demo:phone", "13254784511", "go-demo:age", "20", "go-demo:address", "广东省惠州市").Err()
if err != nil {
panic(err)
}
// 6、MGet:批量查询key的值:可以传入任意个key,一次性返回多个值(如果key不存在,则返回nil)
vals, err := conn.MGet("go-demo:name", "go-demo:password", "go-demo:phone").Result()
if err != nil {
panic(err)
}
fmt.Println(vals)
// 7、Incr、IncrBy、IncrByFloat(和IncrBy的区别是累加的是浮点数):对一个key的数值进行递增操作
val, err := conn.Incr("go-demo:age").Result() // Incr函数每次加一
if err != nil {
panic(err)
}
fmt.Println("go-demo:age 最新值 =", val)
val, err := conn.IncrBy("go-demo:age", 2).Result() // IncrBy、IncrByFloat函数,可以指定每次递增多少
if err != nil {
panic(err)
}
fmt.Println("go-demo:age 最新值 =", val)
// 8、Decr、DecrBy:对一个key的数值进行递减操作
val, err := conn.Decr("go-demo:age").Result() // Decr函数每次减一
if err != nil {
panic(err)
}
fmt.Println("go-demo:age 最新值 =", val)
val, err := conn.DecrBy("go-demo:age", 2).Result() // DecrBy函数,可以指定每次递减多少
if err != nil {
panic(err)
}
fmt.Println("go-demo:age 最新值 =", val)
// 9、Del:删除key,支持批量删除
conn.Del("go-demo:age") // 删除key
err := conn.Del("go-demo:age", "go-demo:address").Err() // 删除多个key, Del函数支持删除多个key
if err != nil {
panic(err)
}
// 10、Expire:设置key的过期时间,单位秒
_, err := conn.Expire("go-demo:address", time.Minute*6).Result()
if err != nil {
panic(err)
}
}
func hash使用(conn *redis.Client) {
// 初始化hash数据的多个字段值
var myMap = make(map[string]interface{})
myMap["id"] = 1
myMap["name"] = "符华"
myMap["age"] = 20
myMap["token"] = "Tjkl=sdjkuioOTHJg5412=erweYTJKkl65sdfl"
// 1、HMSet:根据key和多个字段名和字段值,批量设置hash字段值
err := conn.HMSet("go-demo:login:1", myMap).Err() // 一次性保存多个hash字段值
if err != nil {
panic(err)
}
// 2、HMGet:根据key和多个字段名,批量查询多个hash字段值
vals, err := conn.HMGet("go-demo:login:1", "name", "token").Result() // HMGet支持多个field字段名,意思是一次返回多个字段值
if err != nil {
panic(err)
}
fmt.Println(vals)
// 3、HSet:根据key和field字段设置,field字段的值
err := conn.HSet("go-demo:login:1", "name", "赤鸢").Err()
if err != nil {
panic(err)
}
// 4、HGet:根据key和field字段,查询field字段的值
name, err := conn.HGet("go-demo:login:1", "name").Result()
if err != nil {
panic(err)
}
fmt.Println(name)
// 5、HGetAll:根据key查询所有字段和值
data, err := conn.HGetAll("go-demo:login:1").Result()
if err != nil {
panic(err)
}
for field, val := range data { // data是一个map类型,这里使用使用循环迭代输出
fmt.Println(field, val)
}
// 6、HIncrBy:根据key和field字段,递增字段的数值,可以指定递增次数,每次递增+1
count, err := conn.HIncrBy("go-demo:login:1", "age", 2).Result()
if err != nil {
panic(err)
}
fmt.Println(count)
// 7、HKeys:根据key返回所有字段名
keys, err := conn.HKeys("go-demo:login:1").Result()
if err != nil {
panic(err)
}
fmt.Println(keys)
// 8、HLen:根据key,查询hash的字段数量
size, err := conn.HLen("go-demo:login:1").Result()
if err != nil {
panic(err)
}
fmt.Println(size)
// 9、HSetNX:如果field字段不存在,则设置hash字段值
err := conn.HSetNX("go-demo:login:1", "phone", "13452117452").Err()
if err != nil {
panic(err)
}
// 10、HDel:根据key和字段名,删除hash字段,支持批量删除hash字段
conn.HDel("go-demo:login:1", "phone") // 删除一个字段id
conn.HDel("go-demo:login:1", "age", "phone") // 删除多个字段
// 11、HExists:检测hash字段名是否存在。
err := conn.HExists("go-demo:login:1", "id").Err()
if err != nil {
panic(err)
}
}
func list使用(conn *redis.Client) {
// 1、LPush:从列表左边插入数据
conn.LPush("go-demo:userList", "符华") // 插入一个数据
err := conn.LPush("go-demo:userList", "赤鸢", "云墨", "炽翎").Err() // LPush支持一次插入任意个数据
if err != nil {
panic(err)
}
// 2、RPush:从列表右边插入数据
conn.RPush("go-demo:userList", "白夜") // 插入一个数据
err := conn.RPush("go-demo:userList", "月轮", "迅羽").Err() // 支持一次插入任意个数据
if err != nil {
panic(err)
}
// 3、LPushX:跟LPush的区别是,仅当列表存在的时候才插入数据,用法完全一样。
// 4、RPushX:跟RPush的区别是,仅当列表存在的时候才插入数据, 他们用法一样
// 5、LPop:从列表左边删除第一个数据,并返回删除的数据
val, err := conn.LPop("go-demo:userList").Result()
if err != nil {
panic(err)
}
fmt.Println(val)
// 6、RPop:从列表的右边删除第一个数据,并返回删除的数据
val, err := conn.RPop("go-demo:userList").Result()
if err != nil {
panic(err)
}
fmt.Println(val)
// 7、LLen:返回列表的大小
val, err := conn.LLen("go-demo:userList").Result()
if err != nil {
panic(err)
}
fmt.Println(val)
// 8、LRange:返回列表的一个范围内的数据,也可以返回全部数据(可用于分页)
vals, err := conn.LRange("go-demo:userList",0,-1).Result() // 返回从0开始到-1位置之间的数据,意思就是返回全部数据
if err != nil {
panic(err)
}
fmt.Println(vals)
// 9、LRem:删除列表中的数据
dels, err := conn.LRem("go-demo:userList",1,100).Result() // 从列表左边开始,删除100, 如果出现重复元素,仅删除1次,也就是删除第一个
if err != nil {
panic(err)
}
fmt.Println(dels)
conn.LRem("go-demo:userList",2,100) // 如果存在多个100,则从列表左边开始删除2个100
conn.LRem("go-demo:userList",-2,100) // 如果存在多个100,则从列表右边开始删除2个100(第二个参数负数表示从右边开始删除几个等于100的元素)
conn.LRem("go-demo:userList",0,100) // 如果存在多个100,第二个参数为0,表示删除所有元素等于100的数据
// 10、LIndex:根据索引坐标,查询列表中的数据
val, err := conn.LIndex("go-demo:userList",5).Result() // 列表索引从0开始计算,这里返回第6个元素
if err != nil {
panic(err)
}
fmt.Println(val)
// 11、LInsert:在指定位置插入数据
err := conn.LInsert("go-demo:userList", "before", 5, 4).Err() // 在列表中5的前面插入4,before是之前的意思
if err != nil {
panic(err)
}
conn.LInsert("go-demo:userList", "before", "白夜", "玄衣素裳") // 在列表中 白夜 元素的前面插入 玄衣素裳
conn.LInsert("go-demo:userList", "after", "白夜", "春风之旅") // 在列表中 白夜 元素的后面插入 春风之旅
}
func set使用(conn *redis.Client) {
// 1、SAdd:添加集合元素
err := conn.SAdd("go-demo:roleList","管理员").Err()
if err != nil {
panic(err)
}
conn.SAdd("go-demo:roleList","VIP", "SVIP", "VVIP")
// 2、SCard:获取集合元素个数
size, err := conn.SCard("go-demo:roleList").Result()
if err != nil {
panic(err)
}
fmt.Println(size)
// 3、SIsMember:判断元素是否在集合中
ok, _ := conn.SIsMember("go-demo:roleList", "VVIP").Result()
if ok {
fmt.Println("集合包含指定元素")
}
// 4、SMembers:获取集合中所有的元素
es, _ := conn.SMembers("go-demo:roleList").Result()
fmt.Println(es) // 返回的es是string数组
// 5、SRem:删除集合元素,可以批量删除
conn.SRem("go-demo:roleList", "管理员")
conn.SRem("go-demo:roleList", "VIP", "20SVIP19")
// 6、SPop、SPopN:随机返回集合中的元素,并且删除返回的元素
val, _ := conn.SPop("go-demo:roleList").Result() // 随机返回集合中的一个元素,并且删除这个元素
fmt.Println(val)
vals, _ := conn.SPopN("go-demo:roleList", 5).Result() // 随机返回集合中的5个元素,并且删除这些元素
fmt.Println(vals)
}
这个有序集合,需要用到一个结构体:redis.Z
type Z struct {
Score float64 // 分数
Member interface{} // 元素名
}
func sortedSet使用(conn *redis.Client) {
// 1、ZAdd:添加一个或者多个元素到集合,如果元素已经存在则更新分数
err := conn.ZAdd("go-demo:scores", redis.Z{4.5,"admin"}).Err() // 添加一个集合元素到集合中, 这个元素的分数是4.5,元素名是admin
if err != nil {
panic(err)
}
// 2、ZCard:返回集合元素个数
size, err := conn.ZCard("go-demo:scores").Result()
if err != nil {
panic(err)
}
fmt.Println(size)
// 3、ZCount:统计某个分数范围内的元素个数:默认第二,第三个参数是大于等于和小于等于的关系;如果加上 ( 则表示大于或者小于,相当于去掉了等于关系。
size, err := conn.ZCount("go-demo:scores", "2","5").Result() // 返回: 2<=分数<=5 的元素个数, 注意:"1", "5"两个参数是字符串
size, err := conn.ZCount("go-demo:scores", "(1","5").Result() // 返回: 1<分数<=5 的元素个数
if err != nil {
panic(err)
}
fmt.Println(size)
// 4、ZIncrBy:增加元素的分数
conn.ZIncrBy("go-demo:scores", 2,"admin") // 给元素5,加上2分
// 5、ZRange:返回集合中某个索引范围的元素,根据分数从小到大排序
// 6、ZRevRange:用法跟ZRange一样,区别是ZRevRange的结果是按分数从大到小排序。
vals, err := conn.ZRange("go-demo:scores", 0,-1).Result() // 返回从0到-1位置的集合元素,元素按分数从小到大排序( 0到-1代表则返回全部数据)
if err != nil {
panic(err)
}
for _, val := range vals {
fmt.Println(val)
}
// 7、ZRangeByScore:根据分数范围返回集合元素,元素根据分数从小到大排序,支持分页。
// 8、ZRevRangeByScore:用法类似ZRangeByScore,区别是元素根据分数从大到小排序。
// 初始化查询条件, Offset和Count用于分页
op := redis.ZRangeBy{
Min:"2", // 最小分数
Max:"10", // 最大分数
Offset:0, // 类似sql的limit, 表示开始偏移量
Count:5, // 一次返回多少数据
}
vals, err := conn.ZRangeByScore("go-demo:scores", op).Result()
if err != nil {
panic(err)
}
for _, val := range vals {
fmt.Println(val)
}
// 9、ZRangeByScoreWithScores:用法跟ZRangeByScore一样,区别是除了返回集合元素,同时也返回元素对应的分数
// 初始化查询条件, Offset和Count用于分页
op := redis.ZRangeBy{
Min:"2", // 最小分数
Max:"10", // 最大分数
Offset:0, // 类似sql的limit, 表示开始偏移量
Count:5, // 一次返回多少数据
}
vals, err := conn.ZRangeByScoreWithScores("go-demo:scores", op).Result()
if err != nil {
panic(err)
}
for _, val := range vals {
fmt.Println(val.Member) // 集合元素
fmt.Println(val.Score) // 分数
}
// 10、ZRem:删除集合元素,支持批量删除
conn.ZRem("go-demo:scores", "admin") // 删除集合中的元素tizi
conn.ZRem("go-demo:scores", "admin", "fuhua") // 支持一次删除多个元素
// 11、ZRemRangeByRank:根据索引范围删除元素
conn.ZRemRangeByRank("go-demo:scores", 0, 5) // 集合元素按分数排序,从最低分到高分,删除第0个元素到第5个元素(这里相当于删除最低分的几个元素)。
// 这个例子,删除最高分数的两个元素,-1代表最高分数的位置,-2第二高分,以此类推。
conn.ZRemRangeByRank("go-demo:scores", -1, -2) // 位置参数写成负数,代表从高分开始删除。
// 12、ZRemRangeByScore:根据分数范围删除元素
conn.ZRemRangeByScore("go-demo:scores", "2", "5") // 删除范围: 2<=分数<=5 的元素
conn.ZRemRangeByScore("go-demo:scores", "2", "(5") // 删除范围: 2<=分数<5 的元素
// 13、ZScore:查询元素对应的分数
score, _ := conn.ZScore("go-demo:scores", "admin").Result()
fmt.Println(score)
// 14、ZRank:根据元素名,查询集合元素在集合中的排名,从0开始算,集合元素按分数从小到大排序
// 15、ZRevRank的作用跟ZRank一样,区别是ZRevRank是按分数从大到小排序。
rk, _ := conn.ZRank("go-demo:scores", "admin").Result()
fmt.Println(rk)
}
ok,以上就是本篇笔记的全部内容了,主要是 go-redis 连接redis的一些操作、基本使用。其实我个人做项目用的比较频繁的是普通的 get、set,还有 hash 和 list ,像集合和有序集合用的很少,几乎没怎么用过。
比如我做的springboot项目,用到redis的场景大部分都是存验证码、登录校验、token校验、存登录用户信息这些,还有少部分是没有mysql或sqlite数据库,而是用redis当作数据库来用的。