8.1 GO通过redigo读写Redis
8.1.1 以go mod方式引入redigo包
step1. 编写redis.conf文件
(base) yanglei@yuanhong section8-1 % cat redis.conf
# 指定default用户的密码 允许执行所有命令 允许访问所有key 允许访问所有频道
user default on >default_password ~* &* +@all
# 指定用户名和密码 允许执行所有命令 允许访问所有key 允许访问所有频道
user redis_user on >redis_password ~* &* +@all
# 指定端口
port 6379
step2. 启动redis-server
(base) yanglei@yuanhong ~ % redis-server /Users/yanglei/Desktop/StudyRedisBaseOnDocker/conf/chapter8/section8-1/redis.conf
step3. 使用go mod初始化项目
(base) yanglei@yuanhong operateRedis % pwd
/Users/yanglei/Desktop/StudyRedisBaseOnDocker/code/chapter8/section8-1/operateRedis
(base) yanglei@yuanhong operateRedis % go mod init operateRedis
go: creating new go.mod: module operateRedis
step4. 引入redigo包
编写main.go
如下:
/Users/yanglei/Desktop/StudyRedisBaseOnDocker/code/chapter8/section8-1/operateRedis/main.go
:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
)
func main() {
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
log.Fatalf("Could not connect: %v\n", err)
} else {
log.Println("Connected")
}
defer conn.Close()
}
执行go mod tidy
step5. 运行
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 13:48:03 Connected
step6. 认证并确认连接
PING
:该命令用于测试与Redis服务器的连接是否仍然活跃.若链接活跃则Redis服务器会回复PONG
修改main.go
如下:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
)
func main() {
// 连接到Redis
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
log.Fatalf("Could not connect: %v\n", err)
return
}
// 认证
_, err = conn.Do("AUTH", "redis_user", "redis_password")
if err != nil {
log.Fatalf("Could not authenticate: %v\n", err)
return
}
// 探活
reply, err := redis.String(conn.Do("PING"))
if err != nil {
log.Fatalf("Could not ping: %v\n", err)
return
}
log.Printf("PING Response = %s\n", reply)
defer conn.Close()
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:00:22 PING Response = PONG
step7. 封装连接
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
└── main.go
1 directory, 4 files
conn/redis.go
:
package conn
import (
"github.com/gomodule/redigo/redis"
"log"
)
type Conf struct {
NetWork string // NetWork 网络类型
Address string // Address Redis地址 格式: ip:port
User string // User 用户名
Password string // Password 密码
}
// NewConn 根据给定的配置 创建Redis连接
func NewConn(conf *Conf) (conn redis.Conn, err error) {
// 连接到Redis
conn, err = redis.Dial(conf.NetWork, conf.Address)
if err != nil {
log.Fatalf("Could not connect: %v\n", err)
return
}
if conf.User != "" || conf.Password != "" {
err = auth(conf, conn)
if err != nil {
log.Fatalf("Could not authenticate: %v\n", err)
return
}
}
err = ping(conn)
if err != nil {
log.Fatalf("Could not ping: %v\n", err)
return
}
return
}
// auth 根据给定的配置和连接 进行认证
func auth(conf *Conf, conn redis.Conn) (err error) {
_, err = conn.Do("AUTH", conf.User, conf.Password)
return err
}
// ping 根据给定的连接 进行探活
func ping(conn redis.Conn) (err error) {
_, err = conn.Do("PING")
return err
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
_, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
log.Print("Connect to Redis success!")
}
step8. 测试
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:33:15 Connect to Redis success!
8.1.2 通过redigo读写Redis字符串
写入字符串
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
└── set.go
2 directories, 5 files
operate/set.go
:
package operate
import "github.com/gomodule/redigo/redis"
func Set(conn redis.Conn, key string, value string) (reply string, err error) {
return redis.String(conn.Do("SET", key, value))
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
reply, err := operate.Set(redisConn, "name", "Peter")
if err != nil {
panic(err)
}
log.Printf("Set command reply: %s\n", reply)
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:44:31 Set command reply: OK
读取字符串
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── get.go
└── set.go
2 directories, 6 files
operate/get.go
:
package operate
import "github.com/gomodule/redigo/redis"
func Get(conn redis.Conn, key string) (reply string, err error) {
return redis.String(conn.Do("GET", key))
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
reply, err := operate.Get(redisConn, "name")
if err != nil {
panic(err)
}
log.Printf("Get command reply: %s\n", reply)
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:49:10 Get command reply: Peter
8.1.3 操作各种Redis命令
DEL
指令
DEL
指令工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── get.go
└── set.go
2 directories, 7 files
operate/del.go
:
package operate
import "github.com/gomodule/redigo/redis"
func Del(conn redis.Conn, key string) (reply int, err error) {
return redis.Int(conn.Do("DEL", key))
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
reply, err := operate.Del(redisConn, "name")
if err != nil {
panic(err)
}
log.Printf("Del command reply: %d\n", reply)
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:53:03 Del command reply: 1
KEYS
指令
KEYS
指令工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── get.go
├── keys.go
└── set.go
2 directories, 8 files
operate/keys.go
:
package operate
import "github.com/gomodule/redigo/redis"
func Keys(conn redis.Conn, pattern string) (reply []string, err error) {
return redis.Strings(conn.Do("KEYS", pattern))
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
replies, err := operate.Keys(redisConn, "*a*")
if err != nil {
panic(err)
}
for _, reply := range replies {
log.Printf("Keys command reply: %s\n", reply)
}
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 14:57:57 Keys command reply: repeat
2023/08/18 14:57:57 Keys command reply: rename
注:此处这2个key是我通过redis-cli
事先添加进去的
EXISTS
指令
EXISTS
指令工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── exists.go
├── get.go
├── keys.go
└── set.go
2 directories, 9 files
operate/exists.go
:
package operate
import "github.com/gomodule/redigo/redis"
func Exists(conn redis.Conn, key string) (reply bool, err error) {
return redis.Bool(conn.Do("EXISTS", key))
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
reply, err := operate.Exists(redisConn, "name")
if err != nil {
panic(err)
}
log.Printf("name exists: %v\n", reply)
reply, err = operate.Exists(redisConn, "rename")
if err != nil {
panic(err)
}
log.Printf("rename exists: %v\n", reply)
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/18 15:46:27 name exists: false
2023/08/18 15:46:27 rename exists: true
也能通过该包使用其他Redis命令
8.1.4 以事务的方式操作Redis
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── exists.go
├── get.go
├── keys.go
├── set.go
└── transaction.go
2 directories, 10 files
operate/transaction.go
:
package operate
import (
"errors"
"github.com/gomodule/redigo/redis"
)
func Transaction(conn redis.Conn) (replies []string, err error) {
// 1. Watch
_, err = redis.String(conn.Do("WATCH", "counter"))
if err != nil {
return nil, err
}
// 若counter的值大于10 则不执行事务
// 此处假定10为counter的原值
counter, _ := redis.Int(conn.Do("GET", "counter"))
if counter > 10 {
conn.Do("UNWATCH")
return nil, errors.New("counter value is too high. Not continuing the transaction")
}
// 2. Multi
err = conn.Send("MULTI")
if err != nil {
return nil, err
}
// 3. Exec
err = conn.Send("SET", "transaction_key_1", "transaction_value_1")
if err != nil {
conn.Do("DISCARD")
return nil, err
}
err = conn.Send("SET", "transaction_key_2", "transaction_value_2")
if err != nil {
conn.Do("DISCARD")
return nil, err
}
replies, err = redis.Strings(conn.Do("EXEC"))
if err != nil {
conn.Do("DISCARD")
return nil, err
}
return replies, nil
}
main.go
:
package main
import (
"log"
"operateRedis/conn"
"operateRedis/operate"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
redisConn, err := conn.NewConn(&conf)
if err != nil {
panic(err)
}
replies, err := operate.Transaction(redisConn)
if err != nil {
panic(err)
}
for _, reply := range replies {
log.Printf("Transaction reply: %v\n", reply)
}
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/21 16:49:20 Transaction reply: OK
2023/08/21 16:49:20 Transaction reply: OK
8.1.5 redis连接池
如果在项目中有多个GO客户端需要连接并操作Redis对象,那么每一次访问Redis都需要经历创建连接 -> 打开Redis并操作 -> 释放连接
的步骤.连接和关闭数据库的操作比较浪费资源,如果频繁操作,会影响Redis甚至整个系统的性能,所以这种场景可以用redigo
连接池来管理Redis的连接.
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ ├── pool.go
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── exists.go
├── get.go
├── keys.go
├── set.go
└── transaction.go
2 directories, 11 files
其中,conn/pool.go
如下:
package conn
import (
"github.com/gomodule/redigo/redis"
"time"
)
type PoolConf struct {
// MaxIdle 最大空闲连接数
MaxIdle int
// MaxActive 最大连接数,0表示无限制
MaxActive int
// IdleTimeout 空闲连接超时时间
IdleTimeout time.Duration
// Conf Redis连接配置
Conf Conf
}
var Pool *redis.Pool
func NewPool(poolConf PoolConf) {
Pool = &redis.Pool{
MaxIdle: poolConf.MaxIdle,
MaxActive: poolConf.MaxActive,
IdleTimeout: poolConf.IdleTimeout,
Dial: func() (redis.Conn, error) {
return redis.Dial(poolConf.Conf.NetWork, poolConf.Conf.Address)
},
}
}
// GetConnFromPool 从连接池中获取一个经过认证后的连接
func GetConnFromPool(poolConf PoolConf) (conn redis.Conn, err error) {
conn = Pool.Get()
err = auth(&poolConf.Conf, conn)
if err != nil {
return nil, err
}
return conn, nil
}
// CloseConnToPool 将该连接返回到连接池中
func CloseConnToPool(conn redis.Conn) error {
// 关闭一个从连接池中取出的连接时,使用conn.Close()方法
// 并不会让该连接真正的关闭,而是将该连接返回到连接池中,以便后续复用
return conn.Close()
}
main.go
:
package main
import (
"fmt"
"log"
"operateRedis/conn"
"operateRedis/operate"
"time"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
poolConf := conn.PoolConf{
MaxIdle: 10,
MaxActive: 100,
IdleTimeout: time.Hour,
Conf: conf,
}
// 初始化连接池
conn.NewPool(poolConf)
// 从连接池中获取一个连接
redisConn, err := conn.GetConnFromPool(poolConf)
if err != nil {
log.Fatalf("get conn from pool error: %v\n", err)
}
// 将该连接返回到连接池中
defer conn.CloseConnToPool(redisConn)
// 使用该连接进行操作
reply, err := operate.Set(redisConn, "name", "Peter")
if err != nil {
log.Fatalf("set error: %v\n", err)
}
fmt.Printf("set reply: %v\n", reply)
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
set reply: OK
8.1.6 用管道的方式提升操作性能
在单个客户端里,如果要读写大量数据,那么可以采用管道(pipeline)的方式.比如要一次性地执行20次的读写,那么每条命令都需要发送到Redis服务器,而每条命令的执行结果都需要返回给客户端.如果采用管道的方式,那么这20条命令会以批量的方式一次性地发送到服务器,而结果也会一次性地返回到客户端.
换言之,在大数据操作的场景里,通过管道的方式能大量节省"传输命令和结果的时间".
工程结构如下:
(base) yanglei@yuanhong operateRedis % tree ./
./
├── conn
│ ├── pool.go
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
└── operate
├── del.go
├── exists.go
├── get.go
├── keys.go
├── pipeline.go
├── set.go
└── transaction.go
2 directories, 12 files
其中,operate/pipeline.go
如下:
package operate
import "github.com/gomodule/redigo/redis"
// Pipeline 以管道方式一次性发送多个命令
func Pipeline(conn redis.Conn) (operateNum int, err error) {
operateNum = 0
err = conn.Send("SET", "name", "Peter")
if err != nil {
return 0, err
}
operateNum++
err = conn.Send("GET", "name")
if err != nil {
return 0, err
}
operateNum++
err = conn.Send("SET", "age", "18")
if err != nil {
return 0, err
}
operateNum++
err = conn.Send("GET", "age")
if err != nil {
return 0, err
}
operateNum++
// 将缓冲区的命令写入到连接中
err = conn.Flush()
if err != nil {
return 0, err
}
return operateNum, nil
}
main.go
如下:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
"operateRedis/conn"
"operateRedis/operate"
"time"
)
func main() {
conf := conn.Conf{
NetWork: "tcp",
Address: "localhost:6379",
User: "redis_user",
Password: "redis_password",
}
poolConf := conn.PoolConf{
MaxIdle: 10,
MaxActive: 100,
IdleTimeout: time.Hour,
Conf: conf,
}
// 初始化连接池
conn.NewPool(poolConf)
// 从连接池中获取一个连接
redisConn, err := conn.GetConnFromPool(poolConf)
if err != nil {
log.Fatalf("get conn from pool error: %v\n", err)
}
// 将该连接返回到连接池中
defer conn.CloseConnToPool(redisConn)
// 使用该连接进行操作
operateNum, err := operate.Pipeline(redisConn)
if err != nil {
log.Fatalf("pipeline error: %v\n", err)
}
// 读取响应
for i := 0; i < operateNum; i++ {
reply, err := redis.String(redisConn.Receive())
if err != nil {
log.Fatalf("receive error: %v\n", err)
}
log.Printf("reply: %v\n", reply)
}
}
运行结果:
(base) yanglei@yuanhong operateRedis % go run main.go
2023/08/21 16:43:47 reply: OK
2023/08/21 16:43:47 reply: Peter
2023/08/21 16:43:47 reply: OK
2023/08/21 16:43:47 reply: 18
Last updated