Redis连接池Wireshark抓包分析
1.文章背景
因线上redis连接池出现i/o timeout,遂想通过抓包分析redis连接池的各配置参数。
连接相对于其他对象,创建成本较高,资源也有限。如果没有连接池,在高并发场景下,连接关闭又新建,很快就会因为过多的TIME_WAIT(连接主动关闭方)导致无法创建更多连接了,程序被压死。
本文主要确认:
1.redis连接池是否复用
2.redis连接池超时分析
3.redis连接池直接替换(加锁)
4.redis连接池直接替换(不加锁公司现有模式)
5.redis连接池客户端空闲超时
6.redis服务器端空闲超时
7.redis连接池推荐使用方式
2.实验说明:
redis服务器:
工具版本:
golang版本: 1.17
go-redis版本: github.com/go-redis/redis v6.15.9+incompatible
wireshark版本: Version 3.4.6 (v3.4.6-0-g6357ac1405b8)
redis客户端参数
type Options struct {
// The network type, either tcp or unix.
// Default is tcp.
Network string
// host:port address.
Addr string
// Dialer creates new network connection and has priority over
// Network and Addr options.
Dialer func() (net.Conn, error)
// Hook that is called when new connection is established.
OnConnect func(*Conn) error
// Optional password. Must match the password specified in the
// requirepass server configuration option.
Password string
// Database to be selected after connecting to the server.
DB int
// 命令执行失败时,最多重试多少次,默认为0即不重试
// Maximum number of retries before giving up.
// Default is to not retry failed commands.
MaxRetries int
//每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔
// Minimum backoff between each retry.
// Default is 8 milliseconds; -1 disables backoff.
MinRetryBackoff time.Duration
//每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔
// Maximum backoff between each retry.
// Default is 512 milliseconds; -1 disables backoff.
MaxRetryBackoff time.Duration
//连接建立超时时间,默认5秒。
// Dial timeout for establishing new connections.
// Default is 5 seconds.
DialTimeout time.Duration
//读超时,默认3秒, -1表示取消读超时
// Timeout for socket reads. If reached, commands will fail
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
// Default is 3 seconds.
ReadTimeout time.Duration
//写超时,默认等于读超时
// Timeout for socket writes. If reached, commands will fail
// with a timeout instead of blocking.
// Default is ReadTimeout.
WriteTimeout time.Duration
//默认连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU
// Maximum number of socket connections.
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
PoolSize int
// Minimum number of idle connections which is useful when establishing
// new connection is slow.
//在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;
MinIdleConns int
//连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接
// Connection age at which client retires (closes) the connection.
// Default is to not close aged connections.
MaxConnAge time.Duration
//当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒
// Amount of time client waits for connection if all connections
// are busy before returning an error.
// Default is ReadTimeout + 1 second.
PoolTimeout time.Duration
//闲置超时,默认5分钟,-1表示取消闲置超时检查
// Amount of time after which client closes idle connections.
// Should be less than server's timeout.
// Default is 5 minutes. -1 disables idle timeout check.
IdleTimeout time.Duration
//闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。
// Frequency of idle checks made by idle connections reaper.
// Default is 1 minute. -1 disables idle connections reaper,
// but idle connections are still discarded by the client
// if IdleTimeout is set.
IdleCheckFrequency time.Duration
// Enables read only queries on slave nodes.
readOnly bool
// TLS Config to use. When set TLS will be negotiated.
TLSConfig *tls.Config
}
3.wireshark抓包设置
设置只抓redis包
host 10.1.1.245 and port 6979
4.抓包分析-连接池复用
4.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println("==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
stats := redisHandle.PoolStats()
if err != nil {
//获取当前连接池状态
fmt.Println("redis connections: TotalConns= ", stats.TotalConns, " Timeouts= ", stats.Timeouts, " IdleConns= ", stats.IdleConns)
fmt.Println("不能ping通", time.Now(), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
for i := 0; i < 10; i++ {
getRedis := GetRedis()
getRedis.Set("name", "hello01", -1)
//time.Sleep(3 * time.Second)
s := getRedis.Get("name").String()
fmt.Println(" 获取redis的key: ", s)
}
}
4.2 查看进程运行状态
4.3 查看wireshark抓包情况
详见包redisonepool.pcapng
4.4 抓包分析
总共会建立两个连接:
1.客户端初始的时候会建立两个连接 (端口51588)和(端口51587)
(其中一个包是ping生成的,一个是建立连接使用的)
2.因poolsize为1,所以会关闭一个连接。
3.后续的连接会复用一个连接(端口515887)
5.抓包分析-连接池超时
5.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/spf13/cast"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println("==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
stats := redisHandle.PoolStats()
if err != nil {
//获取当前连接池状态
fmt.Println("redis connections: TotalConns= ", stats.TotalConns, " Timeouts= ", stats.Timeouts, " IdleConns= ", stats.IdleConns)
fmt.Println("不能ping通", time.Now(), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
for i := 0; i < 10; i++ {
wait.Add(1)
vt := i
go func() {
defer wait.Done()
//随机延迟,需要时为了让go并发争用
s :=rand.Intn(15)
time.Sleep(time.Second*time.Duration(s))
//getRedis := GetRedis()
strings := []string{cast.ToString(vt)}
//设置lua脚本主要是为了使连接执行很长时间,占用连接,从而显示连接池超时
t2 := time.Now()
eval := redisHandle.Eval(scriptStr, strings, len(strings))
result, err := eval.Result()
fmt.Printf("%v, 结果:%v,随机时间是:%v,lua执行耗时:%v ,err: %v\n",time.Now(),result, s,time.Since(t2), err)
}()
}
wait.Wait()
}
5.2 查看进程运行状态
5.3 查看wireshark抓包情况
详见包redispooltimeout.pcapng
5.4 抓包分析
总共会建立两个连接:
1.客户端初始的时候会建立两个连接 (端口61445和61446)
2.其中一个用于连接池(端口61445),因为poolsize为1,另一个连接销毁(端口61446)。
3.后续的连接会复用这个连接池(端口61445)
4.因为连接没有设置重试,所以直接超时就报错,此处为连接池错误。
此处引申一下:
此处使用lua脚本是为了让redis处理时间占用很长时间,此时连接池维持的一个连接被占用,而且由于没有设置超时重试,所以会直接报错
6.抓包分析-连接池覆盖(此处加锁)
6.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/spf13/cast"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var lock sync.Mutex
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
//保证同一时刻只能拿到一个连接,如果连接池慢,则重新创建,如果连接池没有慢则直接返回
lock.Lock()
defer lock.Unlock()
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println("==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
//ReadTimeout:
//PoolTimeout: 7 * time.Second, //获取连接池超时时间
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
stats := redisHandle.PoolStats()
if err != nil {
//获取当前连接池状态
fmt.Println("redis connections: TotalConns= ", stats.TotalConns, " Timeouts= ", stats.Timeouts, " IdleConns= ", stats.IdleConns)
fmt.Println("不能ping通", time.Now(), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
for i := 0; i < 15; i++ {
wait.Add(1)
vt := i
//go func() {
go func() {
defer wait.Done()
//随机延迟,需要时为了保持go
//s :=rand.Intn(5)
s :=rand.Intn(3)
time.Sleep(time.Second*time.Duration(s))
getRedis := GetRedis()
strings := []string{cast.ToString(vt)}
//设置lua脚本主要是为了使连接执行很长时间,占用连接
st := time.Now()
eval := getRedis.Eval(scriptStr, strings, len(strings))
result, err := eval.Result()
fmt.Printf(" 结果:%v,耗时:%v ,err: %v\n", result,time.Since(st), err)
}()
}
wait.Wait()
}
6.2 查看进程运行状态
6.3 查看wireshark抓包情况
详见包redispoolreplace.pcapng
6.4 抓包分析
总共会建立两次连接池,四次连接:
1.客户端初始的时候会建立两个连接 (端口52541和52542)
2.其中一个用于连接池(端口52542),因为poolsize为1,另一个连接销毁(端口52541)。
3.后续的连接会复用这个连接池(端口52542)
4.因为redis计算时间较长,导致只有一个连接池的连接迟迟不能释放,所以下一个连接检测到连接池报错,会重新建立一个新的连接池。
5.客户端初始的时候会重新建立两个连接 (端口52547和52548)
6.其中一个用于连接池(端口52547),因为poolsize为1,另一个连接销毁(端口52548)。
7.后续的连接会复用这个连接池(端口52547)
8.请注意此时。旧的连接没有旧的连接池没有被杀死,可以正常处理数据,等到程序关闭后销毁(内核管理)。
此处引申一下:
此处使用lua脚本是为了让redis处理时间占用很长时间,此时连接池维持的一个连接被占用,其他进程getredis的时候,会检测到获取线程池错误,而重新获取一个连接。
7.抓包分析-连接池覆盖(没有加锁-公司现在模式)
7.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/spf13/cast"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var lock sync.Mutex
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
fmt.Println(time.Now(),"开始时间")
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
//保证同一时刻只能拿到一个连接,如果连接池慢,则重新创建,如果连接池没有慢则直接返回
//lock.Lock()
//defer lock.Unlock()
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println(time.Now(),"==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
//ReadTimeout:
//PoolTimeout: 7 * time.Second, //获取连接池超时时间
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
if err != nil {
//获取当前连接池状态
fmt.Println(time.Now(),"连接池状态错误", time.Since(t), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
for i := 0; i < 15; i++ {
wait.Add(1)
vt := i
//go func() {
go func() {
defer wait.Done()
//随机延迟,需要时为了保持go
//获取计时
t := time.Now()
s :=rand.Intn(3)
time.Sleep(time.Second*time.Duration(s))
redisHandle = GetRedis()
strings := []string{cast.ToString(vt)}
//设置lua脚本主要是为了使连接执行很长时间,占用连接池,从而需要重新创建连接池
st := time.Since(t)
//设置执行lua开始时间点
t2 := time.Now()
eval := redisHandle.Eval(scriptStr, strings, len(strings))
result, err := eval.Result()
fmt.Printf("%v, 结果:%v,随机时间是:%v,建立连接到获取结果耗时%v,lua执行耗时:%v ,err: %v\n",time.Now(),result, s,st,time.Since(t2), err)
}()
}
wait.Wait()
}
7.2 查看进程运行状态
7.3 查看wireshark抓包情况
详见包redispoolreplacewithnolockclient.pcapng,redispoolreplacewithnolockserver.pcapng
7.4 抓包分析
总共会建立7次连接池,十四次连接:
1.客户端初始的时候会建立两个连接
2.其中一个用于连接池,因为poolsize为1,另一个连接销毁(端口52541)。
重点
3.因为getredis没有加锁,等到真正执行命令时,不是原子操作,所以导致真正执行eval时,获取执行状态异常,所以报错了。
什么意思呢,就是因为没有锁redishanle可能是同时获取的,同时PoolTimeout=ReadTimeout+1 等于4s,如果不用随机数(下图),那么最多执行四到五次,而不会重新创建连接池。因为并发状态下,刚开始时没有阻塞的斜程获取的redishandle值都一样,不会报错。
4.而如果加入随机数,那么可能有一定概率是能获取到错误值的,这个值可能是redishandle执行时,或者是重新生成redishandle时。
4.最后我想说,这里触发的panic确实是客户端导致的,和服务端无关,但是我没有判断出世为什么。
此处引申一下:
此处使用lua脚本是为了让redis处理时间占用很长时间,此时连接池维持的一个连接被占用,其他进程getredis的时候,会检测到获取线程池错误,而重新获取一个连接。
8.抓包分析-客户端超时
8.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var lock sync.Mutex
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
fmt.Println(time.Now(),"开始时间")
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
//保证同一时刻只能拿到一个连接,如果连接池慢,则重新创建,如果连接池没有慢则直接返回
//lock.Lock()
//defer lock.Unlock()
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println(time.Now(),"==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
//ReadTimeout:
//PoolTimeout: 7 * time.Second, //获取连接池超时时间
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
if err != nil {
//获取当前连接池状态
fmt.Println(time.Now(),"连接池状态错误", time.Since(t), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
for i := 0; i < 2; i++ {
redisHandle.Set("name", "hello01", -1)
//time.Sleep(3 * time.Second)
s := redisHandle.Get("name").String()
fmt.Println(" 获取redis的key: ", s)
time.Sleep(25*time.Second)
}
}
8.2 查看进程运行状态
8.3 查看wireshark抓包情况
详见包redisclientout.pcapng
8.4 抓包分析
总共会建立四个连接:
1.客户端初始的时候会建立两个连接 (端口51891)和(端口51892)
(其中一个包是ping生成的,一个是建立连接使用的)
2.因poolsize为1,所以会关闭一个连接(端口51892)
3.后续的连接客户端会超时。客户端的闲时检查事件默认时1分钟,所以此处不会自动检查
4.等25秒后,连接建立的时候会重新建立两个连接(这里我感觉是个bug)
5.等到1分钟的空闲连接检查,其中一个连接会被干掉。
6.等70十分钟后客户端主动断开连接。
9.抓包分析-服务端超时
9.1 只设置一个连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var lock sync.Mutex
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 100000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
fmt.Println(time.Now(), "开始时间")
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
//如果存在全局连接池就进行复用
//如果全局连接池,复用超时,则新创建连接池进行覆盖
//比如只存在一个连接池,而且被长期占用
//保证同一时刻只能拿到一个连接,如果连接池慢,则重新创建,如果连接池没有慢则直接返回
//lock.Lock()
//defer lock.Unlock()
if redisHandle != nil {
_, err := redisHandle.Ping().Result()
//如果没有报错,直接复用
if err == nil {
return redisHandle
}
fmt.Println(time.Now(), "==========重新创建全局连接池===============", err)
}
//创建连接时计时开始,用于计算超时时间对比
t := time.Now()
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1, //连接池最大为1
MinIdleConns: 1, //最小空闲连接池为1
IdleTimeout: 20 * time.Second, //客户端空闲超时为20s
IdleCheckFrequency: 80*time.Second,
//ReadTimeout:
//PoolTimeout: 7 * time.Second, //获取连接池超时时间
})
//重新测试新的连接池是否可用
//如果不可用则直接panic
_, err := redisHandle.Ping().Result()
if err != nil {
//获取当前连接池状态
fmt.Println(time.Now(), "连接池状态错误", time.Since(t), time.Since(t), err.Error())
panic(err)
}
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
redisHandle.Set("name", "hello01", -1)
//time.Sleep(3 * time.Second)
s := redisHandle.Get("name").String()
fmt.Println(" 获取redis的key: ", s)
time.Sleep(70 * time.Second)
redisHandle.Set("name", "hello01", -1)
//time.Sleep(3 * time.Second)
s = redisHandle.Get("name").String()
fmt.Println(" 获取redis的key: ", s)
time.Sleep(10 * time.Second)
}
9.2 查看进程运行状态
9.3 查看wireshark抓包情况
详见包redisclientout.pcapng
9.4 抓包分析
前提条件:
已知服务器的超时时间为: 60s IP:10.1.1.245
总共会建立四个连接:
1.客户端初始的时候会建立两个连接 (端口50496)和(端口50495)
(其中一个包是ping生成的,一个是建立连接使用的)
2.因poolsize为1,所以会关闭一个连接(端口50495)。
3.后续的连接客户端会超时。客户端的空闲检查时间默认是1分钟,可能会导致客户端先发生超时重连,所以要设置IdleCheckFrequency > 60s
4.等60秒后,redis服务端先发生断开。
5.重新建立一个两个连接
6.查询完毕后先关闭一个连接。
6.等80十分钟后客户端结束后主动断开连接
10.抓包分析-连接池复用(综合汇总)
10.1 设置连接池并运行
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/spf13/cast"
"math/rand"
"sync"
"time"
)
var db = 0
var addr = "10.1.1.245:6979"
var pwd = "xxxxxxxxxxxxxx"
var wait sync.WaitGroup
var lock sync.Mutex
var redisHandle *redis.Client
var scriptStr = `
local a = 0
while (a < 10000000 ) do
a = a + 1
end
return KEYS[1]
`
//初始化一个连接池
func init() {
fmt.Println(time.Now(),"开始时间")
redisHandle = GetRedis()
}
func GetRedis() *redis.Client {
redisHandle = redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
DialTimeout: 3 * time.Second, //设置3秒超时
PoolSize: 1000, //连接池最大为1000
MinIdleConns: 10, //最小空闲连接池为10
MaxRetries: 3, //设置连接池超时重试为3次
IdleTimeout: 3 * time.Second, //客户端空闲超时为20s
//ReadTimeout:
//PoolTimeout: 7 * time.Second, //获取连接池超时时间
})
return redisHandle
}
func main() {
//设置随机种子
rand.Seed(time.Now().Unix())
//设置超时
//查看连接数
for i := 0; i < 15; i++ {
wait.Add(1)
vt := i
//go func() {
go func() {
defer wait.Done()
//随机延迟,需要时为了保持go
//获取计时
s :=rand.Intn(3)
time.Sleep(time.Second*time.Duration(s))
strings := []string{cast.ToString(vt)}
//设置lua脚本主要是为了使连接执行很长时间,占用连接池,从而需要重新创建连接池
//设置执行lua开始时间点
t2 := time.Now()
eval := redisHandle.Eval(scriptStr, strings, len(strings))
result, err := eval.Result()
fmt.Printf("%v, 结果:%v,随机时间是:%v,lua执行耗时:%v ,err: %v\n",time.Now(),result, s,time.Since(t2), err)
}()
}
wait.Wait()
}
10.2 查看进程运行状态
](http
10.3 查看wireshark抓包情况
详见包redispoolend.pcapng
10.4 抓包分析
1.复用连接池
2.定义重试次数MaxRetries: 3,
3.增大空闲连接数IdleTimeout: 3 * time.Second,
4.增大最大空闲连接池 PoolSize: 1000,