[go-redis]客户端的创建与配置说明

news2025/2/28 15:26:14

创建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

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

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

相关文章

【NLP高频面题 - 分词篇】WordPiece 分词器是如何训练的?

【NLP高频面题 - 分词篇】WordPiece 分词器是如何训练的&#xff1f; 重要性&#xff1a;★★ &#x1f4af; NLP Github 项目&#xff1a; NLP 项目实践&#xff1a;fasterai/nlp-project-practice 介绍&#xff1a;该仓库围绕着 NLP 任务模型的设计、训练、优化、部署和应用…

机器学习决策树原理详解

一、引言 在当今蓬勃发展的人工智能与大数据领域&#xff0c;大模型正以前所未有的影响力改变着众多行业的格局。而决策树作为机器学习算法家族中的经典成员&#xff0c;以其简洁直观的特点和广泛的适用性&#xff0c;不仅能独立解决诸多实际问题&#xff0c;更是诸多先进大模…

[小白系列]Ubuntu安装教程-安装prometheus和Grafana

Docker安装prometheus 拉取镜像 docker pull prom/prometheus 配置文件prometheus.yml 在/data/prometheus/建立prometheus.yml配置文件。&#xff08;/data/prometheus/可根据自己需要调整&#xff09; global:scrape_interval: 15s # By default, scrape targets ev…

【Qt之·类QSettings·参数保存】

系列文章目录 文章目录 前言一、概述1.1 QSetting是什么1.2 为什么学习QSetting是重要的 二、不同存储位置的优缺点三、 QSetting的高级用法四、实例演示总结 前言 在当今的应用程序开发中&#xff0c;设置管理是一个至关重要的方面。应用程序的设置包括用户偏好、配置选项和其…

HCIP——VRRP的实验配置

一、VRRP的理论知识 1.1VRRP&#xff08;虚拟路由冗余协议&#xff09;的概述&#xff1a; 通过把几台路由设别联合组成一台虚拟的路由设备&#xff0c;既能够实现网关的备份&#xff0c;又能解决多个网关之间互相冲突的问题。 1.2VRRP状态机&#xff1a; VRRP协议状态机有…

从爱尔兰歌曲到莎士比亚:LSTM文本生成模型的优化之旅

上一篇&#xff1a;《再用RNN神经网络架构设计生成式语言模型》 序言&#xff1a;本文探讨了如何通过多种方法改进模型的输出&#xff0c;包括扩展数据集、调整模型架构、优化训练数据的窗口设置&#xff0c;以及采用字符级编码。这些方法旨在提高生成文本的准确性和合理性&am…

Mysql | 尚硅谷 | 第02章_MySQL环境搭建

Mysql笔记&#xff1a;第02章_MySQL环境搭建 说明&#xff1a;本内容整理自尚硅谷B站MySQL视频>>尚硅谷B站MySQL视频 文章目录 Mysql笔记&#xff1a;第02章_MySQL环境搭建第02章_MySQL环境搭建 1. MySQL的卸载步骤1&#xff1a;停止MySQL服务步骤2&#xff1a;[软件](h…

unity 让文字呈现弧度变化

效果&#xff1a; using UnityEngine; using TMPro; using Core;[ExecuteInEditMode] public class TMTextWrap : MonoBehaviour {private TMP_Text m_TextComponent;public AnimationCurve VertexCurve new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.5f, 1), new …

java抽奖系统(一)2.0

1. 项⽬介绍 1.1 背景 随着数字营销的兴起&#xff0c;企业越来越重视通过在线活动来吸引和留住客⼾。抽奖活动作为⼀种有效的营 销⼿段&#xff0c;能够显著提升⽤⼾参与度和品牌曝光率。于是我们就开发了以抽奖活动作为背景的Spring Boot项⽬&#xff0c;通过这个项⽬提供⼀…

【5G】Spectrum 频谱

频谱是移动运营商的关键资产&#xff0c;可用的频谱是定义移动网络容量和覆盖范围的重要因素。本章讨论了5G的不同频谱选项、它们的特性以及5G早期部署阶段的预期频谱。5G是首个旨在利用大约400 MHz到90 GHz之间所有频段的移动无线系统。5G还设计用于在许可、共享和非许可频谱带…

复现论文:PromptTA: Prompt-driven Text Adapter for Source-freeDomain Generalization

github&#xff1a;zhanghr2001/PromptTA: Source-free Domain Generalization 论文&#xff1a;[2409.14163] PromptTA: Prompt-driven Text Adapter for Source-free Domain Generalization 自己标注&#xff1a;PromptTA: Prompt-driven Text Adapter for Source-free Domai…

电子应用设计方案-43:智能手机充电器系统方案设计

智能手机充电器系统方案设计 一、引言 随着智能手机的广泛应用&#xff0c;对充电器的性能、效率和安全性提出了更高的要求。本方案旨在设计一款高效、安全、兼容多种快充协议的智能手机充电器。 二、系统概述 1. 系统目标 - 提供快速、稳定、安全的充电功能。 - 兼容主流的智…

基于springboot+vue实现的项目评审系统 (源码+L文+ppt)4-116

摘 要 相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低运营人员成本&#xff0c;实现了项目评审系统的标准化、制度化、程序化的管理&#xff0c;有效地防止了项目评审的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准确…

深入了解架构中常见的4种缓存模式及其实现

4种缓存模式 随着应用程序的复杂性日益增加&#xff0c;缓存管理变得至关重要。缓存不仅能有效减轻数据库负载&#xff0c;还能显著提升数据访问速度。选择合适的缓存模式能够在不同的业务场景下发挥出最佳效果。 本文将详细介绍四种常见的缓存模式&#xff1a;Cache-Aside (…

【论文阅读】处理器芯片敏捷设计方法:问题与挑战

作者&#xff1a;包云岗老师 包云岗老师是计算机体系结构方向的大牛&#xff0c;推动了体系结构方面的开源事业! 欢迎对本栏目感兴趣的人学习"一生一芯"~ 学习体会&#xff1a; 已有的软硬件生态系统和开发成本制约了对新结构的探索。但目前仍在几种路线上做尝试~ 1…

Android记单词app(包含数据库)

一、功能与要求 实现功能:设计与开发记单词系统的,系统功能包括用户登录、用户注册、单词操作(单词的添加、查询、修改及删除)以及忘记密码等。 指标要求:通过用户登录、用户注册、单词操作、忘记密等功能的设计与开发,掌握Android常用布局、控件的使用、监听器的设置以及…

数据结构与算法学习笔记----树与图的深度优先遍历

数据结构与算法学习笔记----树与图的深度优先遍历 author: 明月清了个风 first publish time: 2024.12.9 pa⭐️这里只有一道题哈哈。 Acwing 846.树的重心 给定一棵树&#xff0c;树中包含 n n n个节点&#xff08;编号 1 ∼ n 1 \sim n 1∼n&#xff09;和 n − 1 n - 1 n…

TSWIKI知识库软件

TSWIKI 知识库软件介绍 推荐一个适合本地化部署、自托管的知识库软件 TSWIKI介绍 tswiki 是一个适合小团队、个人的知识库、资料管理的软件&#xff0c;所有数据均本地化存储。可以本地化、私有云部署&#xff0c;安装简单。在线预览。 主要功能说明 1、简化的软件依赖和安…

mid360使用cartorapher进行3d建图导航

1. 添加urdf配置文件&#xff1a; 添加IMU配置关节点和laser关节点 <!-- imu livox --> <joint name"livox_frame_joint" type"fixed"> <parent link"base_link" /> <child link"livox_frame" /> <o…

第四十六篇 Vision Transformer论文翻译

论文连接:https://arxiv.org/abs/2010.11929 GitHub:https://github.com/google-research/vision_transformer 摘要 虽然Transformer架构已成为自然语言处理任务的实际标准,但其在计算机视觉中的应用仍然有限。在计算机视觉中,注意力机制要么与卷积网络结合使用,要么在保…