zoukankan      html  css  js  c++  java
  • predixy源码学习

    Predixy是一个代理,代理本质上就是用来转发请求的。其主要功能就是接收客户端的请求,然后把客户端请求转发给redis服务端,在redis服务端处理完消息请求后,接收它的响应,并把这个响应返回给客户端。

    1.整体架构
    Predixy的架构比较简单,它采用多线程的模式。入口代码逻辑也比较清晰:

    Main函数里创建一个代理,然后在init方法里面创建多个处理线程Handler,handler的个数是由配置文件配置,这个一般可以根据CPU的核数来配置。Proxy初始化完成之后,就在run里面起handler线程,Proxy里面有一个用来保存handler的vector,创建的多个handler之间基本上是相互独立。整个predixy的流程基本就在handler的run方法中,入口是Handler::run()

    Handler是一个事件循环。mEventLoop是创建Handler时初始化的Multiplexor(后续的Multiplexor我们统一以epoll为例),在mEventLoop->wait里面处理注册到epoll上的fd事件。
    注册到epoll里面的事件主要有:
    1、连接事件:在创建Handler的时候,创建mEventLoop,并把监听事件加入到epoll里面
    2、已经建立连接的客户端的收包和回包
    3、到redis服务端的请求转发和回包消息转发给客户端
    主要的两个函数是wait和postEvent:wait里面主要是处理客户端请求读取,redis回包读取,postEvent主要是处理客户端请求转发给redis和redis回包转发给客户端;在wait中收到的包,会在接下来的postEvent中立马尽量发出去,如果一次发送不完,会注册写事件到epoll中,等可写了会进行再次发送。

    2.事件循环
    上面介绍了大体的逻辑,下面我们来仔细看看事件循环里两个主要的函数wait和postEvent。
    Wait的入口在EpollMultiplexor::wait,有事件来了之后,就循环调用handler的handlerEvent方法,入口在Handler::handleEvent:

    这里我们看到有3种连接的类型
    1、ListenType
    监听的socket,在创建监听socket的时候指定的类型
    2、AcceptType
    连接建立后,就会创建一个AcceptConnection,类型转为AcceptType
    3、ConnectType
    客户端有消息过来时,连接是AcceptConnection,读取了客户端消息,在转发时,需要拿到predixy和redis的连接来做这个转发的动作,这时候的连接就是ConnectConnection。
    handleListenEvent:主要是处理接收连接,把新来的连接加入到epoll
    handleAcceptConnectionEvent:读取客户端的消息AcceptConnection::readEvent,如果有可写事件,则会addPostEvent,使得前面没发完的消息可以再次发送。
    readEvent里面会读消息-->AcceptConnection::parse解析请求,把请求放入mRequests队列--->Handler::handleRequest

    然后根据路由,选择server,getConnectConnection,把消息加入到mSendRequests队列。
    handleConnectConnectionEvent:和handleAcceptConnectionEvent是类似的,不过处理的是响应的消息,读从redis到predixy的回包,然后ConnectConnection::handleResponse
    PostEvent入口是Handler::postEvent(),这里会统一处理写,postConnectConnectionEvent是predixy往redis写消息,写了之后会把请求放到mSentRequests队列;postAcceptConnectionEvent是predixy往客户端写。

    这里的写操作都会先进行一个合包fill,然后掉接口写,如果写完了,会把写事件删掉,如果一次没写完,会把写事件加到epoll里面,等epoll可写了就会返回写事件。

    3.排队机制
    从上面我们可以看到predixy的处理流程涉及到几个队列,主要是下面3个:
    AcceptConnection::mRequests : 客户端连接的请求队列
    ConnectConncetion::mSendRequests : 到redis端的待发送队列
    ConnectConncetion::mSentRequests : 到redis端的已发送队列
    这几个队列的配合关系如下图所示:

    1、客户端发送请求给predixy,predixy先将req消息放到AcceptConnection::mRequests队列
    2、解析消息后,将req放到对应redis连接的ConnectConncetion::mSendRequests队列里面,这里可能有多个redis,所以这一步需要先路由到具体的server
    3、Predixy转发请求给redis
    4、将req从ConnectConncetion::mSendRequests 转移到ConnectConncetion::mSentRequests
    5、Redis处理消息完毕回响应给predixy
    6、Predixy将响应消息对应的req从ConnectConncetion::mSentRequests移除,并通知对应的AcceptConnection
    7、AcceptConnection接收到响应后吧消息返回给客户端并出队
    这里有一个细节,predixy需要保证同一个连接先发送的请求需要先回复,因此predixy收到redis回包后,从mSentRequests中取出头部请求,只有当mRequests队首的请求完成了才能回,否则在mRequests队列里面等待

    postEvent在回包的时候,则会从队首开始,对所有done为true的请求处理回包。

    4.路由选择
    在predixy向redis转发请求时,需要先根据路由策略取到server,得知是向哪个redis转发,然后从server获取ConnectConncetion进行操作。

    在Proxy::init中会先根据配置初始化ServerPool,ServerPool里面有一个个ServerGroup,ServerGroup里面是Server

    StandaloneServerPool和ClusterServerPool中serverGroup的组织方式不同:StandaloneServerPool中的mGroupPool是配置文件指定哪些server属于同一个ServerGroup,
    此外,ClusterServerPool中,每个ServerGroup负责若干个slots,而StandaloneServerPool,每个group也负责一部分数据,负责的数据的方式取决于mDist的值,有modula和random 2种。StandaloneServerPool和ClusterServerPool都有std::vector mServerPool,用于实现randomkey。
    从ServerPool中获取server分2个步骤:
    1、找到ServerGroup,如何寻找取决于数据分布方式
    2、从ServerGroup中找一个server,如何寻找取决于命令的读写属性,节点的角色和权重
    每种ServerPool需要实现下面几个接口:
    GetServerFunc mGetServerFunc; //从ServerPool获取server
    RefreshRequestFunc mRefreshrequestFunc; //发送请求,刷新server信息
    HandleResponseFunc mHandleResponseFunc; //处理刷新server请求的回包
    Server中只有server的ip和port信息,具体的连接由每个handler自己维护连接池:mConnectionPool

    5.共享连接和独占连接
    Predixy有2种使用到后端连接的方式
    SharedConnection :无上下文的请求使用
    PrivateConnection :有上下文的请求使用
    每个handler为每个Server都维护2类连接池mPrivateConns和mShareConns。PrivateConnection是每个带Context的请求独占一个,一般这类请求通过长连接发起,predixy上维护的请求数量也是有限的。

    6.多key命令的拆分
    多key命令,例如mget、mset、de、unlink这类命令在parse的时候,会拆开。例如:mget a b c会拆分成mget a,mget b。Mget c;一个请求被拆分成多个请求依次入队列,会将第一个设置为leader,后续的设置为follower,在处理回包的时候,对后端返回的请求进行调整。

  • 相关阅读:
    通用测试用例(转载)
    微信小程序开发-使用阿里巴巴矢量图标
    flask框架启服务+json格式入参+postman获取上个接口的token作为下个接口的入参+关联接口【多测师_王sir】
    this.$set的正确使用
    vue中异步函数async和await的用法
    Tornado 异步协程coroutine原理
    nvm安装配置
    python基础
    数组for循环方法总结
    react 之props传值
  • 原文地址:https://www.cnblogs.com/sigma0-/p/12823821.html
Copyright © 2011-2022 走看看