> 文章列表 > Go Redis

Go Redis

Go Redis

简述

Go语言中操作Redis有很多库可供选择,其中比较流行的有go-redis、redigo和go-redis-cluster等。
这里以go-redis为例,介绍如何使用Go语言操作Redis。

go get github.com/go-redis/redis #安装 第三方库

简单案例:

package mainimport ("fmt""github.com/go-redis/redis"
)func main() {// 创建Redis客户端client := redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "", // Redis密码DB:       0,  // Redis数据库编号})// 测试连接pong, err := client.Ping().Result()fmt.Println(pong, err)// 设置键值对err = client.Set("key", "value", 0).Err()if err != nil {panic(err)}// 获取键值对val, err := client.Get("key").Result()if err != nil {panic(err)}fmt.Println("key", val)// 删除键值对err = client.Del("key").Err()if err != nil {panic(err)}
}****

需要注意的是,使用go-redis库操作Redis时,需要注意线程安全性,可以使用连接池等方式来保证线程安全。

redis 连接池

作用一:
在连接池中制定最高连接IO上限,避免过度开辟连接到redis服务端的IO资源。

作用二:
事先初始化一定数量的IO连接,放入到连接池中,当GO需要操作redis时,直接从池子中获取连接,提高了IO连接的效率。

简单案例:

package mainimport ("fmt""github.com/go-redis/redis""sync"
)func main() {// 创建Redis连接池pool := redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "", // Redis密码DB:       0,  // Redis数据库编号PoolSize: 10, // 连接池大小})// 测试连接pong, err := pool.Ping().Result()fmt.Println(pong, err)// 并发操作Redisvar wg sync.WaitGroupfor i := 0; i < 100; i++ {wg.Add(1)go func() {defer wg.Done()err := pool.Set("key", "value", 0).Err()if err != nil {panic(err)}val, err := pool.Get("key").Result()if err != nil {panic(err)}fmt.Println("key", val)err = pool.Del("key").Err()if err != nil {panic(err)}}()}wg.Wait()// 关闭连接池pool.Close()
}

Go常见操作

	// 获取val, err := pool.Get("wtt").Result()if err != nil {panic(err)}fmt.Printf("%T---%+v\\n", val, val)// 设置err = pool.Set("key", "10", 0).Err()if err != nil {panic(err)}// 有效期设置err = pool.Set("key", "value", time.Second*10).Err()if err != nil {panic(err)}// 自增自减err = pool.Set("key", "10", 0).Err()if err != nil {panic(err)}err = pool.Incr("key").Err()if err != nil {panic(err) // 如果值不是 数字类型,则报错:ERR value is not an integer or out of range}err = pool.Decr("key").Err()if err != nil {panic(err)}

字符串

	// 存储字符串err := client.Set("name", "Tom", 0).Err()if err != nil {panic(err)}// 读取字符串name, err := client.Get("name").Result()if err != nil {panic(err)}fmt.Println("name:", name)// 删除字符串err = client.Del("name").Err()if err != nil {panic(err)}

列表

	// 存储列表err = client.LPush("list", "a", "b", "c").Err()if err != nil {panic(err)}// 读取列表list, err := client.LRange("list", 0, -1).Result()if err != nil {panic(err)}fmt.Println("list:", list)// 删除列表err = client.Del("list").Err()if err != nil {panic(err)}// 获取列表长度length, err := client.LLen("list").Result()if err != nil {panic(err)}fmt.Println("list length:", length)// 获取列表中指定位置的元素element, err := client.LIndex("list", 1).Result()if err != nil {panic(err)}fmt.Println("list[1]:", element)// 从列表头部弹出一个元素element, err = client.LPop("list").Result()if err != nil {panic(err)}fmt.Println("pop from left:", element)// 从列表尾部弹出一个元素element, err = client.RPop("list").Result()if err != nil {panic(err)}fmt.Println("pop from right:", element)// 将一个元素插入到列表头部,如果列表不存在则不插入err = client.LPushX("list", "d").Err()if err != nil {panic(err)}// 将一个元素插入到列表尾部,如果列表不存在则不插入err = client.RPushX("list", "e").Err()if err != nil {panic(err)}// 将一个元素插入到列表中指定元素的前面或后面err = client.LInsert("list", "BEFORE", "b", "x").Err()if err != nil {panic(err)}// 设置列表中指定位置的元素err = client.LSet("list", 1, "y").Err()if err != nil {panic(err)}// 截取列表,只保留指定范围内的元素err = client.LTrim("list", 1, 3).Err()if err != nil {panic(err)}// 阻塞式弹出元素,如果列表为空则一直等待,直到有元素可弹出result, err := client.BLPop(0, "list").Result()if err != nil {panic(err)}fmt.Println("blpop:", result)
}

集合

// 存储集合err = client.SAdd("set", "a", "b", "c").Err()if err != nil {panic(err)}// 读取集合set, err := client.SMembers("set").Result()if err != nil {panic(err)}fmt.Println("set:", set)// 删除集合err = client.Del("set").Err()if err != nil {panic(err)}// 获取集合中元素的数量count, err := client.SCard("set").Result()if err != nil {panic(err)}fmt.Println("set count:", count)// 判断元素是否在集合中exists, err := client.SIsMember("set", "a").Result()if err != nil {panic(err)}fmt.Println("a exists in set:", exists)// 从集合中随机弹出一个元素element, err := client.SPop("set").Result()if err != nil {panic(err)}fmt.Println("pop from set:", element)// 从集合中随机获取一个元素element, err = client.SRandMember("set").Result()if err != nil {panic(err)}fmt.Println("random element from set:", element)// 从集合中移除指定元素err = client.SRem("set", "b").Err()if err != nil {panic(err)}// 获取多个集合的交集intersection, err := client.SInter("set1", "set2").Result()if err != nil {panic(err)}fmt.Println("set1 ∩ set2:", intersection)// 获取多个集合的并集union, err := client.SUnion("set1", "set2").Result()if err != nil {panic(err)}fmt.Println("set1 ∪ set2:", union)// 获取多个集合的差集difference, err := client.SDiff("set1", "set2").Result()if err != nil {panic(err)}fmt.Println("set1 - set2:", difference)

散列

// 存储散列err = client.HSet("hash", "name", "Tom").Err()if err != nil {panic(err)}// 读取散列hash, err := client.HGetAll("hash").Result()if err != nil {panic(err)}fmt.Println("hash:", hash)// 删除散列err = client.Del("hash").Err()if err != nil {panic(err)}// 获取散列中指定字段的值value, err := client.HGet("hash", "field1").Result()if err != nil {panic(err)}fmt.Println("hash[field1]:", value)// 获取散列中所有字段和值all, err := client.HGetAll("hash").Result()if err != nil {panic(err)}fmt.Println("hash:", all)// 获取散列中所有字段fields, err := client.HKeys("hash").Result()if err != nil {panic(err)}fmt.Println("hash fields:", fields)// 获取散列中所有值values, err := client.HVals("hash").Result()if err != nil {panic(err)}fmt.Println("hash values:", values)// 判断散列中是否存在指定字段exists, err := client.HExists("hash", "field1").Result()if err != nil {panic(err)}fmt.Println("field1 exists in hash:", exists)// 删除散列中指定字段err = client.HDel("hash", "field2").Err()if err != nil {panic(err)}// 获取散列中字段的数量count, err := client.HLen("hash").Result()if err != nil {panic(err)}fmt.Println("hash count:", count)

有序集合

// 存储有序集合err = client.ZAdd("zset", redis.Z{Score: 1, Member: "a"}, redis.Z{Score: 2, Member: "b"}, redis.Z{Score: 3, Member: "c"},).Err()if err != nil {panic(err)}// 读取有序集合zset, err := client.ZRangeWithScores("zset", 0, -1).Result()if err != nil {panic(err)}fmt.Println("zset:", zset)// 删除有序集合err = client.Del("zset").Err()if err != nil {panic(err)}// 获取有序集合中元素的数量count, err := client.ZCard("zset").Result()if err != nil {panic(err)}fmt.Println("zset count:", count)// 获取有序集合中指定成员的排名rank, err := client.ZRank("zset", "b").Result()if err != nil {panic(err)}fmt.Println("b rank in zset:", rank)// 获取有序集合中指定成员的分数score, err := client.ZScore("zset", "c").Result()if err != nil {panic(err)}fmt.Println("c score in zset:", score)// 获取有序集合中指定排名范围内的成员和分数rangeByRank, err := client.ZRangeWithScores("zset", 0, 1).Result()if err != nil {panic(err)}fmt.Println("zset rank 0-1:", rangeByRank)// 获取有序集合中指定分数范围内的成员和分数rangeByScore, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{Min: "2",Max: "3",}).Result()if err != nil {panic(err)}fmt.Println("zset score 2-3:", rangeByScore)// 从有序集合中移除指定成员err = client.ZRem("zset", "b").Err()if err != nil {panic(err)}// 获取有序集合中指定排名范围内的成员和分数,并按分数从大到小排序rangeByRankDesc, err := client.ZRevRangeWithScores("zset", 0, 1).Result()if err != nil {panic(err)}fmt.Println("zset rank 0-1 desc:", rangeByRankDesc)

redis 发布订阅(消息队列)

Redis的发布订阅(Publish/Subscribe)是一种消息传递模式,它包含两个主要的角色:发布者和订阅者。
发布者将消息发送到指定的频道(Channel),订阅者可以订阅一个或多个频道,从而接收发布者发送的消息。

  • 发布者(生产者)
func main() {// 连接Redisclient := redis.NewClient(&redis.Options{Addr:     "localhost:6666",Password: "123456", // Redis密码DB:       0,        // Redis数据库编号})// 发布消息for i := 0; i < 10; i++ {msg := fmt.Sprintf("消息%d", i)err := client.Publish("mychannel", msg).Err()if err != nil {panic(err)}time.Sleep(time.Second)}fmt.Println("消息发布完成")
}
  • 订阅者(消费值)
func main() {// 连接Redisclient := redis.NewClient(&redis.Options{Addr:     "localhost:6666",Password: "123456", // Redis密码DB:       0,        // Redis数据库编号})// 订阅频道pubsub := client.Subscribe("mychannel")defer pubsub.Close()// 接收消息for {msg, err := pubsub.ReceiveMessage()if err != nil {panic(err)}fmt.Println("Received message:", msg.Payload)}
}

redis 管道

类似于mysql的存储过程, Redis管道是一种优化Redis操作的方法,它可以将多个Redis命令打包成一个请求一次性发送给Redis服务器,
从而减少了网络通信的开销,提高了Redis的性能。

func main() {// 批量设置键值对pipe := client.Pipeline() // 生成一个管道pipe.Set("key1", "value1", 0)pipe.Set("key2", "value2", 0)_, err := pipe.Exec() // 将管道中 存储的命令 一次性都执行if err != nil {panic(err)}
}

redis 事务

Redis事务是一组命令的集合,这些命令可以作为一个单独的操作来执行,要么全部执行成功,要么全部执行失败,
不存在部分执行的情况。Redis事务的执行是原子性的,即在事务执行期间,其他客户端不能对其中的数据进行修改。

Redis事务的执行分为三个步骤:=开始事务、执行事务、提交事务=。
在执行事务期间,客户端可以向事务中添加多个命令,这些命令不会立即执行,而是在执行事务时一起执行。

  • MULTI:开启事务
  • EXEC:执行事务(让开启事务之后的一系列的redis语句 对redis客户端的数据生效)
  • DISCARD:取消事务
  • WATCH:监视事务中的键变化,一旦有改变则取消事务。
func main() {// 连接Redisclient := redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "", // Redis密码DB:       0,  // Redis数据库编号})// 开始事务tx := client.TxPipeline()defer tx.Close()// 添加命令tx.Set("key1", "value1", 0)tx.Set("key2", "value2", 0)// 执行事务_, err := tx.Exec()if err != nil {panic(err)}fmt.Println("Transaction executed")
}

思考: redis 事务操作的时候 会 锁着 待操作的 数据吗?

Redis在执行事务期间=不会锁定待操作的数据=,因此在事务执行期间,其他客户端仍然可以对其中的数据进行修改。

Redis的事务是通过将多个命令打包成一个原子操作来实现的,这些命令在执行时会被放入一个队列中,
直到执行EXEC命令时才会一起执行。因此,在事务执行期间,其他客户端可以对其中的数据进行修改,
但是这些修改不会影响到事务的执行结果。

如果需要保证事务的执行结果不受其他客户端的影响,可以使用Redis的 乐观锁机制,即在执行事务前获取数据的版本号
然后在执行事务时检查数据的 版本号 是否发生变化,如果没有变化,则执行事务,否则放弃事务。

总之,=Redis的事务机制并不是通过锁定待操作的数据来实现的,而是通过将多个命令打包成一个原子操作来实现的=。

redis 分布式事务锁

Redis分布式事务锁是一种 基于Redis 实现的分布式锁,它可以用于解决分布式系统中的 并发问题。
在分布式系统中,多个进程或线程可能同时访问同一个资源,如果不加控制,就会出现数据不一致的问题。
分布式锁就是为了解决这个问题而设计的。

Redis分布式事务锁的实现原理是利用Redis的事务和Lua脚本功能。具体实现步骤如下:
1、生成一个唯一的标识符,作为锁的名称。
2、使用Redis的SETNX命令尝试获取锁,如果返回值为1,则表示获取锁成功,否则表示锁已经被其他进程或线程占用。
3、如果获取锁成功,则设置锁的过期时间,防止锁一直被占用。
4、执行业务逻辑。
5、释放锁,使用Redis的DEL命令删除锁。

下面是一个使用Go语言实现Redis分布式事务锁的示例代码:

package mainimport ("fmt""time""github.com/go-redis/redis"
)func main() {// 创建Redis客户端client := redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "", // Redis无密码DB:       0,  // 默认数据库})// 生成一个唯一的标识符,作为锁的名称lockKey := "lock"// 设置锁的过期时间lockExpire := 10 * time.Second// 尝试获取锁for {// 使用Redis的SETNX命令尝试获取锁ok, err := client.SetNX(lockKey, 1, lockExpire).Result()if err != nil {panic(err)}// 如果获取锁成功,则执行业务逻辑if ok {fmt.Println("get lock success")// 执行业务逻辑// ...// 释放锁err := client.Del(lockKey).Err()if err != nil {panic(err)}fmt.Println("release lock success")break}// 如果获取锁失败,则等待一段时间后重试time.Sleep(100 * time.Millisecond)}
}

说明:
需要注意的是,Redis分布式事务锁虽然可以解决分布式系统中的并发问题,但也存在一些缺点,比如可能会出现死锁、锁竞争等问题,需要根据具体情况进行调整和优化。