redis go
记录在使用go-redis中的代码细节以及个人理解。
介绍
go-redis是支持Redis Server和Redis Cluster的Golang客户端,中文官方链接:https://redis.uptrace.dev/zh/
Quick Start( .go)
redis已在"localhost:6379"
启动且遵循默认配置。通过以下代码块建立连接,返回rdb
句柄。
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
而通过NewClient
返回的rdb相关结构如下:
type Client struct {
*baseClient
cmdable
hooksMixin
}
type baseClient struct {
opt *Options
connPool pool.Pooler
onClose func() error // hook called when client is closed
}
/ Options keeps the settings to set up redis connection.
type Options struct {
Network string // The network type, either tcp or unix.// Default is tcp.
Addr string// host:port address.
ClientName string// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
...
}
主要涉及options中的连接配置,如地址、密码、连接数,当然也支持拼接连接方式,只不过扩展性有限。
opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
if err != nil {
panic(err)
}
rdb := redis.NewClient(opt)
类似于redis-cli
提供的GET/SET/HMSET/HGET
等方法,go-redis
也提供类似方法,如下分别增加并获取string/hash
数据:
var ctx = context.Background() //empty context with nothing
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
panic(err)
}
//val, err := rdb.Do(ctx, "get", "key").Text() 该方法与下行等效
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
panic(err)
}
fmt.Println("key 's value:", val)
//输出:key 's value: value
val2, err := rdb.Get(ctx, "key2").Result()
if err == redis.Nil {
fmt.Println("key2 does not exist")
} else if err != nil {
panic(err)
} else {
fmt.Println("key2's value:", val2)
}
//输出:key2 does not exist
err3 := rdb.HSet(ctx, "T1Y", "field1", "T", "field2", "Y").Err()
if err3 != nil {
panic(err)
} else {
fmt.Println("Hash insert OK")
}
val3, err := rdb.HGet(ctx, "T1Y", "field1").Result()
//val4, err := rdb.HGetAll(ctx, "T1Y").Result()
if err != nil {
fmt.Println("err accur:", err)
} else {
fmt.Println(val3)
}
//输出:Hello
//GetALL可以输出map[field1:Hello field2:World]
特别的,也可使用.Do达到类似接近使用命令行cmd操作redis的效果,如欲获取“key”对应的value值,可以如下表达:
//等效写法/等效于 val, err := rdb.Get(ctx, "key").Result()
val, err := rdb.Do(ctx, "get", "key").Text()
此外内置格式转换,看起来还是非常灵活的。
性能分析
redis因其高qps为主要特征,因此尝试以go-redis编写一个测试程序,具体代码如下:
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/go-redis/redis/v8"
)
var (
Pool *redis.Client //连接池
oSynWait sync.WaitGroup //互斥锁
)
const (
OnMaxRun = 10000 //单线程执行命令数
AllMaxRun = 50 //并发数
TimeFormat = "2006-01-02 15:04:05.99" //时间格式
Type = "set" //指令类型
)
func main() {
fmt.Println("---开始redis压力测试---")
GetPool()
OldTime := time.Now()
fmt.Println("开始", OldTime.Format(TimeFormat))
oSynWait.Add(AllMaxRun)
for i := 0; i < AllMaxRun; i++ {
go ReadWriteInfo()
}
oSynWait.Wait()
EndTime := time.Now()
fmt.Println("结束", EndTime.Format(TimeFormat))
fmt.Printf("并发数: %d ;总数据量:%d ;耗时: %.2fs ;\n类型:%s ;key:1;value:1;\nqps:%.0f\n",
AllMaxRun, AllMaxRun*OnMaxRun, EndTime.Sub(OldTime).Seconds(), Type, AllMaxRun*OnMaxRun/time.Since(OldTime).Seconds())
fmt.Println("---测试结束-")
}
// 取得一个连接池
func GetPool() {
Pool = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
PoolSize: AllMaxRun, //默认情况下, go-redis 连接池大小为 runtime.GOMAXPROCS * 10 // runtime.GOMAXPROCS(1)
})
}
// 存放数据
func ReadWriteInfo() {
ctx := context.TODO()
defer oSynWait.Done()
for i := 0; i < OnMaxRun; i++ {
err := Pool.Set(ctx, "1", "1", 0).Err()
if err != nil {
fmt.Println("存放数据失败", err)
return
}
// _, err = Pool.Get(ctx, "1").Result()
// if err != nil {
// fmt.Println("取出数据失败", err)
// return
// }
}
}
输出内容如下:
-------------------------开始redis压力测试-------------------------
开始 2023-07-20 20:42:26.25
结束 2023-07-20 20:42:37.28
并发数: 50 ;总数据量:500000 ;耗时: 11.03s ;
类型:set ;key:1;value:1;
qps:93474
-----------------------------测试结束-----------------------------
仅存数据时qps能达到10w,若增加取出数据则约为5w,还是比较理想的情况。
当修改PoolSize: AllMaxRun,
指定的连接池数量时,如改为1qps约为1w,此时频繁地建立连接(网络因素)严重影响其性能。而redis性能另一方面是内存大小了。
MySQL
尝试去测试与Mysql的联动(未完待续,2023年7月21日)