简述
Go语言中操作Redis有很多库可供选择,其中比较流行的有go-redis、redigo和go-redis-cluster等。
这里以go-redis
为例,介绍如何使用Go语言操作Redis。
go get github.com/go-redis/redis #安装 第三方库
简单案例:
package main
import (
"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 main
import (
"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)
// 并发操作Redis
var wg sync.WaitGroup
for 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() {
// 连接Redis
client := 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() {
// 连接Redis
client := 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() {
// 连接Redis
client := 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 main
import (
"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分布式事务锁虽然可以解决分布式系统中的并发问题,但也存在一些缺点,比如可能会出现死锁、锁竞争
等问题,需要根据具体情况进行调整和优化。