用 Go 访问 Redis
Redis 官方并没有提供 Redis 访问包,官网列出来了很多 Go 语言的客户端包,它们都能实现对 Redis 的访问和操作,本文以其中较为人性化的 Redigo 来讲解。
1. Redis 连接
获取项目包:
$ go get github.com/gomodule/redigo
接下来调用 redis.Dial()
连接 Redis 服务器:
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed, err:", err)
return
}
defer c.Close()
}
2. Redis 设置和获取字符串
最常用的是 Do()
方法,他可以支持 Redis 的 Set、Get、MSet、MGet、HSet、HGet 等常用命令:
_, err = c.Do("Set", "username", "jim")
if err != nil {
fmt.Println(err)
return
}
在 Redigo 客户端包中,通过调用 redis.String()
函数来获取字符串:
res, err := redis.String(c.Do("Get", "username"))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(res)
3. Redis 批量设置
可以用 Do()
批量设置字符串:
_, err = c.Do("MSet", "username", "james", "phone", "18888888888")
if err != nil {
fmt.Println("MSet error: ", err)
return
}
4. Redis hash 操作
_, err = c.Do("HSet", "names", "jim", "barry")
if err != nil {
fmt.Println("hset error: ", err)
return
}
res3, err := redis.String(c.Do("HGet", "names", "jim"))
if err != nil {
fmt.Println("hget error: ", err)
return
}
fmt.Println(res3)
5. Redis 设置过期时间
_, err = c.Do("expire", "names", 5)
if err != nil {
fmt.Println("expire error: ", err)
return
}
6. Redis 队列
// 队列
_, err = c.Do("lpush", "Queue", "jim", "barry", 9)
if err != nil {
fmt.Println("lpush error: ", err)
return
}
for {
r, err := redis.String(c.Do("lpop", "Queue"))
if err != nil {
fmt.Println("lpop error: ", err)
break
}
fmt.Println(r)
}
res4, err := redis.Int(c.Do("llen", "Queue"))
if err != nil {
fmt.Println("llen error: ", err)
return
}
fmt.Println(res4)
7. 实现 Redis 连接池功能
为什么使用连接池? Redis也是一种数据库,它基于 C/S 模式,因此如果需要使用,则必须先建立连接。C/S 模式就是一种远程通信的交互模式,因此 Redis服务器可以单独作为一个数据库服务器独立存在。
假设Reds服务器与客户端分处异地,虽然基于内存的 Redis数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间。因为,每次数据交互都需要先建立连接。假设一 次数据交互总共用时 30ms,超高性能的 Redis 数据库处理数据所花的时间可能不到 1ms,也就是 说前期的连接占用了 29ms。
连接池则可以实现在客户端建立多个与服务器的连接并且不释放当需要使用连接时,通过一定的算法获取已经建立的连接,使用完后则还给连接池,这就免去了连接服务器所占用的时间。
Redigo 客户端包中通过 Pool
对象来建立连接池,其使用方法如下:
-
使用 Pool 结构体初始化一个连接池:
pool = &redis.Pool{ MaxIdle: 16, MaxActive: 1024, IdleTimeout: 300, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "localhost:6379") }, }
-
调用
Do()
方法来设置和获取字符串完整代码如下:
package main import ( "fmt" "github.com/gomodule/redigo/redis" ) var pool *redis.Pool func init() { pool = &redis.Pool{ MaxIdle: 16, MaxActive: 1024, IdleTimeout: 300, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "localhost:6379") }, } } func main() { c := pool.Get() defer c.Close() _, err := c.Do("Set", "username", "jack") if err != nil { fmt.Println(err) return } r, err := redis.String(c.Do("Get", "username")) if err != nil { fmt.Println(err) return } fmt.Println(r) }
8. Redis 实现管道操作
请求/响应服务可以实现持续处理新请求。客户端可以发送多个命令到服务器端而无须等待响应,最后再一次性读取多个响应。
Send()、 Flush()、 Receive()方法支持管道化操作。Send()方法用于向连接的输出缓冲中写入 命令。 Flush()方法用于将连接的输出缓冲清空并写入服务器端。 Recevie()方法用于按照 FIFO 顺序依次读取服务器端的响应。示例代码如下:
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed, err:", err)
return
}
defer c.Close()
c.Send("SET", "username1", "jim")
c.Send("SET", "username2", "jack")
c.Flush()
v, err := c.Receive()
fmt.Printf("v:%v,err:%v\n", v, err)
v, err = c.Receive()
fmt.Printf("v:%v,err:%v\n", v, err)
v, err = c.Receive() // 一直等待
fmt.Printf("v:%v,err:%v\n", v, err)
}
结果输出:
v:OK,err:<nil>
v:OK,err:<nil>
9. Redis 的并发
在日常开发中,有时会遇到这样的场景:多个人时对同一个数据进行修改,导致并发问题发 生。使用 Redis来解决这个问题是很好的选择。
Redis 管道使得客户端能够用“无等待响应”的方式,来连续发送多条命令请求至 Redis 服务器端,然后服务器端按照请求顺序返回相应的结果。
Redis管道( Pipelining)的操作可以理解为并发操作,并通过 Send、 Flush、 Receivel 这3个方法实现客户端可以用Send(方法一次性向服务器发送一个或多个命令。命令发送完毕后, 用 Flush()
方法将缓冲区的命令一次性发送到服务器端,客户端再用 Receive()
方法依次按照先进先出的顺序读取所有命令的结果。 Redis并发的示例如下
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("connect redis error :", err)
return
}
defer conn.Close()
conn.Send("HSET", "students", "name", "jim", "age", "19")
conn.Send("HSET", "students", "score", "100")
conn.Send("HGET", "students", "age")
conn.Flush()
res1, err := conn.Receive()
fmt.Printf("Receive res1:%v \n", res1)
res2, err := conn.Receive()
fmt.Printf("Receive res2:%v\n", res2)
res3, err := conn.Receive()
fmt.Printf("Receive res3:%s\n", res3)
}
输出结果:
Receive res1:<nil>
Receive res2:1
Receive res3:%!s(<nil>)
10. Redis 的事务
MULTI、EXEC、 DISCARD 和 WATCH 方法是构成 Redis 事务的基础。使用 Go 语言对 Redis 进行事务操作的本质也是使用这些命令。
- MULTI:开启事务;
- EXEC:执行事务;
- DISCARD:取消事务;
- WATCH:监视事务中的键变化,一旦有改变则取消事务
示例代码:
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("connect redis error :", err)
return
}
defer conn.Close()
conn.Send("MULTI")
conn.Send("INCR", "foo")
conn.Send("INCR", "bar")
r, err := conn.Do("EXEC")
fmt.Println(r)
}