背景故事
之前负责的一个商城项目,需要从供应商库进行订单下单同步,服务器间通讯通过http请求。 加密方式采用DES加密方式。在运行初期一切正常,几个月后
供应商发现有重复订单存在,而客户端这边接收到异常生成订单异常信息,订单生成不同步。供应商的处理逻辑我们无从得知,只能从自身角度思考为什么会有这
种问题,在排除了一系列原因后,定位到一个问题。那就是 HttpURLConnection的post请求重发机制。
场景再现
Http请求是通过HttpUrlConnection封装的一套Java请求客户端 部分源码如下:
1 | //请求的header如下 |
Java代码调用doRequest通过HttpUrlConnection模拟一个Post请求。结果服务端会收到两次请求。
原因分析
HttpURLConnection 采用 Sun 私有的一个 HTTP 协议实现类: HttpClient.java
1 | public boolean parseHTTP(MessageHeader var1, ProgressSource var2, HttpURLConnection var3) throws IOException { |
当发生IOException就会执行判断是否进行重试。
failedOnce 默认是 false,表示是否已经失败过一次了。这也就限制了最多发送 2 次请求。
var3 是请求信息
retryPostProp 默认是 true ,可以通过命令行参数( -Dsun.net.http.retryPost=false )来指定值。
streaming:默认 false 。 true if we are in streaming mode (fixed length or chunked) 。
bug链接:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6427251
这个Bug很早就有了,归根结底原因就是sun提供的实现与Http对于Post请求的规范有不同。Http协议里Post不是幂等的,不能进行重试。
解决方案
- 使用Apache Client请求
- 修改JVM启动参数 添加:-Dsun.net.http.retryPost=false
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6427251
总结心得
- http协议方面:http规定的部分是规范,实现有千种方法。有的符合协议,有的又有所区别,在对接过程中,指定接入方式,形成书面文档规范。有利于后续
问题职责归属。 - 在寻找问题方面,无法完整获取所有信息时,从已掌握的信息出发,避免任何一点得出结论的依据。