创建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 默认 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 客户端实例。先看下函数调用流程
以下是代码的详细总结:
-  函数签名: func NewClient(opt *Options) *Client- 输入参数:opt *Options,指向Options结构体的指针,用于配置 Redis 客户端。
- 返回值:*Client,返回一个指向Client结构体的指针,表示新创建的 Redis 客户端实例。
 
- 输入参数:
type Client struct {
	*baseClient
	cmdable
	hooksMixin
}
-  初始化 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
	// 如果只有类型没有变量这里会创建一个和类型名称相同的成员变量
	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:执行命令过程中遇到的错误。
 
方法逻辑
-  处理重试间隔 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


![[小白系列]Ubuntu安装教程-安装prometheus和Grafana](https://i-blog.csdnimg.cn/direct/cf3ce92c505e4e0bbcf16c2b712ea2ac.png)
















