线程竞争导致的EOFException解决 ---  一次线上问题解决流程

1 前言

在放假之前搞定这个问题,回家也安心了,感谢同事的帮忙微笑


2 现象描述

这段异常在一次上线之后,经常出现。但是在上线之前,测试环境中没有出现。

java.io.EOFException

    at java.io.DataInputStream.readUnsignedShort(DataInputStream.java:323)

    at java.io.ObjectInputStream$BlockDataInputStream.readUnsignedShort(ObjectInputStream.java:2763)

    at java.io.ObjectInputStream$BlockDataInputStream.readUTF(ObjectInputStream.java:2819)

    at java.io.ObjectInputStream.readUTF(ObjectInputStream.java:1050)

    at com.netease.xxx.po.XXXAdaptor.readExternal(XXXAdaptor.java:622)

    at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1791)

    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)

    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)

    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)                

...............                                                                                                        

    at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1791)                 

    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)               

    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)                      

    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)                        

    at org.jboss.netty.handler.codec.serialization.ObjectDecoder.decode(ObjectDecoder.java:129)

    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:282)      

    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:216) 

XXXAdaptor是项目中一个rpc传输的po对象,实现了Externalizable接口,能够保证序列化的版本兼容。版本兼容的实现方案是参考protocol buffer的方式,新增的属性直接放到一个hashmap中,序列化传输的时候遍历map中的元素,这样就可以无限制的添加新的字段。

出问题的那一行代码:someField.readUTF(); 每次rpc调用,会把同时所有rpc server 节点都请求一次,每个请求是由一个线程完成的,请求的参数是线程共享的。


3 第一次解决尝试

刚在线上发现这个问题以为是因为网络原因导致的,在网上查的资料说是因为流数据已经读完,就会抛出EOFException,是正常情况。在测试环境上没有出现,只在正式环境中出现,这种情况肯定是不正常。难道是网络数据没有发送完,那NettyObjectEncoderObjectDecorder肯定会出现异常,抱着试一试的想法,在XXXAdaptor.writeExternal方法结束的地方调用ObjectOutput.flush(),结果是。。。果然没有效果


4 第二次尝试

在反序列的代码中加上异常捕获,XXXAdaptor.readExternal反序列化hashmap的代码块中捕获异常,并且把hashmap中的entry总数打印出来,抛出异常的时候,已经遍历的entry index也输出。

上线之后找出眉目了:size=2,index=1。在序列化中,hashmap只有一对key-valuesize怎么回变成???!

我们重新走查了一边XXXAdaptor.writeExternal方法,发现有一句话很可疑:

hashmap.put(“someKey”,”someValue”);

一般的rpc调用情况下,这句话没有任何问题,在线上环境,这个服务有n个节点,每次调用,rpc框架在客户端都会启动n个线程来调用服务节点,每个线程都会对这个对象进行序列化,XXXAdaptor.writeExternal方法被多个线程调用,在没有保证每个线程一个XXXAdaptor clone实例的情况下,hashmap.put(“someKey”,”someValue”)实际上是被多线程操作---这是一个很悲催的thread race condition。而在测试环境中,只有一个服务节点,肯定不会出现这种情况。

用测试代码连接线上服务节点,调用只读操作,debug模式下,果然出现hashmap size=2,内部的table数组只有一对key-value,但是hashmap toString出来的数据是2对一样的key-value(这是偶然情况,还有可能出现size=2,其他数据都正常的情况)。至于hashmap多线程写操作导致的情况,网络上有很多具体的描述,严重的例如cpu load 100%,死循环。我的这段代码是多线程对同一个key多次put操作引起的,不会出现死循环,这是不幸中的万幸。

故在发序列化代码中,捕获忽略EOFException就可以解决这个问题。


5 最终解决

Hashmap类型属性改为ConcurrentMapput操作使用putIfAbsent代替。

项目中使用的rpc框架底层使用了多线程,但是在上层代码中并不能体现出来,为使用人员屏蔽了细节,但是没有却不能保证100%的可靠,这才悲催。。。有失败就会有进步!


参考

http://docs.jboss.org/process-guide/en/html/serialization.html jboss在序列化过程中对不同版本对象的兼容性实现的方式:也是捕获忽略EOFException


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值