测试组在测试环境验证问题的时候,发现在手机微信页面点击某个按钮,却触发了2次ajax请求.
于是开发组小伙伴在修正这个bug的过程中, 一会怀疑前端js问题, 一会又怀疑后端java代码问题, 却没考虑到网络链路转发机制的问题.
后来笔者提议不改前台,只改后台,注释全部业务代码,改成让线程直接睡眠 Thread.currentThread(N * 1000 ) , 动态修改N参数来模拟业务时间, 发现 sleep 10s以下的请求只会触发一次, 当sleep 10s以上时,请求会无缘无故触发2次.
微信在请求其它服务器时, 如果目标服务器需要10秒以上才能响应,那么微信会自动再次触发相同请求. 时序图如下:
于是开始验证, 找各种机型和APP试验,得出下表:
类型 | 客户端 | 请求类型 | 服务端 | 服务端 Sleep 秒数 | 服务端收到请求次数 | 补充说明 |
手机android | 微信 | post | 阿里云ECS主机 | 2 | 1次 | |
手机android | 微信 | post | 阿里云ECS主机 | 18 | 2次 | |
手机android | qq浏览器 | post | 阿里云ECS主机 | 2 | 1次 | |
手机android | qq浏览器 | post | 阿里云ECS主机 | 18 | 2次 | |
手机android | uc浏览器 | post | 阿里云ECS主机 | 2 | 1次 | |
手机android | uc浏览器 | post | 阿里云ECS主机 | 18 | 1次 | |
手机android | 傲游浏览器 | post | 阿里云ECS主机 | 2 | 1次 | |
手机android | 傲游浏览器 | post | 阿里云ECS主机 | 18 | 1次 | |
苹果iphone | 微信 | post | 阿里云ECS主机 | 2 | 1次 | |
苹果iphone | 微信 | post | 阿里云ECS主机 | 18 | 1次 | |
苹果iphone | qq浏览器 | post | 阿里云ECS主机 | 未试验 | ||
苹果iphone | qq浏览器 | post | 阿里云ECS主机 | 未试验 | ||
苹果iphone | uc浏览器 | post | 阿里云ECS主机 | 未试验 | ||
苹果iphone | uc浏览器 | post | 阿里云ECS主机 | 未试验 | ||
联想/华硕笔记本PC | 微信 | post | 阿里云ECS主机 | 2 | 1次 | |
联想/华硕笔记本PC | 微信 | post | 阿里云ECS主机 | 18 | 1次 | |
联想/华硕笔记本PC | qq浏览器 | post | 阿里云ECS主机 | 2 | 1次 | |
联想/华硕笔记本PC | qq浏览器 | post | 阿里云ECS主机 | 18 | 1次 | |
联想/华硕笔记本PC | 傲游浏览器 | post | 阿里云ECS主机 | 2 | 1次 | |
联想/华硕笔记本PC | 傲游浏览器 | post | 阿里云ECS主机 | 18 | 1次 |
最终得出结论为: android手机端通过微信或手机QQ浏览器 , 用ajax触发post请求, 调用任意目标服务器,如果服务器业务处理需要10秒以上才能响应, 会导致腾讯(微信服务器)自动再次触发相同请求.
在请求地址后追加 &connect_redirect=1 即可让请求不再重发.
怎么找到?非常感谢下链接中的9楼, 真是茫茫人海 , 眼前一亮 , 微信开放破平台都没提及该问题, 真是蛋疼.
而在第二页中还有人提及"加&connect_redirect=1 可以解决80%左右,但极小部分android手机加上后仍旧无效"的说法, 但在笔者目前的自测用例中尚未发生 , 忽略不计了.
笔者在阿里云一台ECS服务器, 很早前就专门搭建了一个网页版的接口对接工具,地址为http://www.kingtool.top/kingtool (注ECS服务器2018年12月底到期)
延迟返回接口地址: http://www.kingtool.top/kingtool/httptest?delay=18&connect_redirect=1
delay=18 是我自己配置的服务端动态参数,意为让我的java后台sleep(18)秒,即18秒后返回响应报文
connect_redirect=1 是腾讯(微信)服务器配置的动态参数, 意为只触发一次请求, 超时也不重发。
为了解决ajax跨域问题, 请求链路为 前台页面-->触发ajax请求-->阿里云ecs-->阿里云ecs java代码调用目标服务器真实地址
此时post请求一次我的阿里云延迟返回接口, 18秒结束后, 后台日志只打印一次,成功解决.
我的请求报文"天行健,君子以自强不息,地势坤,君子以厚德载物"在后台日志中的的确确只打印一了一次.
不知为何之前方案的connect_redirect=1参数突然MMP失灵了,只能另寻出路。
由于面向互联网的接口,多次相同报文的请求的事实终究无法避免,要从根本上解决该问题, 还是得从服务方的角度出发:
要么精简自身系统以缩短处理时间绕开微信2次触发问题,
要么采用锁机制控制第2次请求
于是又画了以下时序图,核心思想在于让第2次的处理从第1次的请求结果中去拿。
而在集群模式下,A系统集群中的各server无法感知其它server的处理情况,所以解决重点在于使用独立的redis或memcache缓存,并配以自动失效时间以保证内存的稳定性(防缓存无限增大),采用该方案后可看到第2次绿条的处理时间明显比第1次红条短多了。
(在下图样例中 假设A系统处理业务需要11秒, 那么第2次不会再处理业务,最多比第一次多等1~2秒左右 , 最终就算2次触发响应时间最多也不会超过12秒,)
简要流程说明
以第一次红条运行时间11秒为前提
首先计算请求报文的md5值, 作为缓存的key,也就是查询要用的唯一识别码,
0.5秒时,第一次的红条请求到达, 以md5为key去缓存中查询对应的value,必然为空,1秒时,设置对应value为0,作为正在处理的标志.
然后第一次红条请求一直在处理中,时间往下嘀嗒嘀嗒走到10秒,
此时时间超过了10秒,
10.1秒时,第二次的绿条请求到达,还是以md5为key去缓存中查询对应的value,10.8秒去查询的时候会查到md5对应的value为0,发现该条数据正在被第一次的红条处理中,好吧,那绿条每隔1秒再去查询md5对应的value, 判断是否已处理完(非0)了
11秒时,第一次的红条请求顺利结束,把缓存中md5对应value从0更新成返回的业务报文(非0),作为处理完成的标志.
11.8秒(相当于12秒),第二次的绿条发现,缓存中md5为key对应的value,已从0更新成了返回业务报文(非0),那么就把该返回业务报文作为真正需要的业务报文返回出去,经历时间大约12秒左右.
于是,当第一次请求如果需要17秒处理时间,那么每二次会在17+1秒返回, 当第一次如果需要23秒处理时间,第二次请求会在23+1秒返回,依次类推.
补充说明
至于是否用0作为正在处理标志位,依据你的业务而定, 要是业务本身就返回0或1,那肯定需要换成true,false或其它特殊字符作为正在处理标志了.
另外如果您的业务本身请求就很频率(比如平均每隔个5秒就会有请求过来, 而每次请求时间又需要个20来秒)那么本方法由于第二次请求需要线程阻塞在那,等着第一次处理结束,所以会不太适合, 可能会浪费大量线程资源 , 此时最好从业务本身角度出发看看能不能缩短处理时间, 要想鱼和熊掌都能兼得,那各位哥请把自家的服务器和网络硬件设备镀层金吧.
求助微信一次ajax请求,会访问两次 [问题点数:20分,无满意结帖,结帖人showbo]--https://bbs.csdn.net/topics/391816470
24楼说:
我也遇到这样的问题,微商城提交订单,超过10秒钟,微信浏览器重发,导致订单重复提交的问题。拦截器里看到确实有两次提交,确定我们代码里不做超时重试,然后这种情况似乎跟手机有关,我的手机就没有出现这样的问题,其他两个同事的手机就出现,巨坑
后台收到微信重复请求问题--https://blog.csdn.net/gotohomebye/article/details/78508741 作者说:
都困扰快2周了,网上各种查理不出头绪,假如真是查出来问题这么严重的话,微信浏览器研发团队不可能不重视,现在运行中的这么多微信公众号不可能没遇到类似问题。其他浏览器都是正常的,肯定代码没问题,微信浏览器的问题也不大