前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 源码分析:go-redis

golang 源码分析:go-redis

作者头像
golangLeetcode
发布2022-08-03 13:59:42
4890
发布2022-08-03 13:59:42
举报

go-redis 的代码实现是一个明显的命令模式,命令模式的优点有将调用操作的对象与知道如何实现该操作的对象解耦,增加新的命令不需要修改现有的类。go-redis 支撑单节点,哨兵模式和集群模式,每一种模式对应的命令接口其实几乎一样的,go-redis就是通过命令模式将命令和每一种client的实现解耦的。

代码语言:javascript
复制
redisdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379", // use default Addr
    Password: "",               // no password set
    DB:       0,                // use default DB
})
代码语言:javascript
复制
redisdb := redis.NewFailoverClient(&redis.FailoverOptions{
    MasterName:    "master",
    SentinelAddrs: []string{":26379"},
})
代码语言:javascript
复制
client := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs:    []string{"redis-cluster.test.com:port"}, //set redis cluster url
        Password: "mypassword",                    //set password
    })

我们以普通client为例进行源码分析,它代码位于:

github.com/go-redis/redis/v8@v8.11.5/redis.go

代码语言:javascript
复制
type Client struct {
  *baseClient
  cmdable
  hooks
  ctx context.Context
}

它通过匿名属性的方式继承了 baseClient,里面包含了链接池的实现。cmdable属性是命令模式的精髓所在。可以看到它其实是一个函数

代码语言:javascript
复制
type cmdable func(ctx context.Context, cmd Cmder) error

但是它比较诡异,它同时也是一个类,因为在go里面函数也是一种基本类型。源码位于github.com/go-redis/redis/v8@v8.11.5/commands.go,以ZRevRange为例,可以看到,这个函数类,在它的属性里面调用了自己。

代码语言:javascript
复制
func (c cmdable) ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd 
        cmd := NewStringSliceCmd(ctx, "zrevrange", key, start, stop)
        _ = c(ctx, cmd)

那么这个函数是如何初始化的呢?答案是Process方法

代码语言:javascript
复制
func NewClient(opt *Options) *Client {
    c.cmdable = c.Process

它调用了hooks的process方法

代码语言:javascript
复制
func (c *Client) Process(ctx context.Context, cmd Cmder) error {
  return c.hooks.process(ctx, cmd, c.baseClient.process)
}

它的参数是基类的process方法

代码语言:javascript
复制
type baseClient struct {
  opt      *Options
  connPool pool.Pooler
  onClose func() error // hook called when client is closed
}

在基类里面实现请求的收发:

代码语言:javascript
复制
func (c *baseClient) process(ctx context.Context, cmd Cmder) error 
        retry, err := c._process(ctx, cmd, attempt)
        err := c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
              err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
      return writeCmd(wr, cmd)
             return wr.WriteArgs(cmd.Args())
             
       err = cn.WithReader(ctx, c.cmdTimeout(cmd), cmd.readReply)
代码语言:javascript
复制
 func (cmd *baseCmd) Args() []interface{} {
  return cmd.args
}

withConn从连接池里取出连接,然后发送请求:

代码语言:javascript
复制
func (c *baseClient) withConn(
  ctx context.Context, fn func(context.Context, *pool.Conn) error,
) error {
            cn, err := c.getConn(ctx)
            err = fn(ctx, cn)
 }
代码语言:javascript
复制
func (c *baseClient) getConn(ctx context.Context) (*pool.Conn, error)
              cn, err := c._getConn(ctx)
代码语言:javascript
复制
func (c *baseClient) _getConn(ctx context.Context) (*pool.Conn, error)
                cn, err := c.connPool.Get(ctx)
                if err := c.initConn(ctx, cn); err != nil {

连接池的代码位于:

github.com/go-redis/redis/v8@v8.11.5/internal/pool/pool.go

代码语言:javascript
复制
type Pooler interface {
  NewConn(context.Context) (*Conn, error)
  CloseConn(*Conn) error	

我们回来看下hooks ,它是一个数组

代码语言:javascript
复制
type hooks struct {
  hooks []Hook
}

它在调用fn之前,调用了所有的前置钩子,调用完成后调用了后置钩子,我们可以在钩子中,对调用redis前后进行一些切面处理。

代码语言:javascript
复制
func (hs hooks) process()
          ctx, retErr = hs.hooks[hookIndex].BeforeProcess(ctx, cmd)
          retErr = fn(ctx, cmd)
          if err := hs.hooks[hookIndex].AfterProcess(ctx, cmd); err != nil {

hook的process方法传入以Cmder,它就是最终要执行的命令

代码语言:javascript
复制
func (hs hooks) process(
  ctx context.Context, cmd Cmder, fn func(context.Context, Cmder) error,
) error
代码语言:javascript
复制
type Hook interface {
  BeforeProcess(ctx context.Context, cmd Cmder) (context.Context, error)
  AfterProcess(ctx context.Context, cmd Cmder) error


  BeforeProcessPipeline(ctx context.Context, cmds []Cmder) (context.Context, error)
  AfterProcessPipeline(ctx context.Context, cmds []Cmder) error
}

Cmder是一个接口

代码语言:javascript
复制
type Cmder interface {
  Name() string
  FullName() string
  Args() []interface{}
  String() string
  stringArg(int) string
  firstKeyPos() int8
  SetFirstKeyPos(int8)
  readTimeout() *time.Duration
  readReply(rd *proto.Reader) error
  SetErr(error)
  Err() error
}

每一个具体命令都实现了上述接口,继承了baseCmd

代码语言:javascript
复制
type IntCmd struct {
  baseCmd
  val int64
}
代码语言:javascript
复制
type baseCmd struct {
  ctx    context.Context
  args   []interface{}
  err    error
  keyPos int8

  _readTimeout *time.Duration
}

以Result()接口为例

代码语言:javascript
复制
func (cmd *StringSliceCmd) Result() ([]string, error) 
      return cmd.Val(), cmd.Err()

StringSliceCmd类似:

代码语言:javascript
复制
type StringSliceCmd struct {
  baseCmd
  val []string
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档