流水线
1
2
3
4
5
6
7
8
9
10
11
12
13
|
pipe := rdb.Pipeline()
incr := pipe.Incr("pipeline_counter")
pipe.Expire("pipeline_counter", time.Hour)
// Execute
//
// INCR pipeline_counter
// EXPIRE pipeline_counts 3600
//
// using one rdb-server roundtrip.
_, err := pipe.Exec()
fmt.Println(incr.Val(), err)
|
上面的代码也可以写成下面的形式:
1
2
3
4
5
6
7
|
var incr *redis.IntCmd
_, err := rdb.Pipelined(func(pipe redis.Pipeliner) error {
incr = pipe.Incr("pipelined_counter")
pipe.Expire("pipelined_counter", time.Hour)
return nil
})
fmt.Println(incr.Val(), err)
|
事务
TxPipeline
TxPipeline的行为类似于Pipeline,但是用MULTI / EXEC包装了排队的命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 开启一个TxPipeline事务
pipe := rdb.TxPipeline()
// 执行事务操作,可以通过pipe读写redis
incr := pipe.Incr("tx_pipeline_counter")
pipe.Expire("tx_pipeline_counter", time.Hour)
// 上面代码等同于执行下面redis命令
//
// MULTI
// INCR pipeline_counter
// EXPIRE pipeline_counts 3600
// EXEC
// 通过Exec函数提交redis事务
_, err := pipe.Exec()
// 提交事务后,我们可以查询事务操作的结果
// 前面执行Incr函数,在没有执行exec函数之前,实际上还没开始运行。
fmt.Println(incr.Val(), err)
|
Watch
redis乐观锁支持,可以通过watch监听一些Key, 如果这些key的值没有被其他人改变的话,才可以提交事务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 定义一个回调函数,用于处理事务逻辑
fn := func(tx *redis.Tx) error {
// 先查询下当前watch监听的key的值
v, err := tx.Get("key").Result()
if err != nil && err != redis.Nil {
return err
}
// 这里可以处理业务
fmt.Println(v)
// 如果key的值没有改变的话,Pipelined函数才会调用成功
_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
// 在这里给key设置最新值
pipe.Set("key", "new value", 0)
return nil
})
return err
}
// 使用Watch监听一些Key, 同时绑定一个回调函数fn, 监听Key后的逻辑写在fn这个回调函数里面
// 如果想监听多个key,可以这么写:client.Watch(fn, "key1", "key2", "key3")
client.Watch(fn, "key")
|
SCAN
可以使用Iterator方法:
1
2
3
4
5
6
7
|
iter := rdb.Scan(ctx, 0, "", 0).Iterator()
for iter.Next(ctx) {
fmt.Println(iter.Val())
}
if err := iter.Err(); err != nil {
panic(err)
}
|
Do命令不能与普通命令混用
异常导致tcp断连的代码
1
2
3
4
5
6
7
8
|
func SpeedLimit(k string, times int, t time.Duration) (e error) {
r := redisdb
key := "speed_limit_" + k
r.Do("MULTI")
r.Incr(key)
e = r.Do("EXEC").Err()
return
}
|
wireshark抓包
redis tcp建立连接三次握手

redis密码鉴权
req

rsp

可以看到三次握手之后是没有做TLS/SSL 握手的。Redis为了高效,仅提供了最基础的密码验证。当你想把redis-server暴露在公网上面时,仅依靠这个密码验证是远远不够的,就算你把密码设得再长再复杂,加大了暴破的难度,但由于通讯过程不加密,你的“正版”客户端在连接时,还是会有密码泄露的风险。
MULTI
至于redis的通信协议,可以参考 redis协议
req

rsp

INCR
req

rsp

从回包可以看到,redis-server对于incr的命令返回了正确的响应,表示已经入队了
redis客户端主动断开连接
这个时候突然出现了,客户端主动向redis-server发起的FIN信令(redis-server的端口是默认6379),既然已经收到了INCR正确的rsq,为甚这个地方要断连呢。
通过断点调试发现,当收到redis-server的正确回包时,走到下图红框的分支,删除了当前连接


原因为在读返回值时,未找到对应的正确的返回值,可以看到这里只支持,’-‘和’:‘的消息前缀格式


但是收到的入队的reply是以’+‘开头的

分析代码发现,Incr这个函数,new一个IntCmd类型的实例,此类型实现的readReply接口如上,只支持’-‘和’:‘的消息前缀格式


将r.Incr(key) 替换为 r.Do(“incr”, key) ,不会由于读replay err导致断连,代码中Do()返回通用的CMD,支持所有的Redis回包格式。
分析后学习到使用go-redis的接口时,尽量二选一,全部使用Do自己传入redis的命令,或者全部使用redis的封装好的命令接口,两种方式混用可能会导致类似上述的异常。
参考:https://www.yuque.com/alipaydg7i09aywu/nkx0fe/ac66mw