redis的四大功能
multi watch discard exec
multi
词条意思是多、多个,这个命令的用途是用来开启事务的,开启事务后,之后执行的cmd命令,都会进入事务队列,并不会马上执行,只有当执行exec后, 队列中的命令才会按顺序进行,执行顺序是先进队列的先执行。如果有一条命令执行失败,那么整个事务都会失败,事务是原子性的。
127.0.0.1:6379> multi //开启事务
OK
127.0.0.1:6379> hset go 1 1 //第一条命令
QUEUED
127.0.0.1:6379> hset go 2 2 //第二条命令
QUEUED
127.0.0.1:6379> exec // 执行队列
1) (integer) 0
2) (integer) 1
discard
discard是用来取消事务的,当事务还未执行时,想要取消multi的队列,可以用该命令取消,取消后队列中的所有命令都会取消。
127.0.0.1:6379> multi //开启事务
OK
127.0.0.1:6379> multi //再次开启(失败)
(error) ERR MULTI calls can not be nested
127.0.0.1:6379> discard //取消事务
OK
exec
exec 如上所说,就是用来执行队列的,与multi组合使用
watch
监听事务 ,一般和multi、exec混合使用,watch用来监听某一个键,在事务开始前,如果当前监听的键被其他客户端改动,那么整个事务都会失败。
127.0.0.1:6379> watch go //客户端A监听go
OK
127.0.0.1:6379> multi //开启事务
OK
127.0.0.1:6379> hset go 10 10 //命令放入队列
QUEUED
127.0.0.1:6379> exec //执行命令
(nil)
127.0.0.1:6379>
---------------------------------------------
127.0.0.1:6379> hset go 11 11 //在客户端A执行exec之前,客户端B执行该命令
(integer) 1
redis命令
set key-value类型储存
127.0.0.1:6379> set redis hello
OK
-------------------------------
127.0.0.1:6379> get redis
"hello"
zadd 有序集合
127.0.0.1:6379> zadd hello 1 china
(integer) 1
127.0.0.1:6379> zadd hello 3 go
(integer) 1
127.0.0.1:6379> zadd hello 2 golang
(integer) 1
-----------------------------------
127.0.0.1:6379> zrange hello 0 10
1) "china"
2) "golang"
3) "go"
sadd 集合
127.0.0.1:6379> sadd world A
(integer) 1
127.0.0.1:6379> sadd world C
(integer) 1
127.0.0.1:6379> sadd world B
(integer) 1
----------------------------
127.0.0.1:6379> smembers world
1) "B"
2) "C"
3) "A"
hset/hmset 哈希储存
127.0.0.1:6379> hmset routine A a
OK
127.0.0.1:6379> hset routine B b
(integer) 1
127.0.0.1:6379> hmset routine C c D d
OK
-------------------------------------
127.0.0.1:6379> hget routine A
"a"
127.0.0.1:6379> hmget routine A B
1) "a"
2) "b"
127.0.0.1:6379> hgetall routine
1) "A"
2) "a"
3) "B"
4) "b"
5) "C"
6) "c"
7) "D"
8) "d"
lpush 链表
127.0.0.1:6379> lpush hello_world A B
(integer) 2
---------------------------------------
127.0.0.1:6379> lrange hello_world 0 10
1) "B"
2) "A"
go-redis使用教学
常规操作
// TestNomarl 常规操作
func TestNomarl() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 2,
Password: "",
})
defer client.Close()
client.Set("hello", "world", 0)
client.LPush("list", "A")
client.ZAdd("zset", redis.Z{1, "A"}, redis.Z{2, "B"})
client.SAdd("set", "A", "B")
client.HSet("hashMap", "key", "value")
client.HMSet("hashMap", map[string]interface{}{"key1": "value1", "key2": "value2"})
}
开启事务
// TestTransaction 事务操作
func TestTransaction() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 2,
Password: "",
})
tx := client.TxPipeline()
tx.SAdd("txset", "A")
tx.SAdd("txset", "B")
cmder, _ := tx.Exec()
for _, cmd := range cmder {
fmt.Println(cmd)
}
}
go-redis的事务是通过管道实现的,也就是TxPipeline
func txPipelineWriteMulti(wr *proto.Writer, cmds []Cmder) error {
multiExec := make([]Cmder, 0, len(cmds)+2)
multiExec = append(multiExec, NewStatusCmd("MULTI"))
multiExec = append(multiExec, cmds...)
multiExec = append(multiExec, NewSliceCmd("EXEC"))
return writeCmd(wr, multiExec...)
}
go-redis管道分事务版本和普通管道,普通管道是将所有命令放入管道中,让管道按顺序执行命令,这个和常规操作造成的结果是一样,只不过前者是调用者一个个去执行,而后者是调用者将命令交给管道,让管道去一个个执行。 事务管道则是,在命令之前加上一条multi命令,最后加一条exec命令用来开启事务
watch监听
当事务还未执行之前,被监听的key被其他的客户端修改,当前事务就会fail,如果在监听期间key没有被修改过,那么事务就会成功
// TestWatch 监听关键字
func TestWatch() {
key := "watch"
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 2,
Password: "",
})
client1 := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 2,
Password: "",
})
err := client.Watch(func(tx *redis.Tx) error {
pipe := tx.TxPipeline()
var err error
pipe.HSet(key, "haha", "hehe")
pipe.HSet(key, "hehe", "haha")
client1.HSet(key, "huhu", "huhu")
_, err = pipe.Exec()
return err
}, key)
fmt.Println(err)
}
go-redis 代码阅读
其实就是通过tcp连接上redis服务端,然后io.write命令到服务端
为了避免反复打开创建tcp连接,go-redis引入了连接池的概念 go-redis的池会有一个连接池(conns)、一个有效连接池(idleConns)、一个连接池上限(PoolSize)的重要概念 连接池不需要关心单个连接的关闭,与创建,通过外部调用p.getConn(),即可获取一个可用的连接。 设计原理:当连接池未达到上限的时候,且idleconns中无可用连接时,将会创建一个conn,并且执行相关cmd,执行完成后,conn同步放进idleConns池中 当idleconns中有值,直接从末端拿出一个使用,使用完后,放入末端。 当连接数conns的数量达到上限时,且没有可用idleconn,将会创建一个conn,并且在执行完cmd后进行销毁。 而长期处于空闲状态的idleconns将会被定时移除,同时同步移除conns。
简单的连接发送请求的实例
conn, err := net.Dial("tcp", "127.0.0.1:6379")
if err != nil {
fmt.Println("err:", err)
return
}
defer conn.Close()
cmd := "SET"
key := "test"
value := "tt"
str := fmt.Sprintf("*%d\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", 3, len(cmd), cmd, len(key), key, len(value), value)
var cmdbuf bytes.Buffer
fmt.Fprintf(&cmdbuf, str)
b := cmdbuf.Bytes()
defer conn.Close()
_, err = conn.Write(b)
if err != nil {
fmt.Println("err:", err)
return
}