[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 默认 tcp
	Network 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-15
	DB 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#timeouts
	ContextTimeoutEnabled 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 {
	*baseClient
	cmdable
	hooksMixin
}
  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
	// 如果只有类型没有变量这里会创建一个和类型名称相同的成员变量
	cmdable
	hooksMixin
}
// 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.Process
	c.initHooks(hooks{
		dial:       c.baseClient.dial,
		process:    c.baseClient.process,
		pipeline:   c.baseClient.processPipeline,
		txPipeline: c.baseClient.processTxPipeline,
	})
}
type hooksMixin struct {
	// 共享锁
	hooksMu *sync.Mutex

	slice   []Hook
	initial hooks
	current 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 := attempt

		retry, 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Solving the Makefile Missing Separator Stop Error in VSCode

1. 打开 Makefile 并转换缩进 步骤 1: 在 VSCode 中打开 Makefile 打开 VSCode。使用文件浏览器或 Ctrl O&#xff08;在 Mac 上是 Cmd O&#xff09;打开你的 Makefile。 步骤 2: 打开命令面板 按 Ctrl Shift P&#xff08;在 Mac 上是 Cmd Shift P&#xff09;&…

交换机四大镜像(端口镜像、流镜像、VLAN镜像、MAC镜像)应用场景、配置实例及区别对比

在网络管理中&#xff0c;端口镜像、流镜像、VLAN镜像和MAC镜像都是用于监控和分析网络流量的重要技术。 端口镜像&#xff08;Port Mirroring&#xff09; 定义&#xff1a;端口镜像是将一个或多个源端口的流量复制到一个目标端口&#xff0c;以便于网络管理员能够监控和分析…

Unity数据持久化

二进制数据持久化的好处&#xff1a;安全、效率高、利于网络通信 文章目录 补充文件夹相关EditorResourcesSteammingAsset 序列化和反序列化序列化反序列化 二进制数据持久化转换为字节数据文件操作写入字节&#xff1a;读取字节安全关闭文件夹操作操作文件夹目录信息和文件信息…

【机器学习】机器学习的基本分类-监督学习-随机森林(Random Forest)

随机森林是一种基于集成学习&#xff08;Ensemble Learning&#xff09;思想的算法&#xff0c;由多个决策树构成。它通过结合多棵决策树的预测结果来提升模型的泛化能力和准确性&#xff0c;同时减少过拟合的风险。 1. 随机森林的核心思想 多样性&#xff1a; 随机森林通过引…

中国矿业大学《2024年868自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《中国矿业大学868自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

SQL SERVER 2016 AlwaysOn 无域集群+负载均衡搭建与简测

之前和很多群友聊天发现对2016的无域和负载均衡满心期待&#xff0c;毕竟可以简单搭建而且可以不适用第三方负载均衡器&#xff0c;SQL自己可以负载了。windows2016已经可以下载使用了&#xff0c;那么这回终于可以揭开令人憧憬向往的AlwaysOn2016 负载均衡集群的神秘面纱了。 …

生产看板到底在看什么?

说起生产看板&#xff0c;可能很多人脑海里冒出来的画面是&#xff1a;车间里一块挂在墙上的大板子&#xff0c;上面贴满了各式各样的卡片、表格&#xff0c;甚至还有几个闪闪发光的指示灯。但是&#xff0c;无论是精益生产方式代表——丰田&#xff0c;还是当下以“智能制造”…

数据链路层(四)---PPP协议的工作状态

1 PPP链路的初始化 通过前面几章的学习&#xff0c;我们学了了PPP协议帧的格式以及组成&#xff0c;那么对于使用PPP协议的链路是怎么初始化的呢&#xff1f; 当用户拨号上网接入到ISP后&#xff0c;就建立起了一条个人用户到ISP的物理链路。这时&#xff0c;用户向ISP发送一…

第四届全国过程模拟与仿真大会召开,积鼎科技相伴大会6年成长

第四届全国过程模拟与仿真学术会议于2024年11月29日-12月2日在广州圆满召开。积鼎科技&#xff0c;作为自主流体仿真软件研发的领航企业&#xff0c;与大会相伴四年&#xff0c;自首届以来一直积极参与其中&#xff0c;见证了大会从初创到逐渐壮大的全过程。每一次参会&#xf…

【RDMA】RDMA read和write编程实例(verbs API)

WRITE|READ编程&#xff08;RDMA read and write with IB verbs&#xff09; &#xff08;本文讲解的示例代码在&#xff1a;RDMA read and write with IB verbs | The Geek in the Corner&#xff09; 将 RDMA 与verbs一起使用非常简单&#xff1a;首先注册内存块&#xff0c…

JAVAWeb之CSS学习

前引 CSS&#xff0c;层叠样式表&#xff08;Cascading Style Sheets&#xff09;&#xff0c;能够对网页中元素位置的排版进行像素级精确控制&#xff0c;支持几乎所有的字体字号样式&#xff0c;拥有网页对象和模型样式编辑的能力&#xff0c;简单来说&#xff0c;美化页面。…

Linux CentOS

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

gazebo 仿真阶段性问题汇总三

目录 报错&#xff1a;关节设置为 revolute 缺少 limit 属性解决方法 轮子转向左右相反解决方法 rviz2只显示一次雷达数据&#xff0c;刷新后消失解决方法 修改2D雷达为3D雷达2d 雷达3D 雷达 报错&#xff1a;关节设置为 revolute 缺少 limit 属性 revolute 表示是有限制的旋转…

python学习笔记14 python中的库,常见的内置库(random、hashlib、json、时间、os)

接着上一篇python学习记录的内容&#xff0c;今天我们来看库 内置库&#xff1a;time、random、os、json…第三方库&#xff1a;requests,pandas,numpy…自定义库&#xff1a;xxx.py 导入的方式 import 直接将一个库导入进来 import base.day04.login_demo as t # 导入login_…

微信小程序版小米商城的搭建流程详解!

很多初学微信小程序语法的同学&#xff0c;可能不知道如何布局和搭建一个项目&#xff0c;下面我将讲解初学者如何搭建项目和注意事项。 一、 app.json的配置 {"pages": ["pages/index/index","pages/classification/classification","pag…

PETR:Position Embedding Transformation forMulti-View 3D Object Detection

全文摘要 本文介绍了一种名为“位置嵌入变换&#xff08;PETR&#xff09;”的新方法&#xff0c;用于多视角三维物体检测。该方法将三维坐标的位置信息编码为图像特征&#xff0c;并产生具有三维位置感知能力的特征。通过对象查询可以感知这些特征并进行端到端的目标检测。在…

2024前端框架年度总结报告(二):新生qwik+solid和次新生svelte+Astro对比 -各自盯着前端的哪些个痛点 - 前端的区域发展差异

引言 2024年&#xff0c;前端开发依然是技术领域的热点之一。随着 Web 应用的日益复杂&#xff0c;前端框架的更新换代也加速了。尽管 React、Vue 和 Angular 老牌框架年度总结 等“老牌”框架仍然占据着主流市场&#xff0c;但一些新兴的框架在不断挑战这些“巨头”的地位&am…

多模态大语言模型的对比

简介 文章主要对比了包括 VideoLLaMA 2 、CogVLM2-video 、MiniCPM-V等模型 目前主流的多模态视觉问答大模型&#xff0c;大部分采用视觉编码器、大语言模型、图像到文本特征的投影模块 目录 简介1. VideoLLaMA 21.1 网络结构1.2 STC connector具体的架构 2. MiniCPM-V 2.62.…

美畅物联丨智能监控,高效运维:视频汇聚平台在储能领域的实践探索

在当今全球能源格局不断变化的大背景下&#xff0c;对清洁能源的需求正以惊人的速度增长。储能项目作为平衡能源供需、提升能源利用效率的关键环节&#xff0c;其规模和复杂度也在不断攀升。在储能项目的运营管理过程中&#xff0c;安全监控、设备运维以及数据管理等方面面临着…

node.js实现分页,jwt鉴权机制,token,cookie和session的区别

文章目录 1. 分⻚功能2. jwt鉴权机制1.jwt是什么2.jwt的应用3.优缺点 3. cookie&#xff0c;token&#xff0c;session的对比 1. 分⻚功能 为什么要分页 如果数据量很⼤&#xff0c;⽐如⼏万条数据&#xff0c;放在⼀个⻚⾯显⽰的话显然不友好&#xff0c;这时候就需要采⽤分⻚…