Thrift的同步client调用原理

先来个传统的client同步调用,分析下具体的原理!

---经过简单的debug环境准备后,我们开始看源码

public String helloWorldString(String content) throws org.apache.thrift.TException {
			
			send_helloWorldString(content);//发送请求
			return recv_helloWorldString();
		}
public void send_helloWorldString(String content) throws org.apache.thrift.TException {
			
			helloWorldString_args args = new helloWorldString_args();//主要是做了一些静态对象初始化
			
			args.setContent(content);
			sendBase("helloWorldString", args);
		}

接着往下看

public helloWorldString_args setContent(String content) {
			
			this.content = content;//就是设置content值
			return this;//返回自己
		}

接下来,开始执行

sendBase("helloWorldString", args);

看看里面做了什么事情

protected void sendBase(String methodName, TBase<?,?> args) throws TException {
    sendBase(methodName, args, TMessageType.CALL);
  }

大概就是CALL,方法名,加上参数

看看具体做了什么

private void sendBase(String methodName, TBase<?, ?> args, byte type) throws TException {
		// 看到这里了
		oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_));
		args.write(oprot_);
		oprot_.writeMessageEnd();
		oprot_.getTransport().flush();
	}

所以把这4行解决就可以看懂发送了,继续看

-------------------------------------------------------

先看第一行

@Override
	public void writeMessageBegin(TMessage tMessage) throws TException {
		// 看到这里了
		if (tMessage.type == TMessageType.CALL || tMessage.type == TMessageType.ONEWAY) {
			// 判断是不是发送模式
			super.writeMessageBegin(//多路复用的话,需要重新构造一下
					new TMessage(SERVICE_NAME + SEPARATOR + tMessage.name, tMessage.type, tMessage.seqid));
		} else {
			super.writeMessageBegin(tMessage);
		}
	}

比如name是这样的

Thread-5[1] print n
 n = "HelloWorldService:helloWorldString"
public void writeMessageBegin(TMessage tMessage) throws TException {
    	// 判断是不是发送模式
    	//这里的concreteProtocol是class org.apache.thrift.protocol.TBinaryProtocol
        concreteProtocol.writeMessageBegin(tMessage);
    }

跟进去

public void writeMessageBegin(TMessage message) throws TException {
		// 看到这里了
		if (strictWrite_) {
			int version = VERSION_1 | message.type;// 版本号
			// 看到这里了
			writeI32(version);
			writeString(message.name);
			writeI32(message.seqid);
		} else {
			writeString(message.name);
			writeByte(message.type);
			writeI32(message.seqid);
		}
	}
public void writeI32(int i32) throws TException {
		//看到这里了
		i32out[0] = (byte) (0xff & (i32 >> 24));
		i32out[1] = (byte) (0xff & (i32 >> 16));
		i32out[2] = (byte) (0xff & (i32 >> 8));
		i32out[3] = (byte) (0xff & (i32));
		trans_.write(i32out, 0, 4);
	}

可以看到,write是写到了本地的缓冲区

public void write(byte[] buf, int off, int len) throws TTransportException {
	//调用了一个本地的缓冲区
    writeBuffer_.write(buf, off, len);
  }

写字符串就是

public void writeString(String str) throws TException {
		try {
			byte[] dat = str.getBytes("UTF-8");
			writeI32(dat.length);//写长度
			trans_.write(dat, 0, dat.length);//写具体内容
		} catch (UnsupportedEncodingException uex) {
			throw new TException("JVM DOES NOT SUPPORT UTF-8");
		}
	}

然后开始写参数了

public void write(org.apache.thrift.protocol.TProtocol oprot, helloWorldString_args struct)
					throws org.apache.thrift.TException {
				struct.validate();
				//看到这里了
				oprot.writeStructBegin(STRUCT_DESC);
				//看到这里了
				if (struct.content != null) {
					
					oprot.writeFieldBegin(CONTENT_FIELD_DESC);//写内容
					oprot.writeString(struct.content);//写具体的值
					oprot.writeFieldEnd();//什么都不做
				}
				//
				oprot.writeFieldStop();//写停止位
				oprot.writeStructEnd();
			}

最后调用TFramedTransport.flush

@Override
  public void flush() throws TTransportException {
    byte[] buf = writeBuffer_.get();//获得内容
    int len = writeBuffer_.len();//长度
    writeBuffer_.reset();//重置本地的缓冲区

    encodeFrameSize(len, i32buf);//总长度
    transport_.write(i32buf, 0, 4);
    transport_.write(buf, 0, len);
    transport_.flush();
  }

也就是写总长度+总内容,

重点就是write + flush

先看write函数,

 /**
   * Writes to the underlying output stream if not null.
   */
  public void write(byte[] buf, int off, int len) throws TTransportException {
    if (outputStream_ == null) {
      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot write to null outputStream");
    }
    try {
      outputStream_.write(buf, off, len);
    } catch (IOException iox) {
      throw new TTransportException(TTransportException.UNKNOWN, iox);
    }
  }

那么outputStream是什么呢?

这就得看socket初始化的时候了

/**
   * Connects the socket, creating a new socket object if necessary.
   */
  public void open() throws TTransportException {
    if (isOpen()) {
      throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected.");
    }

    if (host_.length() == 0) {
      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");
    }
    if (port_ <= 0) {
      throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open without port.");
    }

    if (socket_ == null) {
      initSocket();
    }

    try {
      socket_.connect(new InetSocketAddress(host_, port_), connectTimeout_);
      inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);
      outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);
    } catch (IOException iox) {
      close();
      throw new TTransportException(TTransportException.NOT_OPEN, iox);
    }
  }

那么,这里写内容的话,就是真的写入了

然后就剩下flush了

Step completed: "thread=Thread-5", org.apache.thrift.transport.TIOStreamTransport.flush(), line=159 bci=18
159          outputStream_.flush();//真实的flush了

Thread-5[1] print outputStream_
 outputStream_ = "java.io.BufferedOutputStream@d69f29"

发送完毕,这里可以看到,确实是同步发送,接下来就是接收了      

return recv_helloWorldString();

可以查看栈

Thread-5[1] where
  [1] org.apache.thrift.transport.TFramedTransport.readFrame (TFramedTransport.java:129)
  [2] org.apache.thrift.transport.TFramedTransport.read (TFramedTransport.java:101)
  [3] org.apache.thrift.transport.TTransport.readAll (TTransport.java:86)
  [4] org.apache.thrift.protocol.TBinaryProtocol.readAll (TBinaryProtocol.java:429)
  [5] org.apache.thrift.protocol.TBinaryProtocol.readI32 (TBinaryProtocol.java:318)
  [6] org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin (TBinaryProtocol.java:219)
  [7] org.apache.thrift.protocol.TProtocolDecorator.readMessageBegin (TProtocolDecorator.java:135)
  [8] org.apache.thrift.TServiceClient.receiveBase (TServiceClient.java:77)

到最后,都是涉及到inputStream_

这个也是跟outputStream_一个意思,所以这里确实是同步处理的。

------更细的细节,这里就不说了,thrift能有比较好的效率,确实做得不错,果然是facebook.

 

另外注意到一点,每次请求,都会发送一个seqid过去,这个seqid必须在返回值里存在且一样

protected void receiveBase(TBase<?, ?> result, String methodName) throws TException {
		TMessage msg = iprot_.readMessageBegin();
		if (msg.type == TMessageType.EXCEPTION) {
			TApplicationException x = TApplicationException.read(iprot_);
			iprot_.readMessageEnd();
			throw x;
		}
		if (msg.seqid != seqid_) {//序列号判断
			throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID,
					methodName + " failed: out of sequence response");
		}
		result.read(iprot_);
		iprot_.readMessageEnd();
	}

所以,再花一点时间,看看这个 seqid在服务端的处理。

这里就需要翻看我以前的博客了:https://my.oschina.net/qiangzigege/blog/517263

方便快速找到我想要的代码位置

其实就一句话,收到了seqid,然后回写,代码如下:

public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException {
    T args = getEmptyArgsInstance();
    try {
      args.read(iprot);
    } catch (TProtocolException e) {
      iprot.readMessageEnd();
      TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());
      
      oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
      x.write(oprot);
      oprot.writeMessageEnd();
      oprot.getTransport().flush();
      return;
    }
    iprot.readMessageEnd();
    TBase result = null;

    try {
      result = getResult(iface, args);
    } catch(TException tex) {
      LOGGER.error("Internal error processing " + getMethodName(), tex);
      TApplicationException x = new TApplicationException(TApplicationException.INTERNAL_ERROR, 
        "Internal error processing " + getMethodName());
      oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
      x.write(oprot);
      oprot.writeMessageEnd();
      oprot.getTransport().flush();
      return;
    }

    if(!isOneway()) {
    	logger.info("oprot---"+oprot);
    	logger.info("oprot.getTransport()---"+oprot.getTransport());
    	//这里回写seqid
      oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.REPLY, seqid));
      result.write(oprot);
      oprot.writeMessageEnd();
      oprot.getTransport().flush();
    }
  }

注意一个细节:这里的oprot.getTransport()

oprot.getTransport()---org.apache.thrift.transport.TFramedTransport@7ef6f0aa - [org.apache.thrift.ProcessFunction.24] 
flush invoked... - [org.apache.thrift.transport.TFramedTransport.24] 
transport_ org.apache.thrift.transport.TIOStreamTransport@3bf46110 - [org.apache.thrift.transport.TFramedTransport.24] 
outputStream_---class org.apache.thrift.TByteArrayOutputStream 

所以,这里的flush实际上什么都不做,因为真正的事情,是交给第二层的IO线程来做。

Thrift真是不错啊,业内良心。          

转载于:https://my.oschina.net/qiangzigege/blog/793960

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值