[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.rhkb.cn/news/483849.html

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

相关文章

Linux CentOS

​阿里云开源镜像下载链接 https://mirrors.aliyun.com/centos/7/isos/x86_64/ VMware 安装 CentOS7 自定义 下一步 选择稍后安装操作系统 选择 输入 查看物理机CPU内核数量 CtrlShiftEsc 总数不超过物理机内核数量 推荐内存 自选 推荐 推荐 默认 拆分成多个 默认 自定义硬件…

【STM32 Modbus编程】-作为主设备读取线圈和输入

作为主设备读取线圈和输入 文章目录 作为主设备读取线圈和输入1、硬件准备与连接1.1 RS452模块介绍1.2 硬件配置与接线1.3 软件准备2、读取线圈2.1 主设备发送请求2.2 从设备响应请求2.3 主机接收数据3、读取输入4、结果本文将在前面文章的基础上,实现主设备通过ModBus协议对从…

2-2-18-13 QNX系统架构之原生网络(Qnet)

阅读前言 本文以QNX系统官方的文档英文原版资料为参考&#xff0c;翻译和逐句校对后&#xff0c;对QNX操作系统的相关概念进行了深度整理&#xff0c;旨在帮助想要了解QNX的读者及开发者可以快速阅读&#xff0c;而不必查看晦涩难懂的英文原文&#xff0c;这些文章将会作为一个…

多模态COGMEN详解

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

基于频谱处理的音频分离方法

基于频谱处理的音频分离方法 在音频处理领域&#xff0c;音频分离是一个重要的任务&#xff0c;尤其是在语音识别、音乐制作和通信等应用中。音频分离的目标是从混合信号中提取出单独的音频源。通过频谱处理进行音频分离是一种有效的方法&#xff0c;本文将介绍其基本原理、公…

HTML旋转爱心

系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

查看 tomcat信息 jconsole.exe

Where is the jconsole.exe? location: JDK/bin/jconsole.exe

设计模式10:观察者模式(订阅-发布)

系列总链接&#xff1a;《大话设计模式》学习记录_net 大话设计-CSDN博客 参考&#xff1a;简说设计模式——工厂方法模式 - JAdam - 博客园 参考&#xff1a;简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园 一&#xff1a;概述 观察者模式&#xff0…

AIGC 时代的文学:变革与坚守

目录 一.AIGC 带来的文学变革 1.创作方式的改变 2.阅读体验的升级 3.文学市场的重塑 二.文学在 AIGC 时代的坚守 1.人类情感的表达 2.文学的艺术性 3.文学的社会责任 三.AIGC 与人类作家的共生之路 1.相互学习 2.合作创作 3.共同发展 另&#xff1a; 总结 随着人…

Wwise 使用MIDI文件、采样音频

第一种&#xff1a;当采样音频只有一个文件的时候 1.拖入MIDI文件到Interactive Music Hierarchy层级 2.拖入采样音频到Actor-Mixer Hierarchy层级 3.勾选MIDI显示出面板&#xff0c;设置Root Note与采样音频音高相同&#xff0c;这里是C#5 4.播放测试&#xff0c;成功&…

【计算机网络】实验9: 路由信息协议RIP

实验9 路由信息协议RIP 一、实验目的 本实验的主要目的是深入理解RIP&#xff08;路由信息协议&#xff09;的工作原理&#xff0c;以便掌握其在网络中的应用。通过对RIP的学习&#xff0c;我们将探讨该协议如何实现路由选择和信息传播&#xff0c;从而确保数据包能够在网络中…

python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250+个项目、26.6GB)

文章目录 源代码下载地址项目介绍预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250个项目、26.6GB) 预览 项目备注 1、该资源内项目代码都经过测试运行成功&#xff0c;功能ok的情…

深入理解AVL树:结构、旋转及C++实现

1. AVL树的概念 什么是AVL树&#xff1f; AVL树是一种自平衡的二叉搜索树&#xff0c;其发明者是Adelson-Velsky和Landis&#xff0c;因此得名“AVL”。AVL树是首个自平衡二叉搜索树&#xff0c;通过对树的平衡因子进行控制&#xff0c;确保任何节点的左右子树高度差最多为1&…

spark-sql配置教程

1.前期准备 &#xff08;1&#xff09;首先要把hadoop集群&#xff0c;hive和spark等配置好 hadoop集群&#xff0c;hive的配置可以看看这个博主写的博客 大数据_蓝净云的博客-CSDN博客 或者看看黑马程序员的视频 黑马程序员大数据入门到实战教程&#xff0c;大数据开发必…

Git分布式版本控制工具 Git基本概念、Git工作流程、Git常用命令、Git远程仓库、IDEA操作Git

目录 ​​​​​​ 1.Git基本概念 1.1 概述 1.1.1 开发中的实际场景 1.1.2 版本控制器的方式 1.1.2.1 集中式版本控制工具(SVN) 1.1.2.2 分布式版本控制工具(Git) 2.概述git工作流程 3.Git常用命令 3.1 Git环境配置 3.1.1 下载与安装 3.1.2 基本配置 3.1.3 为常用指令配置别名&…

“停车费“ 在英语中常见的表达方式,柯桥职场英语生活口语商务英语学习

“停车费”用英语怎么说&#xff1f; "停车费" 在英语中有多种表达方式&#xff0c;最常见的是&#xff1a; Parking fee: 这是最直接的翻译&#xff0c;用于各种停车场、路边停车等情况。 Parking c15857575#376harge: 与 parking fee 意思相近&#xff0c;但有时更…

第31天:安全开发-JS应用WebPack打包器第三方库JQuery安装使用安全检测

时间轴&#xff1a; 演示案例&#xff1a; 打包器-WebPack-使用&安全 第三方库-JQuery-使用&安全 打包器-WebPack-使用&安全 参考&#xff1a;https://mp.weixin.qq.com/s/J3bpy-SsCnQ1lBov1L98WA Webpack 是一个模块打包器。在 Webpack 中会将前端的所有资源…

Redis使用场景-缓存-缓存雪崩

前言 之前在针对实习面试的博文中讲到Redis在实际开发中的生产问题&#xff0c;其中缓存穿透、击穿、雪崩在面试中问的最频繁&#xff0c;本文加了图解&#xff0c;希望帮助你更直观的了解缓存雪崩&#x1f600; &#xff08;放出之前写的针对实习面试的关于Redis生产问题的博…

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

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

高通---Camera调试流程及常见问题分析

文章目录 一、概述二、Camera配置的整体流程三、Camera的代码架构图四、Camera数据流的传递五、camera debug FAQ 一、概述 在调试camera过程中&#xff0c;经常会遇到各种状况&#xff0c;本篇文章对camera调试的流程进行梳理。对常见问题的提供一些解题思路。 二、Camera配…