[go-redis]客户端的创建与配置说明
创建redis client
使用go-redis库进行创建redis客户端比较简单,只需要调用redis.NewClient接口创建一个客户端
redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379",Password: "",DB: 0,
})
NewClient接口只接收一个参数redis.Options,在Options里面存放了所有创建Client需要的参数,我们来具体看下各个参数字段的内容以及使用方式,这些字段包括但不限于:
网络连接相关
-
Network:
- 类型:
string
- 描述:网络类型,可以是
tcp
或unix
。默认值为tcp
。
- 类型:
-
Addr:
- 类型:
string
- 描述:Redis 服务器的地址,格式为
host:port
。
- 类型:
-
Dialer:
- 类型:
func(ctx context.Context, network, addr string) (net.Conn, error)
- 描述:自定义的拨号函数,用于创建网络连接。如果设置了
Dialer
,则Network
和Addr
的设置将失效。
- 类型:
-
OnConnect:
- 类型:
func(ctx context.Context, cn *Conn) error
- 描述:连接建立成功时的回调函数。
- 类型:
-
DialTimeout:
- 类型:
time.Duration
- 描述:拨号超时时间,默认为 5 秒。
- 类型:
-
ReadTimeout:
- 类型:
time.Duration
- 描述:同步等待回复的超时时间。默认为 3 秒,
-1
表示阻塞等待,-2
表示完全禁用SetReadDeadline
调用。
- 类型:
-
WriteTimeout:
- 类型:
time.Duration
- 描述:写操作的超时时间。默认为 3 秒,
-1
表示阻塞等待,-2
表示完全禁用SetWriteDeadline
调用。
- 类型:
-
ContextTimeoutEnabled:
- 类型:
bool
- 描述:是否尊重
Context
上下文的超时时间。默认为false
。
- 类型:
认证和权限相关
-
ClientName:
- 类型:
string
- 描述:每个连接都会执行
CLIENT SETNAME
命令为每个连接设置客户端名字。
- 类型:
-
Username:
- 类型:
string
- 描述:用于 Redis ACL 系统的身份验证用户名。
- 类型:
-
Password:
- 类型:
string
- 描述:用于 Redis ACL 系统的身份验证密码。
- 类型:
-
CredentialsProvider:
- 类型:
func() (username string, password string)
- 描述:允许动态更改用户名和密码。
- 类型:
-
CredentialsProviderContext:
- 类型:
func(ctx context.Context) (username string, password string, err error)
- 描述:增强版的
CredentialsProvider
,存在时会忽略CredentialsProvider
。
- 类型:
协议和功能相关
-
Protocol:
- 类型:
int
- 描述:使用的协议版本,2 或 3。默认值为 3。
- 类型:
-
UnstableResp3:
- 类型:
bool
- 描述:启用 Redis Search 模块的不稳定模式,并使用 RESP3 协议。
- 类型:
连接池相关
-
PoolFIFO:
- 类型:
bool
- 描述:连接池类型,
true
表示 FIFO 连接池,false
表示 LIFO 连接池。默认为false
。
- 类型:
-
PoolSize:
- 类型:
int
- 描述:连接池中基础套接字连接数量。默认情况下每个可用的 CPU 核心会有 10 个连接。
- 类型:
-
PoolTimeout:
- 类型:
time.Duration
- 描述:当所有连接都忙时,客户端从连接池中获取连接的超时时间。默认为
ReadTimeout + 1
,即 6 秒。
- 类型:
-
MinIdleConns:
- 类型:
int
- 描述:连接池中最小空闲连接数量。默认为 0。
- 类型:
-
MaxIdleConns:
- 类型:
int
- 描述:连接池中最大空闲连接数量。默认为 0。
- 类型:
-
MaxActiveConns:
- 类型:
int
- 描述:最大活跃连接数量。0 表示不设限制。
- 类型:
-
ConnMaxIdleTime:
- 类型:
time.Duration
- 描述:连接最长空闲时间。默认为 30 分钟,
-1
表示禁用空闲超时检查。
- 类型:
-
ConnMaxLifetime:
- 类型:
time.Duration
- 描述:连接可以被重用的最大时间。默认不关闭空闲连接。
- 类型:
重试机制
-
MaxRetries:
- 类型:
int
- 描述:尝试次数,默认为 3 次,
-1
表示关闭重试,0 表示不尝试只执行一次。
- 类型:
-
MinRetryBackoff:
- 类型:
time.Duration
- 描述:每次重试之间的最小重试间隔。默认为 8 毫秒,
-1
表示禁用重试间隔。
- 类型:
-
MaxRetryBackoff:
- 类型:
time.Duration
- 描述:每次重试之间最大时间间隔。默认为 512 毫秒,
-1
表示禁用重试间隔。
- 类型:
其他配置
-
DB:
- 类型:
int
- 描述:选择哪个数据库,支持 0-15。
- 类型:
-
TLSConfig:
- 类型:
*tls.Config
- 描述:使用的 TLS 配置。设置后,TLS 将进行协商。
- 类型:
-
Limiter:
- 类型:
Limiter
- 描述:限制器接口,用于实现断路器或速率限制器。
- 类型:
-
readOnly:
- 类型:
bool
- 描述:在备机(slave/follower)节点上使能只读模式。
- 类型:
-
DisableIndentity:
- 类型:
bool
- 描述:是否禁用客户端设置标识符,默认为
false
。
- 类型:
-
IdentitySuffix:
- 类型:
string
- 描述:为客户端名字添加后缀,默认为空。
- 类型:
type Options struct {// 网络类型,tcp or unix 默认 tcpNetwork string// host:port 地址.Addr string// 每个连接都会执行 CLIENT SETNAME ClientName 命令为每个连接设置客户端名字ClientName string// Dialer 会创建网络连接,并且有限Network和Addr,也就是说一旦创建Network和Addr设置的网络连接将失效Dialer func(ctx context.Context, network, addr string) (net.Conn, error)// Hook 当连接建立成功的时候会回调该函数.OnConnect func(ctx context.Context, cn *Conn) error// Protocol 2 or 3. 用来和redis协商使用哪个协议版本的字段// Default is 3.Protocol int//ACL(Access Control List):Redis 6.0 引入了 ACL 系统,用于更细粒度地控制客户端对 Redis 服务器的访问权限。// Username 字段用于在连接到使用 Redis ACL 系统的 Redis 6.0 或更高版本实例时,指定用于身份验证的用户名。Username string// Redis ACL系统支持通过密码认证,该字段就是密码Password string// CredentialsProvider 允许更改用户名和密码,当更新之前这里返回原先的用户名和密码CredentialsProvider func() (username string, password string)// CredentialsProviderContext 是 CredentialsProvider 的增强版本,// CredentialsProviderContext 存在会忽略 CredentialsProvider,后期会合并两个接口只保留一个CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)// 选择哪个数据库,下支持0-15DB int// 尝试次数,默认是3次,-1 (not 0)关闭重试,0不尝试只执行一次MaxRetries int// 每次重试之间的最小重试间隔。默认值为8毫秒;-1表示禁用重试间隔MinRetryBackoff time.Duration// 每次重试之间最大时间间隔,默认为512毫秒,-1表示禁用重试间隔MaxRetryBackoff time.Duration// 拨号超时时间 默认是5秒DialTimeout time.Duration// 同步等待回复超时时间,如果超时命令执行失败// - `0` - 默认 (3 seconds).// - `-1` - 阻塞等待 (block indefinitely).// - `-2` - 完全禁用SetReadDeadline调用ReadTimeout time.Duration// 写超时时间// - `0` - 默认 (3 seconds).// - `-1` - 阻塞等待 (block indefinitely).// - `-2` - 完全禁止SetWriteDeadline调用WriteTimeout time.Duration// ContextTimeoutEnabled 为true的情况下会尊重Context上下文的超时时间,否则会忽略.// See https://redis.uptrace.dev/guide/go-redis-debugging.html#timeoutsContextTimeoutEnabled bool// 连接池类型// true 是 FIFO 连接池, false 代表 LIFO 连接池.// 请注意,FIFO的开销比LIFO略高,// 但它有助于更快地关闭空闲连接,从而减小池的大小。PoolFIFO bool// 连接池中基础套接字连接数量// 默认情况下每个可用的CPU核心会有10个连接 runtime.GOMAXPROCS.// 当连接池中被耗尽时,客户端会被分配额外的连接// 当然你可以使用MaxActiveConns限制连接池大小。PoolSize int// 表示当所有连接都忙时,客户端从连接池中获取连接的超时时间默认ReadTimeout + 1 为 6 秒。// 如果所有连接都在忙,并且客户端在 6 秒内无法获取到连接,则会返回一个错误PoolTimeout time.Duration// 连接池中最小空闲连接数量// Default is 0. 空闲连接默认不会被关闭.MinIdleConns int// 连接池中最大空闲连接数量// Default is 0. 空闲连接默认不会被关闭.MaxIdleConns int// 最大活跃连接数量// 0表示不设限制MaxActiveConns int// ConnMaxIdleTime 一个连接最长空闲时间.// 最后比系统超时时间少,否则将不起作用.//过期的连接可能会在重新使用之前被懒惰地关闭。如果d小于或等于0,则由于连接处于空闲状态,不会关闭连接。//默认值为30分钟。“-1”禁用空闲超时检查。ConnMaxIdleTime time.Duration// ConnMaxLifetime是一个连接可以被重用的最大时间。//// 过期的连接可能会在重用之前惰性关闭。// 如果<= 0,连接不会因为连接的"超期"(age)而关闭。//// 默认不关闭空闲连接。ConnMaxLifetime time.Duration// 使用的TLS配置。设置后,TLS将进行协商。TLSConfig *tls.Config// 限制器接口,用于实现断路器或速率限制器。Limiter Limiter// 在备机 slave/follower 节点上使能只读模式化.readOnly bool// 是否禁用客户端设置标识符,默认false.DisableIndentity bool// 为客户端名字添加后缀,默认空.IdentitySuffix string// EnableUnstable 字段用于启用 Redis Search 模块的不稳定模式(Unstable mode),// 并且该模式使用 RESP3 协议.UnstableResp3 bool
}
用户可以根据需要在创建redis客户端时进行选择性配置。
redis.NewClient的实现
NewClient
函数,用于创建一个新的 Redis 客户端实例。先看下函数调用流程
以下是代码的详细总结:
-
函数签名:
func NewClient(opt *Options) *Client
- 输入参数:
opt *Options
,指向Options
结构体的指针,用于配置 Redis 客户端。 - 返回值:
*Client
,返回一个指向Client
结构体的指针,表示新创建的 Redis 客户端实例。
- 输入参数:
type Client struct {*baseClientcmdablehooksMixin
}
-
初始化
Options
:opt.init()
- 调用
opt.init()
方法,对传入的Options
进行初始化。这一步确保Options
中的某些默认值被正确设置。
- 调用
-
创建
Client
实例:c := Client{baseClient: &baseClient{opt: opt,}, }
- 创建一个新的
Client
实例c
。 baseClient
是Client
的嵌入结构体,用于封装基本的客户端逻辑。- 将初始化后的
Options
传递给baseClient
。
- 创建一个新的
-
初始化
Client
:c.init()
- 调用
c.init()
方法,对Client
实例进行初始化。这一步可能包括设置一些内部状态或初始化其他资源。
- 调用
-
创建连接池:
c.connPool = newConnPool(opt, c.dialHook)
- 调用
newConnPool
函数,创建一个新的连接池connPool
。 newConnPool
函数接受Options
和dialHook
作为参数,返回一个连接池实例。dialHook
是Client
中的一个方法,用于在创建连接时执行一些额外的操作。
- 调用
-
返回
Client
实例:return &c
- 返回初始化完成的
Client
实例。
- 返回初始化完成的
NewClient
函数的主要作用是根据传入的 Options
配置创建并初始化一个新的 Redis 客户端实例。具体步骤包括:
- 初始化
Options
。 - 创建
Client
实例并初始化其嵌入的baseClient
。 - 初始化
Client
实例。 - 创建并设置连接池。
- 返回初始化完成的
Client
实例。
初始化Options
函数签名
// 因为是小写,因此redis包外不能调用
func (opt *Options) init()
- 输入参数:
opt *Options
,指向Options
结构体的指针。 - 返回值:无。
初始化逻辑
-
地址 (
Addr
)if opt.Addr == "" {opt.Addr = "localhost:6379" }
- 如果
Addr
为空,则设置为默认值"localhost:6379"
。
- 如果
-
网络类型 (
Network
)if opt.Network == "" {if strings.HasPrefix(opt.Addr, "/") {opt.Network = "unix"} else {opt.Network = "tcp"} }
- 如果
Network
为空,则根据Addr
的前缀判断是否为 Unix 套接字,如果是则设置Network
为"unix"
,否则设置为"tcp"
。
- 如果
-
连接超时时间 (
DialTimeout
)if opt.DialTimeout == 0 {opt.DialTimeout = 5 * time.Second }
- 如果
DialTimeout
为 0,则设置为默认值5 * time.Second
。
- 如果
-
拨号器 (
Dialer
)if opt.Dialer == nil {opt.Dialer = NewDialer(opt) }
- 如果
Dialer
为nil
,则使用NewDialer
函数创建一个新的拨号器,并赋值给Dialer
。
- 如果
-
连接池大小 (
PoolSize
)if opt.PoolSize == 0 {opt.PoolSize = 10 * runtime.GOMAXPROCS(0) }
- 如果
PoolSize
为 0,则设置为10 * runtime.GOMAXPROCS(0)
,即最大处理器数的 10 倍。
- 如果
-
读取超时时间 (
ReadTimeout
)switch opt.ReadTimeout { case -2:opt.ReadTimeout = -1 case -1:opt.ReadTimeout = 0 case 0:opt.ReadTimeout = 3 * time.Second }
- 根据
ReadTimeout
的不同值进行处理:-2
设置为-1
,完全禁止SetWriteDeadline调用。-1
设置为0
,表示阻塞调用0
设置为默认值3 * time.Second
。
- 根据
-
写入超时时间 (
WriteTimeout
)switch opt.WriteTimeout { case -2:opt.WriteTimeout = -1 case -1:opt.WriteTimeout = 0 case 0:opt.WriteTimeout = opt.ReadTimeout }
- 根据
WriteTimeout
的不同值进行处理:-2
设置为-1
。-1
设置为0
。0
设置为ReadTimeout
的值。
- 根据
-
连接池超时时间 (
PoolTimeout
)if opt.PoolTimeout == 0 {if opt.ReadTimeout > 0 {opt.PoolTimeout = opt.ReadTimeout + time.Second} else {opt.PoolTimeout = 30 * time.Second} }
- 如果
PoolTimeout
为 0,则根据ReadTimeout
的值进行设置:- 如果
ReadTimeout
大于 0,则设置为ReadTimeout + time.Second
。 - 否则设置为默认值
30 * time.Second
。
- 如果
- 如果
-
连接最大空闲时间 (
ConnMaxIdleTime
)
if opt.ConnMaxIdleTime == 0 {opt.ConnMaxIdleTime = 30 * time.Minute
}
- 如果
ConnMaxIdleTime
为 0,则设置为默认值30 * time.Minute
。
-
最大重试次数 (
MaxRetries
)if opt.MaxRetries == -1 {opt.MaxRetries = 0 } else if opt.MaxRetries == 0 {opt.MaxRetries = 3 }
- 如果
MaxRetries
为-1
,则设置为0
。 - 如果
MaxRetries
为0
,则设置为默认值3
。
- 如果
-
最小重试间隔 (
MinRetryBackoff
)switch opt.MinRetryBackoff { case -1:opt.MinRetryBackoff = 0 case 0:opt.MinRetryBackoff = 8 * time.Millisecond }
- 根据
MinRetryBackoff
的不同值进行处理:-1
设置为0
。0
设置为默认值8 * time.Millisecond
。
- 根据
-
最大重试间隔 (
MaxRetryBackoff
)switch opt.MaxRetryBackoff { case -1:opt.MaxRetryBackoff = 0 case 0:opt.MaxRetryBackoff = 512 * time.Millisecond }
- 根据
MaxRetryBackoff
的不同值进行处理:-1
设置为0
。0
设置为默认值512 * time.Millisecond
。
- 根据
Client结构体初始化
按照数据初始化过程,可以得到如下数据结构组织图:
可以将以上数据结构组成分解成如下几个部分:
Client结构体如下:
type Client struct {// 无论是直接将结构体放到这里还是将结构体的指针类型放到这里都能起到"继承"的作用*baseClient// 如果只有类型没有变量这里会创建一个和类型名称相同的成员变量cmdablehooksMixin
}
// NewClient returns a client to the Redis Server specified by Options.
func NewClient(opt *Options) *Client {opt.init()c := Client{baseClient: &baseClient{opt: opt,},}c.init()c.connPool = newConnPool(opt, c.dialHook)c.String()return &c
}
创建Client时只传入了一个opt, 我们来看下Client.init方法里面干了什么
func (c *Client) init() {c.cmdable = c.Processc.initHooks(hooks{dial: c.baseClient.dial,process: c.baseClient.process,pipeline: c.baseClient.processPipeline,txPipeline: c.baseClient.processTxPipeline,})
}
type hooksMixin struct {// 共享锁hooksMu *sync.Mutexslice []Hookinitial hookscurrent hooks
}
// 因为Client继承了hooksMixin,所以这里可以直接调用initHooks
func (hs *hooksMixin) initHooks(hooks hooks) {hs.hooksMu = new(sync.Mutex)hs.initial = hooks// 生成hooks链表,这个hooks可以根据需要中途替换hooks,具体建AddHook方法hs.chain()
}
baseClient.dial方法
func (c *baseClient) dial(ctx context.Context, network, addr string) (net.Conn, error) {return c.opt.Dialer(ctx, network, addr)
}// 用户没有自定义拨号函数的情况下,就使用默认的拨号函数
if opt.Dialer == nil {opt.Dialer = NewDialer(opt)
}func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, error) {return func(ctx context.Context, network, addr string) (net.Conn, error) {netDialer := &net.Dialer{Timeout: opt.DialTimeout,KeepAlive: 5 * time.Minute,}// 不支持tls直接直接进行context拨号if opt.TLSConfig == nil {return netDialer.DialContext(ctx, network, addr)}return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)}
}
baseClient.process
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {var lastErr error// c.opt.MaxRetries尝试次数,默认是3次,-1 (not 0)关闭重试,只执行一次for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {// 这里还需要防止闭包??还是为了编程习惯良好保持的?attempt := attemptretry, err := c._process(ctx, cmd, attempt)if err == nil || !retry {// err == nil 说明成功需要返回// 如果retry为0就算失败return err}lastErr = err}return lastErr
}
func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool, error) {if attempt > 0 {// 每次重试之间的最小重试间隔。默认值为8毫秒;-1表示禁用重试间隔// 每次重试之间最大时间间隔,默认为512毫秒,-1表示禁用重试间隔if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {return false, err}}retryTimeout := uint32(0)if err := c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {// 发送命令return writeCmd(wr, cmd)}); err != nil {// 进行原子+1 说明发送命令失败,这里需要返回一个失败err 并将retruTimeout技术增加atomic.StoreUint32(&retryTimeout, 1)return err}readReplyFunc := cmd.readReply// Apply unstable RESP3 search module.if c.opt.Protocol != 2 && c.assertUnstableCommand(cmd) {readReplyFunc = cmd.readRawReply}// 读取返回值if err := cn.WithReader(c.context(ctx), c.cmdTimeout(cmd), readReplyFunc); err != nil {if cmd.readTimeout() == nil {atomic.StoreUint32(&retryTimeout, 1)} else {atomic.StoreUint32(&retryTimeout, 0)}return err}return nil}); err != nil {retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1)return retry, err}return false, nil
}
代码解释
这段代码定义了 baseClient
结构体的 _process
方法,用于实际处理 Redis 命令的执行,并返回是否需要重试以及执行过程中遇到的错误。以下是代码的详细解释:
函数签名
func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool, error)
- 输入参数:
ctx context.Context
:上下文,用于传递请求的生命周期信息和取消信号。cmd Cmder
:表示要执行的 Redis 命令。attempt int
:当前的尝试次数。
- 返回值:
bool
:表示是否需要重试。error
:执行命令过程中遇到的错误。
方法逻辑
-
处理重试间隔
if attempt > 0 {if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {return false, err} }
- 如果当前尝试次数大于 0,调用
internal.Sleep
方法等待一段时间,时间间隔由c.retryBackoff(attempt)
计算得出。 - 如果在等待过程中上下文被取消或超时,返回
false
和相应的错误。
- 如果当前尝试次数大于 0,调用
-
初始化重试超时标志
retryTimeout := uint32(0)
- 声明一个原子变量
retryTimeout
,用于标记是否因超时而需要重试。
- 声明一个原子变量
-
处理连接和命令执行
if err := c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {return writeCmd(wr, cmd)}); err != nil {atomic.StoreUint32(&retryTimeout, 1)return err}readReplyFunc := cmd.readReply// Apply unstable RESP3 search module.if c.opt.Protocol != 2 && c.assertUnstableCommand(cmd) {readReplyFunc = cmd.readRawReply}if err := cn.WithReader(c.context(ctx), c.cmdTimeout(cmd), readReplyFunc); err != nil {if cmd.readTimeout() == nil {atomic.StoreUint32(&retryTimeout, 1)} else {atomic.StoreUint32(&retryTimeout, 0)}return err}return nil }); err != nil {retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1)return retry, err }
- 调用
c.withConn
方法获取连接,并在连接上执行命令。 - 使用
cn.WithWriter
方法写入命令:- 调用
writeCmd
方法将命令写入连接。 - 如果写入过程中出错,设置
retryTimeout
为 1 并返回错误。
- 调用
- 根据命令类型选择读取回复的方法:
- 默认使用
cmd.readReply
方法读取回复。 - 如果使用的是 RESP3 协议且命令不稳定,使用
cmd.readRawReply
方法读取原始回复。
- 默认使用
- 使用
cn.WithReader
方法读取回复:- 调用
cmd.readReply
或cmd.readRawReply
方法读取回复。 - 如果读取过程中出错,检查是否因超时而需要重试,设置
retryTimeout
相应的值并返回错误。
- 调用
- 如果命令执行成功,返回
nil
。
- 调用
-
判断是否需要重试
retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1) return retry, err
- 调用
shouldRetry
方法判断是否需要重试,传入错误和retryTimeout
的值。 - 返回是否需要重试和错误。
- 调用
-
返回成功
return false, nil
- 如果命令执行成功,返回
false
和nil
。
- 如果命令执行成功,返回
详细解析
-
处理重试间隔
- 如果当前尝试次数大于 0,调用
internal.Sleep
方法等待一段时间,时间间隔由c.retryBackoff(attempt)
计算得出。这一步是为了避免频繁重试导致的高负载。 - 如果在等待过程中上下文被取消或超时,返回
false
和相应的错误。
- 如果当前尝试次数大于 0,调用
-
初始化重试超时标志
- 声明一个原子变量
retryTimeout
,用于标记是否因超时而需要重试。初始值为 0。
- 声明一个原子变量
-
处理连接和命令执行
- 调用
c.withConn
方法获取连接,并在连接上执行命令。 - 使用
cn.WithWriter
方法写入命令:- 调用
writeCmd
方法将命令写入连接。 - 如果写入过程中出错,设置
retryTimeout
为 1 并返回错误。
- 调用
- 根据命令类型选择读取回复的方法:
- 默认使用
cmd.readReply
方法读取回复。 - 如果使用的是 RESP3 协议且命令不稳定,使用
cmd.readRawReply
方法读取原始回复。
- 默认使用
- 使用
cn.WithReader
方法读取回复:- 调用
cmd.readReply
或cmd.readRawReply
方法读取回复。 - 如果读取过程中出错,检查是否因超时而需要重试,设置
retryTimeout
相应的值并返回错误。
- 调用
- 如果命令执行成功,返回
nil
。
- 调用
-
判断是否需要重试
- 调用
shouldRetry
方法判断是否需要重试,传入错误和retryTimeout
的值。shouldRetry
方法会根据错误类型和超时情况决定是否需要重试。 - 返回是否需要重试和错误。
- 调用
-
返回成功
- 如果命令执行成功,返回
false
和nil
。
- 如果命令执行成功,返回
baseClient.processPipeline
func (c *baseClient) processPipeline(ctx context.Context, cmds []Cmder) error {if err := c.generalProcessPipeline(ctx, cmds, c.pipelineProcessCmds); err != nil {return err}return cmdsFirstErr(cmds)
}
baseClient.processTxPipeline
func (c *baseClient) processTxPipeline(ctx context.Context, cmds []Cmder) error {if err := c.generalProcessPipeline(ctx, cmds, c.txPipelineProcessCmds); err != nil {return err}return cmdsFirstErr(cmds)
}
redis.newConnPool
redis.newConnPool属于线程池,比较复杂这里不进行说明后面会单独抽一节进行说明
func newConnPool(opt *Options,dialer func(ctx context.Context, network, addr string) (net.Conn, error),
) *pool.ConnPool {return pool.NewConnPool(&pool.Options{Dialer: func(ctx context.Context) (net.Conn, error) {return dialer(ctx, opt.Network, opt.Addr)},PoolFIFO: opt.PoolFIFO,PoolSize: opt.PoolSize,PoolTimeout: opt.PoolTimeout,MinIdleConns: opt.MinIdleConns,MaxIdleConns: opt.MaxIdleConns,MaxActiveConns: opt.MaxActiveConns,ConnMaxIdleTime: opt.ConnMaxIdleTime,ConnMaxLifetime: opt.ConnMaxLifetime,})
}
总结
经过上述过程,一个完整的Client算是创建完成了,后面你就可以使用Client对redis进行操作了
附录
- 数据来源-《go-redis》
- 代码仓库:gitee note_lab
- redis gitee redis
- go-redis gitee go-redis
相关文章:
[go-redis]客户端的创建与配置说明
创建redis client 使用go-redis库进行创建redis客户端比较简单,只需要调用redis.NewClient接口创建一个客户端 redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379",Password: "",DB: 0, })NewClient接口只接收一个参数red…...
Windows 和 Linux 系统命令行操作详解:从文件管理到进程监控
1.切换盘符与目录操作 在命令行中,切换盘符和目录是最常见的操作。尽管 DOS 和 Linux 在这些操作上有所不同,但它们都能实现相似的功能。 (1)切换盘符 ①DOS命令:在 DOS 中,切换盘符非常简单,使用 盘符名:ÿ…...
SpringBoot中@Import和@ImportResource和@PropertySource
1. Import Import注解是引入java类: 导入Configuration注解的配置类(4.2版本之前只可以导入配置类,4.2版本之后也可以导入普通类)导入ImportSelector的实现类导入ImportBeanDefinitionRegistrar的实现类 SpringBootApplication…...
etcd-v3.5release-(3)-readIndexRead
笔记1:读操作包括两种,readIndex和serilizable,readIndex指一致性读,一旦a读到了数据x,那么a及a以后的数据都能读到x,readIndex读会先确认本leader是不是有效地leader,如果有效则记录此刻的comm…...
Chrome 中小于 12px 文字的实现方式与应用场景详解
让 Chrome 支持小于 12px 的文字 在 Web 开发中,有时需要将文字显示为小于 12px 的尺寸,尤其是在设计精细的 UI 元素时。虽然大多数浏览器支持小于 12px 的字体大小,但 Chrome 默认情况下会通过调整文本渲染来确保文字可读性,尤其在非常小的文字尺寸下,可能会进行抗锯齿处…...
数据挖掘之数据预处理
引言 数据挖掘是从大量数据中提取有用信息和知识的过程。在这个过程中,数据预处理是不可或缺的关键步骤。数据预处理旨在清理和转换数据,以提高数据质量,从而为后续的数据挖掘任务奠定坚实的基础。由于现实世界中的数据通常…...
slam学习笔记6---样例展示雅可比手推过程
背景:一直在使用模板、自动化求导,对于背后雅可比推导只剩一个基本概念,有必要好好梳理巩固一下。本人水平有限,若推导过程有误,欢迎评论区提出。 假设一个二维slam问题,使用欧式距离观测模型,…...
ThreadLocal 详解
ThreadLocal 详解 ThreadLocal 是 Java 提供的一种线程本地存储机制,用于为每个线程提供独立的变量副本,变量的值仅在线程内可见,从而实现线程隔离。这种特性在需要为每个线程维护独立状态的场景中非常有用,例如用户上下文、事务…...
SQL SERVER 2016 AlwaysOn 无域集群+负载均衡搭建与简测
之前和很多群友聊天发现对2016的无域和负载均衡满心期待,毕竟可以简单搭建而且可以不适用第三方负载均衡器,SQL自己可以负载了。windows2016已经可以下载使用了,那么这回终于可以揭开令人憧憬向往的AlwaysOn2016 负载均衡集群的神秘面纱了。 …...
Unity 基于Collider 组件在3D 物体表面放置3D 物体
实现 从鼠标点击的屏幕位置发送射线,以射线监测点击到的物体,根据点击物体的法线向量调整放置物体的位置及朝向。 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100)) {obj.transform.…...
项目搭建:springboot,mybatis, maven
创建一个基于Spring Boot、MyBatis和Maven的项目可以简化很多配置,因为Spring Boot自带了很多自动配置的功能。下面我将给出一个简单的示例来展示如何搭建这样一个项目。 ### 1. 创建一个新的Spring Boot项目 你可以通过Spring Initializr(https://sta…...
网页端五子棋对战(四)---玩家匹配实现上线下线处理
文章目录 1.游戏大厅用户匹配1.1请求和响应1.2设计匹配页面1.3获取玩家信息1.4玩家信息的样式设置1.5初始化我们的websocket1.6点击按钮和客户端交互1.7点击按钮和服务器端交互 2.服务器端实现匹配功能框架2.1方法重写2.2借用session 3.处理上线下线3.1什么是上线下线3.2实现用…...
Linux笔记---进程:进程替换
1. 进程替换的概念 进程替换是指在一个正在运行的进程中,用一个新的程序替换当前进程的代码和数据,使得进程开始执行新的程序,而不是原来的程序。 这种技术通常用于在不创建新进程的情况下,改变进程的行为。 我们之前谈到过for…...
【AI日记】24.12.04 kaggle 比赛 Titanic-7
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 内容: 学习 kaggle 入门比赛 Titanic - Machine Learning from Disaster学习机器学习基础(pandas,numpy,sklearn,seaborn,matplotl…...
使用 Flutter 进行移动应用开发:深入探索
文章目录 前言一、介绍二、安装 Flutter 环境三、Flutter 应用结构与基础组件四、状态管理策略五、高级主题结语 前言 随着移动技术的迅猛发展,跨平台开发的需求日益增长。开发者们一直在寻找一种既能保证应用性能又能减少开发成本和时间的技术方案。Flutter 应运而…...
SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2
Caused by: javax.net.ssl.SSLHandshakeException: the server selected protocol version TLS10 is not accepted by client preferences [TLS12] 原因描述:SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2 解决方法如下: 打开文件…...
Linux命令行解释器的模拟实现
欢迎拜访:羑悻的小杀马特.-CSDN博客 本篇主题:Linux命令行解释器 制作日期:2024.12.04 隶属专栏:linux之旅 本篇简介: 主线带你用ubuntu版系统步步分析实现基础版本的shell;比如支持重定向操作࿰…...
模块化设计割草机器人系统研究与实现(系统架构师论文)
摘要: 割草机器人项目旨在实现自主草坪修剪,以提高园艺工作的效率。本文以具体项目为背景,介绍系统架构师在项目开发过程中的架构设计策略、关键技术应用、问题解决方案和优化措施。首先,本文分析割草机器人项目的总体架构需求&a…...
javascript(前端)作为客户端端通过grpc与cpp(服务端)交互
参考文章 https://blog.csdn.net/pathfinder1987/article/details/129188540 https://blog.csdn.net/qq_45634989/article/details/128151766 前言 临时让我写前端, 一些配置不太懂, 可能文章有多余的步骤但是好歹能跑起来吧 你需要提前准备 公司有自带的这些, 但是版本大都…...
股市复盘笔记
复盘是股市投资中非常重要的一个环节,它指的是投资者在股市收盘后,对当天的市场走势、个股表现以及自己的交易行为进行回顾和总结,以便更好地指导未来的投资决策。以下是对复盘的详细解释: 一、复盘的目的 总结市场走势ÿ…...
【SARL】单智能体强化学习(Single-Agent Reinforcement Learning)《纲要》
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
Linux 权限管理:用户分类、权限解读与常见问题剖析
🌟 快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。🌟 🚩用通俗易懂且不失专业性的文字,讲解计算机领域那些看似枯燥的知识点🚩 目录 💯L…...
Windows通过指令查看已安装的驱动
Windows通过指令查看已安装的驱动 在 Windows 操作系统中,有几种命令可以用来查看已安装的驱动程序。以下是常见的几种方法: 1. 使用 pnputil 查看已安装驱动程序 pnputil 是一个 Windows 内置工具,可以列出所有已安装的驱动程序包。 命令…...
Springboot(五十一)SpringBoot3整合Sentinel-nacos持久化策略
上文中我记录了在Springboot项目中链接sentinel-dashboard使用限流规则的全过程。 但是呢,有一个小小的问题,我重启了一下我本地的sentinel-dashboard服务,然后,我之前创建的所有的流控规则都没了…… 这……好像有点不合理啊,咱就不能找地儿存储一下?你这一重启就没了,…...
Scala的正则表达式
package hfdobject Test35_3 {def main(args: Array[String]): Unit {println("a\tb")//定义一个规则 正则表达式//1. .表示除了换行之外的其他的任意单个字符//2. \d等于[0-9] 匹配一个数字//3. \D除了\d之外的其他的任意字符,表示非数字//4. \w等价于[…...
cuda12.1版本的pytorch环境安装记录,并添加到jupyter和pycharm中
文章目录 前置准备使用anaconda prompt创建虚拟环境创建虚拟环境激活pytorch虚拟环境把pytorch下载到本地使用pip把安装包安装到pytorch环境中进入python环境检验是否安装成功将环境添加到jupyter在pycharm中使用该环境: 前置准备 安装anaconda,我的版本…...
牛客linux
1、 统计文件的行数 # 方法 1 wc -l ./nowcoder.txt | awk {print $1} # 方法 2 ,awk 可以打印所有行的行号, 或者只打印最后一行 awk {print NR} ./nowcoder.txt |tail -n 1 awk END{print NR} ./nowcoder.txt # 方法 3 grep -c 、-n等等 grep -c "" ./…...
通过HTML Canvas 在图片上绘制文字
目录 前言 一、HTML Canvas 简介 二、准备工作 三、绘制图片 四、绘制文字 五、完整代码 效果演示: 前言 HTML canvas 为我们提供了无限的创意可能性。今天,我们就来探索一下如何通过 HTML canvas 将图片和文字绘制到图片上,创造出独特…...
【C#】ListBox中找到多个image中的其中一个并重置赋值以便清理占用内存
1.ListBox中定义多个image 定义ListBox前台代码及Image控件的赋值 <ListBox Background"{DynamicResource BackgroundBrush}" ItemsSource"{Binding ElementNameDRFinish,PathImages}" Style"{x:Null}" Name"ImageList"ItemConta…...
Apache Commons工具类库使用整理
文章目录 Apache Commons工具类库分类- commons-lang3字符串工具:StringUtils日期工具:DateUtils数值工具:NumberUtils对象工具:ObjectUtils数组工具:ArrayUtils异常工具:ExceptionUtils枚举工具࿱…...
npm 设置镜像
要在npm中设置镜像,你可以使用npm config命令。以下是设置npm镜像的步骤: 临时使用淘宝镜像: npm --registry https://registry.npmmirror.com install package-name 永久设置镜像: npm config set registry https://registry…...
数据结构自测5
第6章 树和二叉树 自测卷解答 一、下面是有关二叉树的叙述,请判断正误(每小题1分,共10分) ( √ )1. 若二叉树用二叉链表作存贮结构,则在n个结点的二叉树链表中只有n—1个非空指针域。 ÿ…...
【后端面试总结】缓存策略选择
一般来说我们常见的缓存策略有三种,他们各自的优劣势和实现逻辑分别如下 Cache Aside(旁路缓存) 特点: 灵活性高:应用程序直接与缓存和数据库交互,具有高度的灵活性,可以根据业务需求自定义缓…...
40分钟学 Go 语言高并发:RPC服务开发实战
RPC服务开发实战 一、RPC服务基础概览 开发阶段关键点重要程度考虑因素接口设计API定义、协议选择、版本控制⭐⭐⭐⭐⭐可扩展性、兼容性服务实现业务逻辑、并发处理、资源管理⭐⭐⭐⭐⭐性能、可靠性错误处理异常捕获、错误码、故障恢复⭐⭐⭐⭐稳定性、可维护性性能测试负载…...
Linux 无界面模式下使用 selenium
文章目录 前言什么是无界面模式?具体步骤安装谷歌浏览器查看安装的谷歌浏览器的版本下载对应版本驱动并安装Python 测试代码 总结个人简介 前言 在 Linux 服务器上运行自动化测试或网页爬虫时,常常需要使用 Selenium 来驱动浏览器进行操作。然而&#x…...
算法第一弹-----双指针
目录 1.移动零 2.复写零 3.快乐数 4.盛水最多的容器 5.有效三角形的个数 6.查找总价值为目标值的两个商品 7.三数之和 8.四数之和 双指针通常是指在解决问题时,同时使用两个指针(变量,常用来指向数组、链表等数据结构中的元素位置&am…...
学习如何解决“区间划分”问题(一般方法论+实例应用讲解)
文章目录 解决“区间划分”问题的一般方法论方法论:解决区间划分问题的四步法1. 问题分析与建模2. 动态规划状态的定义3. 状态转移方程4. 初始条件与边界 方法论应用:最小和最大石子合并得分问题描述步骤 1:问题分析与建模步骤 2:…...
消息中间件-Kafka2-3.9.0源码构建
消息中间件-Kafka2-3.9.0源码构建 1、软件环境 JDK Version 1.8Scala Version 2.12.0Kafka-3.9.0 源码包 下载地址:https://downloads.apache.org/kafka/3.9.0/kafka-3.9.0-src.tgzGradle Version > 8.8Apache Zookeeper 3.7.0 2、源码编译 打开源码根目录修改…...
达梦归档文件名与实例对应关系
默认的,达梦归档文件名比较难以看懂,且多实例下不好区分 靠它就行 select upper(to_char((select DB_MAGIC), xxxxxxxxxx)) mag_id; 这样就对上号了。...
STL算法之sort
STL所提供的各式各样算法中,sort()是最复杂最庞大的一个。这个算法接受两个RandomAccessIterators(随机存取迭代器),然后将区间内的所有元素以渐增方式由小到大重新排列。还有一个版本则是允许用户指定一个仿函数代替operator<作为排序标准。STL的所有…...
elementui table滚动分页加载
文章目录 概要 简化的实现示例: 小结 概要 在使用 Element UI 的 Table 组件时,如果需要实现滚动分页加载的功能,可以通过监听 Table 的滚动事件来动态加载更多数据。 简化的实现示例: <template><el-table ref"…...
【MySQL 进阶之路】索引的使用
5.索引的使用规则 在数据库管理系统(DBMS)中,索引是提高查询效率的关键机制之一。MySQL索引优化是指通过设计、调整和选择合适的索引策略,以提高数据库的查询性能和降低资源消耗。以下是一些关键的索引使用规则: 1. …...
FPGA中所有tile介绍
FPGA中包含的tile类型,以xinlinx 7k为例,可以通过f4pga项目中的原语文件夹查看,主要包含以下这些: 以下是您提到的 Xilinx 7 系列 FPGA 中各种模块的含义及用途: 1. BRAM (Block RAM) BRAM 是 FPGA 中的块存储资源&…...
理解 Python PIL库中的 convert(‘RGB‘) 方法:为何及如何将图像转换为RGB模式
理解 Python PIL库中的 convert(RGB) 方法:为何及如何将图像转换为RGB模式 在图像处理中,保持图像数据的一致性和可操作性是至关重要的。Python的Pillow库(继承自PIL, Python Imaging Library)提供了强大的工具和方法来处理图像&…...
LVS默认的工作模式支持哪些负载均衡算法?
LVS默认的工作模式支持哪些负载均衡算法? LVS(Linux Virtual Server)默认支持多种负载均衡算法,这些算法在不同的场景下具有各自的优势。以下是 LVS 默认支持的负载均衡算法及其特点: 1. 轮询调度(Round Robin Sched…...
C/C++中的调用约定
在C/C编程中,调用约定(calling conventions)是一组指定如何调用函数的规则。主要在你调用代码之外的函数(例如OS API,操作系统应用程序接口)或OS调用你(如WinMain的情况)时起作用。如果编译器不知道正确的调用约定,那么你很可能会遇到非常奇怪…...
RAG评估指南:从检索到生成,全面解析LLM性能评估方法
前言 这一节我们将从时间线出发对RAG的评估方式进行对比,这些评估方式不仅限于RAG流程之中,其中基于LLM的评估方式更加适用于各行各业。 RAG常用评估方式 上一节我们讲了如何用ROUGE 这个方法评估摘要的相似度,由于篇幅限制,没…...
极兔速递开放平台快递物流查询API对接流程
目录 极兔速递开放平台快递物流查询API对接流程API简介物流查询API 对接流程1. 注册用户2. 申请成为开发者3. 企业认证4. 联调测试5. 发布上线 签名机制详解1. 提交方式2. 签名规则3. 字段类型与解析约定 物流轨迹服务极兔快递单号查询的其他方案总结 极兔速递开放平台快递物流…...
FFmpeg:强大的音视频处理工具指南
FFmpeg:强大的音视频处理工具指南 1. FFmpeg简介2. 核心特性2.1 基础功能2.2 支持的格式和编解码器 3. 主要组件3.1 命令行工具3.2 开发库 4. 最新发展5. 安装指南5.1 Windows系统安装5.1.1 直接下载可执行文件5.1.2 使用包管理器安装 5.2 Linux系统安装5.2.1 Ubunt…...
项目集成篇:springboot集成redistemple实现自定义缓存,并且可以设置过期时间
在Spring Boot中集成Redis并使用RedisTemplate实现自定义缓存功能,同时能够设置缓存项的过期时间,可以通过以下步骤来完成。我们将创建一个服务层方法,该方法将使用RedisTemplate直接与Redis交互,并为每个缓存项设置特定的过期时间…...