# 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`:

```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`如下:

```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`:

```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`:

```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`:

```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`:

```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`:

```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`:

```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`指令

工程结构如下:

```
(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`:

```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`:

```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`指令

工程结构如下:

```
(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`:

```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`:

```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`指令

工程结构如下:

```
(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`:

```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`:

```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`:

```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`:

```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`如下:

```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`:

```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`如下:

```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`如下:

```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
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://redis.sai.show/di-8-zhang-go-zheng-he-mysql-yu-redis/8.1-go-tong-guo-redigo-du-xie-redis.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
