【Codis源码】codis-proxy处理流程下(三)

15 篇文章 2 订阅

前言:

接着上一章讲,上一章讲了loopReader。这一块会延续上一章节继续往下讲。

golang版本: go1.13.5 darwin/amd64
codis版本:codis 3.2
调试工具:dlv
codis源码:
https://github.com/CodisLabs/codis/

(一)proxy处理流程

在这里插入图片描述
上一章中讲proxy流程时只讲了session连接部分,这一章把完整的流程串一串。
如图中所示,codis-proxy会分为两大部分:
1)一部分是客户端交互部分,session的两个方法loopWriter和loopReader。
loopWriter负责返回客户端信息,loopReader负责分发slots部分。
2)另一部分是服务交互部分,backendConn的两个方法loopWriter和loopReader。
loopWriter负责写入服务端数据,loopReader部分负责处理返回客户端等待。

(二)源码解析

2.1 RequestChan结构与Request结构

type RequestChan struct {
   lock sync.Mutex  //互斥锁
   cond *sync.Cond  //条件锁

   data []*Request  
   buff []*Request

   waits  int      //等待数
   closed bool     //是否关闭
}

每一个session都有一个RequestChan结构。

type Request struct {
   Multi []*redis.Resp    //保存请求命令,按redis的resp协议类型将请求保存到Multi字段中
   Batch *sync.WaitGroup  /*返回响应时,会在Batch处等待,r.Batch.Wait(),
                           *所以可以做到当请求执行完成后才会执行返回函数*/
   Group *sync.WaitGroup

   Broken *atomic2.Bool

   OpStr string           //输入指令,
   OpFlag

   Database int32        //数据库编号
   UnixNano int64        //精确的unix时间 

   *redis.Resp           //保存响应数据,也是redis的resp协议类型
   Err error

   Coalesce func() error //聚合函数,适用于mget/mset等需要聚合响应的操作命令
}

上一章讲到session中的loopReader将请求放入RequestChan的data字段中,并且将请求放入bc.input 管道中。在Batch处加1。而bc.input管道中存放的则是一个Request结构。如下图:
在这里插入图片描述

下图中为Request交互时原型:
在这里插入图片描述

2.2 BackendConn的loopWriter处理

在这里插入图片描述
图中为dlv调试,BackendConn的loopWriter会开启一堆协程去轮询。

在这里插入图片描述
在bc.input 管道设置后,会在loopWriter的for r := range bc.input 轮询取出数据并且处理。

在这里插入图片描述
tasks也是一个管道,是BackendConn中 loopWriter和loopReader交流信息。

loopWriter源码

func (bc *BackendConn) loopWriter(round int) (err error) {
  // 。。。 省略
   c, tasks, err := bc.newBackendReader(round, bc.config) //调用newBackendReader函数。

   if err != nil {
      return err
   }
    
    //。。。省略

   for r := range bc.input {
      if r.IsReadOnly() && r.IsBroken() {
         bc.setResponse(r, nil, ErrRequestIsBroken)
         continue
      }
      if err := p.EncodeMultiBulk(r.Multi); err != nil { //将请求编码发送server
         return bc.setResponse(r, nil, fmt.Errorf("backend conn failure, %s", err))
      }
      if err := p.Flush(len(bc.input) == 0); err != nil {
         return bc.setResponse(r, nil, fmt.Errorf("backend conn failure, %s", err))
      } else {
         tasks <- r //将Request请求放入tasks管道中
      }
   }
   return nil
}

bc.newBackendReader的tasks也是一个存放*Request的channel,用来BackendConn中的loopWriter和loopReader交流信息。p.EncodeMultiBulk函数是将请求编码发送到server,这里的server是codis server。codis server是改版后的redis服务。

2.3 BackendConn的loopReader处理

func (bc *BackendConn) newBackendReader(round int, config *Config) (*redis.Conn, chan<- *Request, error) {
    //...省略

   if err := bc.verifyAuth(c, config.ProductAuth); err != nil { //验证权限
      c.Close()
      return nil, nil, err
   }
   if err := bc.selectDatabase(c, bc.database); err != nil { //选择数据库
      c.Close()
      return nil, nil, err
   }

   tasks := make(chan *Request, config.BackendMaxPipeline) ///创建task这个管道并且返回给loopWriter
   go bc.loopReader(tasks, c, round) //启动loopReader协程

   return c, tasks, nil
}

newBackendReader函数创建了tasks返回给loopWriter,并且启动loopReader协程。

loopReader函数:

func (bc *BackendConn) loopReader(tasks <-chan *Request, c *redis.Conn, round int) (err error) {
   //...省略
   for r := range tasks { //从tasks取出Request
      resp, err := c.Decode()
      if err != nil {
         return bc.setResponse(r, nil, fmt.Errorf("backend conn failure, %s", err))
      }
     //...省略
      bc.setResponse(r, resp, nil) //响应
   }
   return nil
}

从tasks中取出响应,设置到bc.setResponse函数中。
在这里插入图片描述
图中为设置到bc.setResponse函数的dlv调试结果。

setResponse函数:

func (bc *BackendConn) setResponse(r *Request, resp *redis.Resp, err error) error {
   r.Resp, r.Err = resp, err
   if r.Group != nil {
      r.Group.Done()
   }
   if r.Batch != nil {
      r.Batch.Done()  //r.Batch.Add(1)返回session中loopWriter的r.Batch.Wait等待
   }
   return err
}

在这里插入图片描述
图中为dlv调试结果,r.Batch设置时触发r.Batch.Done() 返回。

在这里插入图片描述
图中为dlv调试结果,r.Batch.Done() 触发session中loopWriter的r.Batch.Wait等待返回。

总结:

1.codis-proxy会分为两大部分:一部分是客户端交互部分,session的两个方法loopWriter和loopReader。
loopWriter负责返回客户端信息,loopReader负责分发slots部分。另一部分是服务交互部分,backendConn的两个方法loopWriter和loopReader。loopWriter负责写入服务端数据,loopReader部分负责处理返回客户端等待。
2.bc.input是连接session中loopReader与backendConn中loopWriter交流信息。
3.tasks是backendConn中loopWriter与loopReader的交流信息。
4.BackendConn中loopReader的r.Batch.Done() 触发返回session中loopWriter的r.Batch.Wait。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Codis是一个分布式Redis解决方案,对于上层的应用来说,连接到CodisProxy和连接原生的RedisServer没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Redis一样使用,Codis底层会处理请求的转发,不停机的数据迁移等工作,所有后边的一切事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的Redis服务。 Codis由四部分组成: CodisProxy(codis-proxy) CodisManager(codis-config) CodisRedis(codis-server) ZooKeeper codis-proxy是客户端连接的Redis代理服务,codis-proxy本身实现了Redis协议,表现得和一个原生的Redis没什么区别(就像Twemproxy),对于一个业务来说,可以部署多个codis-proxycodis-proxy本身是无状态的。 codis-config是Codis的管理工具,支持包括,添加/删除Redis节点,添加/删除Proxy节点,发起数据迁移等操作。codis-config本身还自带了一个httpserver,会启动一个dashboard,用户可以直接在浏览器上观察Codis集群的运行状态。 codis-server是Codis项目维护的一个Redis分支,基于2.8.13开发,加入了slot的支持和原子的数据迁移指令。Codis上层的codis-proxycodis-config只能和这个版本的Redis交互才能正常运行。 Codis依赖ZooKeeper来存放数据路由表和codis-proxy节点的元信息,codis-config发起的命令都会通过ZooKeeper同步到各个存活的codis-proxyCodis支持按照Namespace区分不同的产品,拥有不同的productname的产品,各项配置都不会冲突。 Codis特性: 自动平衡 使用非常简单 图形化的面板和管理工具 支持绝大多数 Redis 命令,完全兼容 twemproxy 支持 Redis 原生客户端 安全而且透明的数据移植,可根据需要轻松添加和删除节点 提供命令行接口 RESTful APIs

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值