[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需要的参数,我们来具体看下各个参数字段的内容以及使用方式,这些字段包括但不限于:

网络连接相关

  1. Network:

    • 类型:string
    • 描述:网络类型,可以是 tcpunix。默认值为 tcp
  2. Addr:

    • 类型:string
    • 描述:Redis 服务器的地址,格式为 host:port
  3. Dialer:

    • 类型:func(ctx context.Context, network, addr string) (net.Conn, error)
    • 描述:自定义的拨号函数,用于创建网络连接。如果设置了 Dialer,则 NetworkAddr 的设置将失效。
  4. OnConnect:

    • 类型:func(ctx context.Context, cn *Conn) error
    • 描述:连接建立成功时的回调函数。
  5. DialTimeout:

    • 类型:time.Duration
    • 描述:拨号超时时间,默认为 5 秒。
  6. ReadTimeout:

    • 类型:time.Duration
    • 描述:同步等待回复的超时时间。默认为 3 秒,-1 表示阻塞等待,-2 表示完全禁用 SetReadDeadline 调用。
  7. WriteTimeout:

    • 类型:time.Duration
    • 描述:写操作的超时时间。默认为 3 秒,-1 表示阻塞等待,-2 表示完全禁用 SetWriteDeadline 调用。
  8. ContextTimeoutEnabled:

    • 类型:bool
    • 描述:是否尊重 Context 上下文的超时时间。默认为 false

认证和权限相关

  1. ClientName:

    • 类型:string
    • 描述:每个连接都会执行 CLIENT SETNAME 命令为每个连接设置客户端名字。
  2. Username:

    • 类型:string
    • 描述:用于 Redis ACL 系统的身份验证用户名。
  3. Password:

    • 类型:string
    • 描述:用于 Redis ACL 系统的身份验证密码。
  4. CredentialsProvider:

    • 类型:func() (username string, password string)
    • 描述:允许动态更改用户名和密码。
  5. CredentialsProviderContext:

    • 类型:func(ctx context.Context) (username string, password string, err error)
    • 描述:增强版的 CredentialsProvider,存在时会忽略 CredentialsProvider

协议和功能相关

  1. Protocol:

    • 类型:int
    • 描述:使用的协议版本,2 或 3。默认值为 3。
  2. UnstableResp3:

    • 类型:bool
    • 描述:启用 Redis Search 模块的不稳定模式,并使用 RESP3 协议。

连接池相关

  1. PoolFIFO:

    • 类型:bool
    • 描述:连接池类型,true 表示 FIFO 连接池,false 表示 LIFO 连接池。默认为 false
  2. PoolSize:

    • 类型:int
    • 描述:连接池中基础套接字连接数量。默认情况下每个可用的 CPU 核心会有 10 个连接。
  3. PoolTimeout:

    • 类型:time.Duration
    • 描述:当所有连接都忙时,客户端从连接池中获取连接的超时时间。默认为 ReadTimeout + 1,即 6 秒。
  4. MinIdleConns:

    • 类型:int
    • 描述:连接池中最小空闲连接数量。默认为 0。
  5. MaxIdleConns:

    • 类型:int
    • 描述:连接池中最大空闲连接数量。默认为 0。
  6. MaxActiveConns:

    • 类型:int
    • 描述:最大活跃连接数量。0 表示不设限制。
  7. ConnMaxIdleTime:

    • 类型:time.Duration
    • 描述:连接最长空闲时间。默认为 30 分钟,-1 表示禁用空闲超时检查。
  8. ConnMaxLifetime:

    • 类型:time.Duration
    • 描述:连接可以被重用的最大时间。默认不关闭空闲连接。

重试机制

  1. MaxRetries:

    • 类型:int
    • 描述:尝试次数,默认为 3 次,-1 表示关闭重试,0 表示不尝试只执行一次。
  2. MinRetryBackoff:

    • 类型:time.Duration
    • 描述:每次重试之间的最小重试间隔。默认为 8 毫秒,-1 表示禁用重试间隔。
  3. MaxRetryBackoff:

    • 类型:time.Duration
    • 描述:每次重试之间最大时间间隔。默认为 512 毫秒,-1 表示禁用重试间隔。

其他配置

  1. DB:

    • 类型:int
    • 描述:选择哪个数据库,支持 0-15。
  2. TLSConfig:

    • 类型:*tls.Config
    • 描述:使用的 TLS 配置。设置后,TLS 将进行协商。
  3. Limiter:

    • 类型:Limiter
    • 描述:限制器接口,用于实现断路器或速率限制器。
  4. readOnly:

    • 类型:bool
    • 描述:在备机(slave/follower)节点上使能只读模式。
  5. DisableIndentity:

    • 类型:bool
    • 描述:是否禁用客户端设置标识符,默认为 false
  6. 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 客户端实例。先看下函数调用流程

builtin Package builtin prov The items documented but their descriptio
pool
redis
The make built-in fu slice, map, or chan value. Unlike new, m make
NewConnPool
NewClient returns a NewClient
newConnPool

以下是代码的详细总结:

  1. 函数签名:

    func NewClient(opt *Options) *Client
    
    • 输入参数:opt *Options,指向 Options 结构体的指针,用于配置 Redis 客户端。
    • 返回值:*Client,返回一个指向 Client 结构体的指针,表示新创建的 Redis 客户端实例。
type Client struct {*baseClientcmdablehooksMixin
}
  1. 初始化 Options:

    opt.init()
    
    • 调用 opt.init() 方法,对传入的 Options 进行初始化。这一步确保 Options 中的某些默认值被正确设置。
  2. 创建 Client 实例:

    c := Client{baseClient: &baseClient{opt: opt,},
    }
    
    • 创建一个新的 Client 实例 c
    • baseClientClient 的嵌入结构体,用于封装基本的客户端逻辑。
    • 将初始化后的 Options 传递给 baseClient
  3. 初始化 Client:

    c.init()
    
    • 调用 c.init() 方法,对 Client 实例进行初始化。这一步可能包括设置一些内部状态或初始化其他资源。
  4. 创建连接池:

    c.connPool = newConnPool(opt, c.dialHook)
    
    • 调用 newConnPool 函数,创建一个新的连接池 connPool
    • newConnPool 函数接受 OptionsdialHook 作为参数,返回一个连接池实例。
    • dialHookClient 中的一个方法,用于在创建连接时执行一些额外的操作。
  5. 返回 Client 实例:

    return &c
    
    • 返回初始化完成的 Client 实例。

NewClient 函数的主要作用是根据传入的 Options 配置创建并初始化一个新的 Redis 客户端实例。具体步骤包括:

  1. 初始化 Options
  2. 创建 Client 实例并初始化其嵌入的 baseClient
  3. 初始化 Client 实例。
  4. 创建并设置连接池。
  5. 返回初始化完成的 Client 实例。

初始化Options

函数签名
// 因为是小写,因此redis包外不能调用
func (opt *Options) init()
  • 输入参数:opt *Options,指向 Options 结构体的指针。
  • 返回值:无。
初始化逻辑
  1. 地址 (Addr)

    if opt.Addr == "" {opt.Addr = "localhost:6379"
    }
    
    • 如果 Addr 为空,则设置为默认值 "localhost:6379"
  2. 网络类型 (Network)

    if opt.Network == "" {if strings.HasPrefix(opt.Addr, "/") {opt.Network = "unix"} else {opt.Network = "tcp"}
    }
    
    • 如果 Network 为空,则根据 Addr 的前缀判断是否为 Unix 套接字,如果是则设置 Network"unix",否则设置为 "tcp"
  3. 连接超时时间 (DialTimeout)

    if opt.DialTimeout == 0 {opt.DialTimeout = 5 * time.Second
    }
    
    • 如果 DialTimeout 为 0,则设置为默认值 5 * time.Second
  4. 拨号器 (Dialer)

    if opt.Dialer == nil {opt.Dialer = NewDialer(opt)
    }
    
    • 如果 Dialernil,则使用 NewDialer 函数创建一个新的拨号器,并赋值给 Dialer
  5. 连接池大小 (PoolSize)

    if opt.PoolSize == 0 {opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
    }
    
    • 如果 PoolSize 为 0,则设置为 10 * runtime.GOMAXPROCS(0),即最大处理器数的 10 倍。
  6. 读取超时时间 (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
  7. 写入超时时间 (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 的值。
  8. 连接池超时时间 (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
  9. 连接最大空闲时间 (ConnMaxIdleTime)

if opt.ConnMaxIdleTime == 0 {opt.ConnMaxIdleTime = 30 * time.Minute
}
  • 如果 ConnMaxIdleTime 为 0,则设置为默认值 30 * time.Minute
  1. 最大重试次数 (MaxRetries)

    if opt.MaxRetries == -1 {opt.MaxRetries = 0
    } else if opt.MaxRetries == 0 {opt.MaxRetries = 3
    }
    
    • 如果 MaxRetries-1,则设置为 0
    • 如果 MaxRetries0,则设置为默认值 3
  2. 最小重试间隔 (MinRetryBackoff)

    switch opt.MinRetryBackoff {
    case -1:opt.MinRetryBackoff = 0
    case 0:opt.MinRetryBackoff = 8 * time.Millisecond
    }
    
    • 根据 MinRetryBackoff 的不同值进行处理:
      • -1 设置为 0
      • 0 设置为默认值 8 * time.Millisecond
  3. 最大重试间隔 (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:执行命令过程中遇到的错误。
方法逻辑
  1. 处理重试间隔

    if attempt > 0 {if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {return false, err}
    }
    
    • 如果当前尝试次数大于 0,调用 internal.Sleep 方法等待一段时间,时间间隔由 c.retryBackoff(attempt) 计算得出。
    • 如果在等待过程中上下文被取消或超时,返回 false 和相应的错误。
  2. 初始化重试超时标志

    retryTimeout := uint32(0)
    
    • 声明一个原子变量 retryTimeout,用于标记是否因超时而需要重试。
  3. 处理连接和命令执行

    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.readReplycmd.readRawReply 方法读取回复。
      • 如果读取过程中出错,检查是否因超时而需要重试,设置 retryTimeout 相应的值并返回错误。
    • 如果命令执行成功,返回 nil
  4. 判断是否需要重试

    retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1)
    return retry, err
    
    • 调用 shouldRetry 方法判断是否需要重试,传入错误和 retryTimeout 的值。
    • 返回是否需要重试和错误。
  5. 返回成功

    return false, nil
    
    • 如果命令执行成功,返回 falsenil
详细解析
  1. 处理重试间隔

    • 如果当前尝试次数大于 0,调用 internal.Sleep 方法等待一段时间,时间间隔由 c.retryBackoff(attempt) 计算得出。这一步是为了避免频繁重试导致的高负载。
    • 如果在等待过程中上下文被取消或超时,返回 false 和相应的错误。
  2. 初始化重试超时标志

    • 声明一个原子变量 retryTimeout,用于标记是否因超时而需要重试。初始值为 0。
  3. 处理连接和命令执行

    • 调用 c.withConn 方法获取连接,并在连接上执行命令。
    • 使用 cn.WithWriter 方法写入命令:
      • 调用 writeCmd 方法将命令写入连接。
      • 如果写入过程中出错,设置 retryTimeout 为 1 并返回错误。
    • 根据命令类型选择读取回复的方法:
      • 默认使用 cmd.readReply 方法读取回复。
      • 如果使用的是 RESP3 协议且命令不稳定,使用 cmd.readRawReply 方法读取原始回复。
    • 使用 cn.WithReader 方法读取回复:
      • 调用 cmd.readReplycmd.readRawReply 方法读取回复。
      • 如果读取过程中出错,检查是否因超时而需要重试,设置 retryTimeout 相应的值并返回错误。
    • 如果命令执行成功,返回 nil
  4. 判断是否需要重试

    • 调用 shouldRetry 方法判断是否需要重试,传入错误和 retryTimeout 的值。shouldRetry 方法会根据错误类型和超时情况决定是否需要重试。
    • 返回是否需要重试和错误。
  5. 返回成功

    • 如果命令执行成功,返回 falsenil
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进行操作了
在这里插入图片描述

附录

  1. 数据来源-《go-redis》
  2. 代码仓库:gitee note_lab
  3. redis gitee redis
  4. go-redis gitee go-redis

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/63043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图形开发基础之在WinForms中使用OpenTK.GLControl进行图形绘制

前言 GLControl 是 OpenTK 库中一个重要的控件&#xff0c;专门用于在 Windows Forms 应用程序中集成 OpenGL 图形渲染。通过 GLControl&#xff0c;可以轻松地将 OpenGL 的高性能图形绘制功能嵌入到传统的桌面应用程序中。 1. GLControl 的核心功能 OpenGL 渲染上下文&…

shell编程7,bash解释器的 for循环+while循环

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 一、引言 1. 栈与队列在编程中的角色定位 栈和队列作为两种基本的数据结构&#xff0c;在众多编程场景中都有着独特的地位。它们为数据的有序…

springboot vue 会员收银系统 (13) 欢迎页优化 开源

前言 完整版演示 http://120.26.95.195/ 开发版演示 http://120.26.95.195:8889/ 在之前的开发进程中&#xff0c;我们完成了会员系统的基本功能 基本完成了收银功能 今天我们对欢迎页进行优化。 1.信息分区 仪表盘将信息分为几个主要区域&#xff0c;包括关键数据统计区、功…

在windows系统用Anaconda搭建运行PyTorch识别安全帽项目的环境

一.背景 我期望基于开源项目实现工业场景中安全帽识别。之前的各种尝试&#xff0c;也不太顺利。发现安全帽识别的开源项目使用的是基于Python的PyTorch实现训练的。上一篇写了Python的安装&#xff0c;发现后续安装其他的并不方便。我为什么选择&#xff0c;下面再详细说原因。…

Spring事务的一道面试题

每次聊起Spring事务&#xff0c;好像很熟悉&#xff0c;又好像很陌生。本篇通过一道面试题和一些实践&#xff0c;来拆解几个Spring事务的常见坑点。 原理 Spring事务的原理是&#xff1a;通过AOP切面的方式实现的&#xff0c;也就是通过代理模式去实现事务增强。 具体过程是&a…

AI换脸facefusion 3.0.1安装及其使用方法(源代码安装)

文章目录 1. 本文简介2. 软件环境准备软件下载及安装代码下载 3. Conda环境4. 加速环境5. 依赖安装6. 图片测试 今天给大家分享换脸facefusion 3.0.1使用方法。不仅可以对单张图片换脸&#xff0c;也可以对视频换脸。如果是单张图片的话&#xff0c;没有GPU速度也很快&#xff…

在CentOS上无Parallel时并发上传.wav文件的Shell脚本解决方案

在CentOS上无Parallel时并发上传.wav文件的Shell脚本解决方案 背景概述解决方案脚本实现脚本说明使用指南注意事项在CentOS操作系统环境中,若需并发上传特定目录下的.wav文件至HTTP服务器,而系统未安装GNU parallel工具,我们可通过其他方法实现此需求。本文将介绍一种利用Sh…

极兔速递开放平台快递物流查询API对接流程

目录 极兔速递开放平台快递物流查询API对接流程API简介物流查询API 对接流程1. 注册用户2. 申请成为开发者3. 企业认证4. 联调测试5. 发布上线 签名机制详解1. 提交方式2. 签名规则3. 字段类型与解析约定 物流轨迹服务极兔快递单号查询的其他方案总结 极兔速递开放平台快递物流…

Linux命令行解释器的模拟实现

欢迎拜访&#xff1a;羑悻的小杀马特.-CSDN博客 本篇主题&#xff1a;Linux命令行解释器 制作日期&#xff1a;2024.12.04 隶属专栏&#xff1a;linux之旅 本篇简介&#xff1a; 主线带你用ubuntu版系统步步分析实现基础版本的shell&#xff1b;比如支持重定向操作&#xff0…

JVM八股文精简

目录 简述JVM类加载过程简述JVM中的类加载器简述双亲委派机制双亲委派机制的优点简述JVM内存模型简述程序计数器简述虚拟机栈简述本地方法栈简述JVM中的堆简述方法区简述运行时常量池简述Java创建对象的过程简述JVM给对象分配内存的策略Java对象内存分配是如何保证线程安全的如…

微信 创建小程序码-有数量限制

获取小程序码&#xff1a;小程序码为圆图&#xff0c;有数量限制。 目录 文档 接口地址 功能描述 注意事项 请求参数 对接 获取小程序码 调用获取 小程序码示例 总结 文档 接口地址 https://api.weixin.qq.com/wxa/getwxacode?access_tokenaccess_token 功能描述 …

【spring mvc】全局处理请求体和响应体

目录 说明实现效果逻辑图 实现步骤创建公共处理的请求和响应的类api接口测试前端请求响应结果 扩展Response响应格式实体ResponseCode 响应状态码RSA工具类 RequestBodyAdvice 介绍使用场景 ResponseBodyAdvice 介绍使用场景 说明 由于项目中需要进行加密传输数据提高项目安全…

消息中间件-Kafka2-3.9.0源码构建

消息中间件-Kafka2-3.9.0源码构建 1、软件环境 JDK Version 1.8Scala Version 2.12.0Kafka-3.9.0 源码包 下载地址&#xff1a;https://downloads.apache.org/kafka/3.9.0/kafka-3.9.0-src.tgzGradle Version > 8.8Apache Zookeeper 3.7.0 2、源码编译 打开源码根目录修改…

AMEYA360 | 杭晶电子:晶振在AR/VR中的应用

晶振在AR/VR设备中扮演重要角色&#xff0c;为其核心电子系统提供稳定的时钟信号&#xff0c;确保设备的高性能运行。 以下是晶振在AR/VR应用中的具体作用&#xff1a; 01、图像处理与同步 1、晶振为图形处理单元(GPU)和显示芯片提供精准的时钟信号&#xff0c;支持高速图像渲染…

【SARL】单智能体强化学习(Single-Agent Reinforcement Learning)《纲要》

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…

跟李笑来学美式俚语(Most Common American Idioms): Part 52

Most Common American Idioms: Part 52 前言 本文是学习李笑来的Most Common American Idioms这本书的学习笔记&#xff0c;自用。 Github仓库链接&#xff1a;https://github.com/xiaolai/most-common-american-idioms 使用方法: 直接下载下来&#xff08;或者clone到本地…

单例模式实现axios的封装(TS版本)

Axios 是一个基于 promise 网络请求库&#xff0c;作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。 在日常前端开发过程中&#xff0c;我们一…

Java刷题常见的集合类,各种函数的使用以及常见的类型转化等等

前言 相信大家在刷算法题的过程中&#xff0c;好不容易想出来大概的思路&#xff0c;也知道去用哪个集合类&#xff0c;但各个集合类的一些命令都长得太像&#xff0c;很容易将他们弄错&#xff0c;并且在各集合之间的转化也是特别烦人&#xff0c;还有很多实用的函数都知道可…

perf list PMU 缓存事件

事件标识事件解释PMU事件路径l1d_cacheL1数据缓存的访问次数&#xff0c;L1缓存是CPU内部最快的缓存&#xff0c;位于距离CPU核心非常近的位置。armv8_pmuv3/l1d_cache/l1d_cache_lmiss_rd表示从L1数据缓存读取数据时发生缓存未命中的次数。armv8_pmuv3/l1d_cache_lmiss_rd/l1d…