一. Redis 连接
1. 进入redis环境
代码语言:txt复制docker run -d -p 6379:6379 --name redis redis:latest
docker exec -it redis redis-cli
2. 检测 Redis 是否正常
代码语言:txt复制127.0.0.1:6379> ping
PONG
3. 退出连接
代码语言:txt复制127.0.0.1:6379> quit
[root@VM-4-12-centos data]#
二. redis内容
1. key 操作命令
输入命令 | 含义 |
---|---|
keys * | 查看所有的key |
dbsize | 获取总key数 |
exists key | 查询键是否存在 |
del key | 删除key |
type key | 查询键类型(string,set..) |
ttl key | 查询key的生命周期(秒) |
pttl key | 查询key的生命周期(毫秒) |
expire key | 设置过期时间(秒) |
pexpire key | 设置过期时间(毫秒) |
persist key | 设置永不过期 |
rename key newkey | 更改键名称 |
2. 字符串string 操作命令
输入命令 | 含义 | 举例 | 返回值 |
---|---|---|---|
set key value | 存放键值 | set java1 test | OK |
get key | 获取键值 | get java1 | "test" |
mset key1 value1 key2 value2 | 批量存放 | mset java1 1 java2 2 java3 3 | OK |
mget key1 key2 | 批量获取 | mget java1 java2 | 1) "test" 2) "2" |
strlen key | 获取值长度 | strlen java1 | 4 |
append key value | 追加内容 | append java1 test2 | (integer) 6 |
getrange key start end | 获取部分内容 | getrange java1 0 3 | "1tes" |
3. 无序集合Set 操作命令
相当于 Java 语言里面的 HashSet
语法 | 含义 | 举例 | 返回值 |
---|---|---|---|
sadd key member... | 存储值 | sadd langs java php c go ruby python kotlin java | (integer) 7 |
smembers key | 获取所有元素语法 | smembers langs | 1) "php"2) "kotlin"3)"c "4) "go"5) "ruby"6) "python"7) "java" |
srandmember key count | 随机获取元素语法 | srandmember langs 3 | 1)"kotlin"2) "python"3) "c " |
sismember key member | 判断集合是否存在元素 | sismember langs go | (integer) 1 |
scard key | 获取集合元素个数 | scard langs | (integer) 7 |
srem key member.. | 删除集合元素 | srem langs java | (integer) 1 |
spop key count | 弹出元素 | spop langs 2 | 1) "python" 2) "go" |
4. 有序集合zset
类似于 Java 的 SortedSet 和 HashMap 的结合体
和列表的区别:
1、列表使用链表实现,两头快,中间慢。有序集合是散列表和跳跃表实现的,即使读取中间的元素也比较快。
2、列表不能调整元素位置,有序集合能。
3、有序集合比列表更占内存。
输入命令 | 含义 | 举例 | 返回值 |
---|---|---|---|
| 存储值 | zadd footCounts 16011 tid 20082 huny 2893 nosy | 3 |
zscore key member | 获取元素分数 | zscore footCounts tid | "16011" |
zincrby key increment member | 增加指定元素分数 | zincrby footCounts 2000 tid | "18011" |
zcard key | 获取集合元素个数 | zcard footCounts | 3 |
| 删除指定元素 | zrem footCounts huny | 1 |
zrank key member | 获取元素排名 | zrank footCounts tid | 2 |
| 获取指定分数范围排名语法 | zrangebyscore footCounts 3000 30000 withscores limit 0 1 | 1) "tid"2) "16011" |
zcount key min max | 获取指定范围分数个数 | zcount footCounts 2000 20000 | 2 |
5. 列表操作命令list
Redis
中的 list
和 Java
中的 LinkedList
很像,底层都是一种链表结构, list
的插入和删除操作非常快,时间复杂度为 0(1),不像数组结构插入、删除操作需要移动数据。
输入命令 | 含义 | 举例 | 返回值 |
---|---|---|---|
lpush key value ... | 左端存值语法 | lpush list lily sandy | 2 |
rpush key value ... | 右端存值语法 | rpush list tom kitty | 4 |
lset key index value | 索引存值语法 | lset list 3 uto | OK |
lpop key | 左端弹出语法 | lpop list | "broad" |
rpop key | 右侧弹出语法 | rpop list | "kitty" |
llen key | 获取元素个数 | llen list | 2 |
lrange key start top | 获取列表元素 | lrange list 0 -1 | 1) "sandy" 2) "lily"3)"uto" |
index key index | 索引获取语法 | lindex list 2 | "ketty " |
lrem key count value | 根据值删除语法 | lrem userids 0 111 //count=0 删除所有 | 2 |
ltrim key start stop | 范围删除语法 | ltrim list 2 4 | OK |
6. 散列表hash
Redis
中的 Hash
和 Java的 HashMap
更加相似, 都是 数组 链表
的结构,当发生 hash 碰撞时将会把元素追加到链表上,值得注意的是在 Redis
的 Hash
中 value
只能是字符串.
输入命令 | 含义 | 举例 | 返回值 |
---|---|---|---|
hset key field value | 存放键值 | hset user name javastack | 1 |
hmset key field value field value ... | 2 | hmset user name javastack age 20 address china | 4 |
hsetnx key field value | 不存在时语法 | hsetnx user tall 180 | 0 |
hget key field | 获取字段值 | hget key field | "20" |
hmget key field field ... | 获取多个字段值 | hmget user name age address | 1) "javastack"2)"20"3) "china" |
hgetall key | 获取所有键与值语法 | hgetall user | 1) "name"2) "javastack"3) "age"4) "20"5) "address"6) "china" |
hkeys key | 获取所有字段语法 | 127.0.0.1:6379> hkeys user | 1) "name"2) "address"3) "tall"4) "age" |
hvals key | 获取所有值语法 | hvals user | 1) "javastack"2) "china"3) "170"4) "20" |
hexists key field | 判断字段是否存在 | hexists user address | 1 |
hlen key | 获取字段数量 | hlen user | 4 |
hincrby key field increment | 递增/减 | hincrby user tall -10 | 170 |
hdel key field field ... | 删除字段 | hdel user age | 1 |
三. key的操作之get set
1. get、set去设置
代码语言:txt复制// exampleClient redis连接客户端的代码
func exampleClient() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
panic(err)
}
fmt.Println("key", val)
val2, err := rdb.Get(ctx, "key2").Result()
if err == redis.Nil {
fmt.Println("key2 does not exist")
} else if err != nil {
panic(err)
} else {
fmt.Println("key2", val2)
}
}
2. 执行任意命令
go-redis 还提供了一个执行任意命令或自定义命令的 Do 方法,特别是一些 go-redis 库暂时不支持的命令都可以使用该方法执行。具体使用方法如下
代码语言:txt复制// doCommand 基本的get set
func doCommand() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
defer cancel()
// 执行命令获取结果
val, err := rdb.Get(ctx, "key").Result()
fmt.Println("test1", val, err)
// 先获取到命令对象
cmder := rdb.Get(ctx, "key")
fmt.Println("test2", cmder.Val())
fmt.Println("test3", cmder.Err())
// 直接执行命令获取错误
// rdb.Set(ctx, "key", 10, time.Hour).Err()
err = rdb.Set(ctx, "key", 3000, 0).Err()
// 直接执行命令获取值
value := rdb.Get(ctx, "key").Val()
fmt.Println("test4", value)
}
3. redis.Nil
go-redis 库提供了一个 redis.Nil 错误来表示 Key 不存在的错误。因此在使用 go-redis 时需要注意对返回错误的判断。在某些场景下我们应该区别处理 redis.Nil 和其他不为 nil 的错误。
代码语言:txt复制// exampleClient redis连接客户端的代码
func exampleClient() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
panic(err)
}
fmt.Println("key", val)
val2, err := rdb.Get(ctx, "key2").Result()
if err == redis.Nil {
fmt.Println("key2 does not exist")
} else if err != nil {
panic(err)
} else {
fmt.Println("key2", val2)
}
}
4. 扫描或遍历所有key
你可以使用 KEYS prefix:*
命令按前缀获取所有 key
func ScanKeysDemo2() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
defer cancel()
// 按前缀扫描key
iter := rdb.Scan(ctx, 0, "prefix:*", 0).Iterator()
// 此外,对于 Redis 中的 set、hash、zset 数据类型,go-redis 也支持类似的遍历方法。
// iter := rdb.SScan(ctx, "set-key", 0, "prefix:*", 0).Iterator()
// iter := rdb.HScan(ctx, "hash-key", 0, "prefix:*", 0).Iterator()
// iter := rdb.ZScan(ctx, "sorted-hash-key", 0, "prefix:*", 0).Iterator()
for iter.Next(ctx) {
fmt.Println("[key]:", iter.Val())
}
if err := iter.Err(); err != nil {
panic(err)
}
}
四. zset命令
代码语言:txt复制func ZSetDemo() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
zsetKey := "language_rank"
languages := []*redis.Z{
{Score: 90.0, Member: "Golang"},
{Score: 98.0, Member: "Java"},
{Score: 95.0, Member: "Python"},
{Score: 97.0, Member: "JavaScript"},
{Score: 99.0, Member: "C/C "},
}
ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
defer cancel()
// ZADD
err := rdb.ZAdd(ctx, zsetKey, languages...).Err()
if err != nil {
fmt.Printf("zadd failed, err:%vn", err)
return
}
fmt.Println("zadd success!")
// Goland分数加10
newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()
if err != nil {
fmt.Printf("zincrby failed, err:%vn", err)
return
}
fmt.Printf("Goland's score is %f now.n", newScore)
// 取分数最高的3个
ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()
for _, z := range ret {
fmt.Println(z.Member, z.Score)
}
fmt.Println("=================")
// 取95~100分的
op := redis.ZRangeBy{
Min: "95",
Max: "100",
}
ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()
if err != nil {
fmt.Printf("zrangebyscore failed, err:%vn", err)
return
}
for _, z := range ret {
fmt.Println(z.Member, z.Score)
}
}
执行上面的函数将得到如下输出结果。
代码语言:txt复制zadd success
Golang's score is 100.000000 now.
Golang 100
C/C 99
Java 98
Python 95
JavaScript 97
Java 98
C/C 99
Golang 100
五. Pipeline
Redis Pipeline 允许通过使用单个 client-server-client 往返执行多个命令来提高性能。区别于一个接一个地执行100个命令,你可以将这些命令放入 pipeline 中,然后使用1次读写操作像执行单个命令一样执行它们。这样做的好处是节省了执行命令的网络往返时间(RTT)。
1. 写法一. pipeline
代码语言:txt复制func PipelineWithNoExecDemo() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, _ := context.WithTimeout(context.Background(), time.Hour)
var incr *redis.IntCmd
_, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
incr = pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)
return nil
})
if err != nil {
panic(err)
}
// 在执行pipe.Exec之后才能获取到结果
fmt.Println(incr.Val())
}
也可以使用 Pipelined
方法,它会在函数退出时调用 Exec. 代码会更加简洁
2. 写法二. pipelined
代码语言:txt复制func PipelineWithNoExecMulDemo() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, _ := context.WithTimeout(context.Background(), time.Hour)
_, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 100; i {
pipe.Set(ctx, fmt.Sprintf("key%d", i), fmt.Sprintf("key%d", i), time.Hour)
}
return nil
})
if err != nil {
panic(err)
}
cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 100; i {
pipe.Get(ctx, fmt.Sprintf("key%d", i))
}
return nil
})
if err != nil {
panic(err)
}
for _, cmd := range cmds {
fmt.Println(cmd.(*redis.StringCmd).Val())
}
}
六. 加锁
Redis 是单线程执行命令的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是, Multi/exec
能够确保在 multi/exec
两个语句之间的命令之间没有其他客户端正在执行命令。
在这种场景我们需要使用 TxPipeline 或 TxPipelined 方法将 pipeline 命令使用 MULTI
和 EXEC
包裹起来。
// ZTransactionTxPipeDemo 这里只是相当于加锁,TxPipeline/TxPipelined
func ZTransactionTxPipeDemo() {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
defer cancel()
// TxPipeline demo
pipe := rdb.TxPipeline()
pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
_, err := pipe.Exec(ctx)
if err != nil {
panic(err)
}
// TxPipelined demo
var incr2 *redis.IntCmd
_, err = rdb.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
incr2 = pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
return nil
})
fmt.Println(incr2.Val(), err)
}
七. 事务-watch
我们通常搭配 WATCH
命令来执行事务操作。从使用 WATCH
命令监视某个 key 开始,直到执行 EXEC
命令的这段时间里,如果有其他用户抢先对被监视的 key 进行了替换、更新、删除等操作,那么当用户尝试执行 EXEC
的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。
Watch方法接收一个函数和一个或多个key作为参数
代码语言:txt复制// WatchDemo 配合TxPipeline/TxPipelined完成增删改查
func WatchDemo() error {
rdb := redis.NewClient(redis.Options{Addr: "localhost:6379", Password: "", DB: 0})
ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Microsecond)
defer cancel()
key := "key"
return rdb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int()
if err != nil; err != redis.Nil {
return err
}
// 假设操作耗时5秒
// 5秒内我们通过其他的客户端修改key,当前事务就会失败
time.Sleep(5 * time.Second)
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, key, n 1, time.Hour)
return nil
})
return err
}, key)
}
参考文章
- Go语言操作Redis
- https://github.com/go-redis/redis
- Redis 常用操作命令,非常详细!
- Redis基础系列短视频《2. Redis简单实战》
- redis中文官网
- Redis 常用命令