Thrift client并发异常out of sequence

网友投稿 2387 2022-05-30

1. 环境异常现象及初步分析

项目中对接外部的thrift服务,使用该服务提供的thrift client进行访问;我们刚开始进行功能验证时一切运行正常,但后来请求量稍微高一点后,就出现了进程卡死的现象,查看后台日志,开始以为是哪个接口有bug导致服务崩溃,后来发现接口是随机性的报错,并且在异常抛出后,client无法再响应后继请求。

跟踪日志查看,每次在出现org.apache.thrift.TApplicationException: xxx  failed: out of sequence response异常后,client便无法进行下一次请求,而out of sequence明显不是我们服务中自己定义的异常,跟进去的代码看下,该异常来自于thrift源码TServiceClient下面这段:

//参见:TServiceClient //API方法调用时,发送请求数据流 protected void sendBase(String methodName, TBase args) throws TException {     oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));//首先写入"方法名称"和"seqid_"     args.write(oprot_);//序列化参数     oprot_.writeMessageEnd();     oprot_.getTransport().flush(); } 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;     }     //检测seqid是否一致     if (msg.seqid != seqid_) {     throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response");     }     result.read(iprot_);     //反序列化     iprot_.readMessageEnd(); }

从上面TApplicationException.BAD_SEQUENCE_ID这块就是我们的异常来源,那么seqid是什么,而我们的msg的seqid又为何不一致了?

2. 根因分析

客户端和服务端之间的通讯的媒介是Message,那么我们就简单看下Message的结构是怎样的。通常在thrift自动生成的代码中都会有send_methodname_xxx,receive_methodname_xxx这些方法,而在send方法中的第一句就是写入一个Message Begin。不论不同的协议会如何处理传入参数,我们知道了这个Message Begin是一个org.apache.thrift.protocol.TMessage类型的。那么TMessage又包括了那些东西

public TMessage(String n, byte t, int s) {      name = n; type = t; seqid = s;  }  public final String name;  public final byte type;  public final int seqid;

由此可见,一个Message应该包括的内容有3个:name,type,seqid。

name:根据上面send方法中的设置来看,name被设置为功能名称,如果使用TMessage的无参数构造器的话,name会被设为空字符串””。

type:虽然这是一个byte类型的,但是实际上是从TMessageType中选择的,具体含义见源码,不过如果使用TMessage的无参数构造器的话,type会被置为TType.STOP。

public final class TMessageType {   public static final byte CALL  = 1;   public static final byte REPLY = 2;   public static final byte EXCEPTION = 3;   public static final byte ONEWAY = 4; }

seqid:这个就是用来标记客户端的

因此,只要我们同一个client的seqid能够对应上就不会出错,那么应该就是我们的client并发请求时响应的seqid乱掉了,导致异常。

3. 锁定原因并解决

我们client的msg的顺序异常,难道client底层不能并发进行请求,没有个连接池来管理seqid,为了验证猜想,继续查看源码可以看到thrift确实没有连接池,client 中生成的 seqid 只是用来和服务端返回的 rseqid 进行匹配。

func (p *TStandardClient) Recv(iprot TProtocol, seqId int32, method string, result TStruct) error {     rMethod, rTypeId, rSeqId, err := iprot.ReadMessageBegin()     if err != nil {         return err     }           if method != rMethod {         return NewTApplicationException(WRONG_METHOD_NAME, fmt.Sprintf("%s: wrong method name", method))     } else if seqId != rSeqId {         return NewTApplicationException(BAD_SEQUENCE_ID, fmt.Sprintf("%s: out of order sequence response", method))     } else if rTypeId == EXCEPTION {         var exception tApplicationException         if err := exception.Read(iprot); err != nil {             return err         }            if err := iprot.ReadMessageEnd(); err != nil {          return err      }      return &exception      } else if rTypeId != REPLY {          return NewTApplicationException(INVALID_MESSAGE_TYPE_EXCEPTION, fmt.Sprintf("%s: invalid message type", method))      }            if err := result.Read(iprot); err != nil {          return err      }            return iprot.ReadMessageEnd() }

thrift 的每个 client 对象中包裹了一个 transport:

...     useTransport, err := transportFactory.GetTransport(transport)     client := NewEchoClientFactory(useTransport, protocolFactory)     if err := transport.Open(); err != nil {         fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:9898", " ", err)         os.Exit(1)     }     defer transport.Close()           req := &EchoReq{Msg: "You are welcome."}     res, err := client.Echo(context.TODO(), req)     ...           type EchoClient struct {         c thrift.TClient     }           func NewEchoClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *EchoClient {         return &EchoClient{             c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t)),         }     }

这个包裹的 transport 就是一条单独的 tcp 连接,没有连接池,并发请求msg的返回先后就无法保证。

既然原因是thrift的单个client不能处理并发请求,并且我们对client的使用场景下不需要client单实例,那就自然而然的想到添加一个对象池管理我们的client,这样就能很好的解决我们的client使用问题,最终选择使用了GenericObjectPool来管理我们的对象池。不过每个配置场景也要好好学习下,后面在性能测试时还出现了配置原因导致了性能损耗问题,这是后话。

相关参考文档: https://issues.cloudera.org/browse/KITE-328

Thrift client并发异常之out of sequence

EI企业智能 智能数据 数据湖治理中心 DGC

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:浏览器常识和http错误码介绍
下一篇:华为云IoT Openlab介绍和测试资料介绍总贴
相关文章