本节主要介绍缓冲相关的传输类,缓存的作用就是为了提高读写的效率。Thrift在实现缓存传输的时候首先建立一个缓存的基类,然后需要实现缓存功能的类都可以直接从这个基类继承。下面就详细分析这个基类以及一个具体的实现类。
  缓存基类TBufferBase
  缓存基类就是让传输类所有的读写函数都提供缓存来提高性能。它在通常情况下采用memcpy来设计和实现快路径的读写访问操作,这些操作函数通常都是小、非虚拟和内联函数。TBufferBase是一个抽象的基类,子类必须实现慢路径的读写函数等操作,慢路径的读写等操作主要是为了在缓存已经满或空的情况下执行。首先看看缓存基类的定义,代码如下:

  class TBufferBase : public TVirtualTransport<TBufferBase> {
   public:
   uint32_t read(uint8_t* buf, uint32_t len) {//读函数
   uint8_t* new_rBase = rBase_ + len;//得到需要读到的缓存边界
   if (TDB_LIKELY(new_rBase <= rBound_)) {//判断缓存是否有足够的数据可读,采用了分支预测技术
   std::memcpy(buf, rBase_, len);//直接内存拷贝
   rBase_ = new_rBase;//更新新的缓存读基地址
   return len;//返回读取的长度
   }
   return readSlow(buf, len);//如果缓存已经不能够满足读取长度需要就执行慢读
   }
   uint32_t readAll(uint8_t* buf, uint32_t len) {
   uint8_t* new_rBase = rBase_ + len;//同read函数
   if (TDB_LIKELY(new_rBase <= rBound_)) {
   std::memcpy(buf, rBase_, len);
   rBase_ = new_rBase;
   return len;
   }
   return apache::thrift::transport::readAll(*this, buf, len);//调用父类的
   }
   void write(const uint8_t* buf, uint32_t len) {//快速写函数
   uint8_t* new_wBase = wBase_ + len;//写入后的新缓存基地址
   if (TDB_LIKELY(new_wBase <= wBound_)) {//判断缓存是否有足够的空间可以写入
   std::memcpy(wBase_, buf, len);//内存拷贝
   wBase_ = new_wBase;//更新基地址
   return;
   }
   writeSlow(buf, len);//缓存空间不足就调用慢写函数
   }
   const uint8_t* borrow(uint8_t* buf, uint32_t* len) {//快速路径借
   if (TDB_LIKELY(static_cast<ptrdiff_t>(*len) <= rBound_ - rBase_)) {//判断是否足够借的长度
   *len = static_cast<uint32_t>(rBound_ - rBase_);
   return rBase_;//返回借的基地址
   }
   return borrowSlow(buf, len);//不足就采用慢路径借
   }
   void consume(uint32_t len) {//消费函数
   if (TDB_LIKELY(static_cast<ptrdiff_t>(len) <= rBound_ - rBase_)) {//判断缓存是否够消费
   rBase_ += len;//更新已经消耗的长度
   } else {
   throw TTransportException(TTransportException::BAD_ARGS,
   "consume did not follow a borrow.");//不足抛异常
   }
   }
   protected:
   virtual uint32_t readSlow(uint8_t* buf, uint32_t len) = ;//慢函数
   virtual void writeSlow(const uint8_t* buf, uint32_t len) = ;
   virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = ;
   TBufferBase()
   : rBase_(NULL)
   , rBound_(NULL)
   , wBase_(NULL)
   , wBound_(NULL)
   {}//构造函数,把所有的缓存空间设置为NULL
   void setReadBuffer(uint8_t* buf, uint32_t len) {//设置读缓存空间地址
   rBase_ = buf;//读缓存开始地址
   rBound_ = buf+len;//读缓存地址界限
   }
   void setWriteBuffer(uint8_t* buf, uint32_t len) {//设置写缓存地址空间
   wBase_ = buf;//起
   wBound_ = buf+len;//边界
   }
   virtual ~TBufferBase() {}
   uint8_t* rBase_;//读从这儿开始
   uint8_t* rBound_;//读界限
   uint8_t* wBase_;//写开始地址
   uint8_t* wBound_;//写界限
  };

  从TBufferBase定义可以看出,它也是从虚拟类继承,主要采用了memcpy函数来实现缓存的快速读取,在判断是否有足够的缓存空间可以操作时采用了分支预测技术来提供代码的执行效率,且所有快路径函数都是非虚拟的、内联的小代码量函数。下面继续看看一个具体实现缓存基类的一个子类的情况!
  TBufferedTransport
  缓存传输类是从缓存基类继承而来,它对于读:实际读数据的大小比实际请求的大很多,多余的数据将为将来超过本地缓存的数据服务;对于写:数据在它被发送出去以前将被先写入内存缓存。
  缓存的大小默认是512字节(代码:static const int DEFAULT_BUFFER_SIZE = 512;),提供多个构造函数,可以只指定一个传输类(另一层次的)、也可以指定读写缓存公用的大小或者分别指定。因为它是一个可以实际使用的缓存类,所以需要实现慢读和慢写功能的函数。它还实现了打开函数open、关闭函数close、刷新函数flush等,判断是否有数据处于未决状态函数peek定义和实现如下:

    bool peek() {
   if (rBase_ == rBound_) {//判断读的基地址与读边界是否重合了,也就是已经读取完毕
   setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//是:重新读取底层来的数据
   }
   return (rBound_ > rBase_);//边界大于基地址就是有未决状态数据
   }
  下面继续看看慢读函数和慢写函数的实现细节(快读和快写继承基类的:也就是默认的读写都是直接从缓存中读取,所谓的快读和快写)。慢读函数实现如下(详细注释):
  uint32_t TBufferedTransport::readSlow(uint8_t* buf, uint32_t len) {
   uint32_t have = rBound_ - rBase_;//计算还有多少数据在缓存中
  
   // 如果读取缓存中已经存在的数据不能满足我们,
   // 我们(也仅仅在这种情况下)应该才从慢路径读数据。
   assert(have < len);
  
   // 如果我们有一些数据在缓存,拷贝出来并返回它
   // 我们不得不返回它而去尝试读更多的数据,因为我们不能保证
   // 下层传输实际有更多的数据, 因此尝试阻塞式读取它。
   if (have > ) {
   memcpy(buf, rBase_, have);//拷贝数据
   setReadBuffer(rBuf_.get(), );//设置读缓存,基类实现该函数
   return have;//返回缓存中已经存在的不完整数据
   }
  
   // 在我们的缓存中没有更多的数据可用。从下层传输得到更多以达到buffer的大小。
   // 注意如果len小于rBufSize_可能会产生多种场景否则几乎是没有意义的。
   setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//读取数据并设置读缓存
  
   // 处理我们已有的数据
   uint32_t give = std::min(len, static_cast<uint32_t>(rBound_ - rBase_));
   memcpy(buf, rBase_, give);
   rBase_ += give;
  
   return give;
  }

  慢读函数主要考虑的问题就是缓存中还有一部分数据,但是不够我们需要读取的长度;还有比较麻烦的情况是虽然现在缓存中没有数据,但是我们从下层传输去读,读取的长度可能大于、小于或等于我们需要读取的长度,所以需要考虑各种情况。下面继续分析慢写函数实现细节:

  void TBufferedTransport::writeSlow(const uint8_t* buf, uint32_t len) {
   uint32_t have_bytes = wBase_ - wBuf_.get();//计算写缓存区中已有的字节数
   uint32_t space = wBound_ - wBase_;//计算剩余写缓存空间
   // 如果在缓存区的空闲空间不能容纳我们的数据,我们采用慢路径写(仅仅)
   assert(wBound_ - wBase_ < static_cast<ptrdiff_t>(len));
  
//已有数据加上需要写入的数据是否大于2倍写缓存区或者缓存区为空
   if ((have_bytes + len >= *wBufSize_) || (have_bytes == )) {
   if (have_bytes > ) {//缓存大于0且加上需要再写入数据的长度大于2倍缓存区
   transport_->write(wBuf_.get(), have_bytes);//先将已有数据写入下层传输
   }
   transport_->write(buf, len);//写入这次的len长度的数据
   wBase_ = wBuf_.get();//重新得到写缓存的基地址
   return;
   }
  
   memcpy(wBase_, buf, space);//填充我们的内部缓存区为了写
   buf += space;
   len -= space;
   transport_->write(wBuf_.get(), wBufSize_);//写入下层传输
  
   assert(len < wBufSize_);
   memcpy(wBuf_.get(), buf, len);//拷贝剩余的数据到我们的缓存
   wBase_ = wBuf_.get() + len;//重新得到写缓存基地址
   return;
  }

  慢写函数也有棘手的问题,就是我们应该拷贝我们的数据到我们的内部缓存并且从那儿发送出去,或者我们应该仅仅用一次系统调用把当前内部写缓存区的内容写出去,然后再用一次系统调用把我们当前需要写入长度为len的数据再次写入出去。如果当前缓存区的数据加上我们这次需要写入数据的长度至少是我们缓存区长度的两倍,我们将不得不至少调用两次系统调用(缓存区为空时有可能例外),那么我们就不拷贝了。否则我们就是按顺序递加的。具体实现分情况处理,最后我们在看看慢借函数的实现,借相关函数主要是为了实现可变长度编码。慢借函数实现细节如下:

  const uint8_t* TBufferedTransport::borrowSlow(uint8_t* buf, uint32_t* len) {
   (void) buf;
   (void) len;
   return NULL;//默认返回空
  }

  在这个类我们可以看出,它什么也没有做,只是简单的返回NULL,所以需要阻塞去借。按照官方的说法,下面两种行为应该当前的版本中实现,在将来的版本可能会发生改变:
  如果需要借的长度最多为缓存区的长度,那么永远不会返回NULL。依赖底层传输,它应该抛出一个异常或者永远不会挂掉;
  一些借用请求可能内部字节拷贝,如果借用的长度最多是缓存区的一半,那么不去内部拷贝。为了优化性能保存这个限制。

thrift之TTransport层的缓存传输类TBufferedTransport和缓冲基类TBufferBase的更多相关文章

  1. thrift之TTransport层的内存缓存传输类TMemoryBuffer

    内存缓存是简单的在内存进行读写操作的一种传输,任何时候想在上面写入数据都是放入缓存中,任何时候读操作数据也是来至于缓存.内存缓存的分配使用c语言的malloc类函数,分配的长度是需要长度的两倍,需要考 ...

  2. thrift之TTransport层的堵塞的套接字I/O传输类TSocket

    本节将介绍第一个实现具体传输功能的类TSocket,这个类是基于TCP socket实现TTransport的接口.下面具体介绍这个类的相关函数功能实现. 1.构造函数 分析一个类的功能首先看它的定义 ...

  3. thrift之TTransport层的分帧传输类TFramedTransport

    帧传输类就是按照一帧的固定大小来传输数据,所有的写操作首先都是在内存中完成的直到调用了flush操作,然后传输节点在flush操作之后将所有数据根据数据的有效载荷写入数据的长度的二进制块发送出去,允许 ...

  4. jdbc链接hive报错:java.lang.ClassNotFoundException: org.apache.thrift.transport.TTransport

    写了个jdbc连接hive2的demo,结果报错:java.lang.ClassNotFoundException: org.apache.thrift.transport.TTransport,实际 ...

  5. pyhive -- thrift.transport.TTransport.TTransportException: TSocket read 0 bytes

    Pyhive 远程连接hive出现问题: from pyhive import hive import pandas as pd #Create Hive connection conn = hive ...

  6. thrift.transport.TTransport.TTransportException: Could not start SASL: Error in sasl_client_start (-4) SASL(-4): no mechanism available: No worthy mechs found

    thrift.transport.TTransport.TTransportException: Could not start SASL: Error in sasl_client_start (- ...

  7. QCache 缓存(类似于map的模板类,逻辑意义上的缓存Cache,方便管理,默认类似于LRU的淘汰算法)

    最近在学习缓存方面的知识,了解了缓存(Cache)的基本概念,为什么要使用缓存,以及一些缓存算法(缓存替换),如LRU.LFU.ARC等等. 这些缓存算法的实现过程会使用一些基本的数据结构,如list ...

  8. [转]掌握 ASP.NET 之路:自定义实体类简介 --自定义实体类和DataSet的比较

    转自: http://www.microsoft.com/china/msdn/library/webservices/asp.net/CustEntCls.mspx?mfr=true 发布日期 : ...

  9. 9_13学习完整修改和查询&&实体类,数据访问类

    完整修改和查询:中间变量运用. 1.先查 2.执行操作 ---------------------------------------------------- namespace ADO.NET_小 ...

随机推荐

  1. 一个PHP日历程序

    <?php  //<-------处理通过GET方法提交的变量;开始-------->  if($HTTP_GET_VARS[year]=="")  {      ...

  2. HTML DOM appendChild() 方法

    <!DOCTYPE html> <html> <body> <ul id="myList"> <li>Coffee< ...

  3. spring junit

    转载自 http://blog.csdn.net/funi16/article/details/8691575 在写单元测试的时候,一般是对数据库进行增删改查的操作,这个时候,如果之前删除了某条记录, ...

  4. 简单验证码实现(Ajax)

    前端页面: <!--验证码输入框 --> <input type="text" class="entry" value="" ...

  5. css小tip

    1. <input>标签的默认样式 当在页面中添加一个input标签,当点击输入框时会有一个外边框包裹着,可以使用 : input { outline: none} 去除点击时产生的外边框 ...

  6. windowsAPI popup trace tip(toolTip)

    class UIHELPER_EXPORT ToolTipWindow : public chMessageHandler{ DECLARE_PROCESS_OBJECT(ToolTipWindow) ...

  7. instancetype、id、NSObject的联系和区别

    1.id和instancetype都能省去具体类型,提高代码的通用性.而NSObject *则没有这种功能. 2.instancetype只能用于方法的返回类型,而id用处和NSObject *类似. ...

  8. android中的MVP模式

    1.建立bean public class UserBean { private String mFirstName; private String mLastName; public UserBea ...

  9. linux 下两台电脑之间ssh无密码连接

    例子:在192.168.0.12使用tecmint用户,连接192.168.0.11主机上的sheena用户 Step 1: Create Authentication SSH-Kegen Keys ...

  10. String、StringBuffer、StringBuilder源码解读

    序 好长时间没有认真写博客了,过去的一年挺忙的.负责过数据库.线上运维环境.写代码.Code review等等东西挺多. 学习了不少多方面的东西,不过还是需要回归实际.加强内功,方能扛鼎. 去年学习M ...