[date: 2018-10-11 17:18] [visits: 5]

wafer信道的不可靠性

wafer - 企业级微信小程序全栈方案

近期帮他人处理了一个与wafer信道有关的技术问题,通过了解wafer信道相关内容,自己也对websocket也多了些认识,本文捋一捋自己这次经历以及收获。

问题场景

一个双人问答对战的小程序,多轮对战的过程中某一方可能出现卡住的情况,由于是第一次接触这个项目,花了些时间摸清技术大概:

小程序使用一个叫wafer的腾讯云SDK做的,服务器也是用腾讯云提供的小程序解决方案,业务上对战双方通过websocket连接服务器,由服务器派发问题,对战双方作答

核心信息大概就是这些,在看了下项目代码后,感觉腾讯云真是想尽办法为程序员降低了开发复杂度,有点像是DreamWave与前端开发,我也就像一个前端工程师初次接触DreamWave一样,有点茫然。

花了好一会才知道wafer大概是个什么东西,以及wafer信道的工作原理,具体可以参考这篇文章

问题分析

最初听完问题的描述后,猜测可能是没有收到服务器发过来的问题消息,然后出现卡住情况。在检查一遍代码后发现这个逻辑确实成立,随后先从代码着手找原因。

wafer信道方案

先简单介绍一下wafer的信道方案如何使用websocket通信,一共三个参与方,client、server与third-server(腾讯云PaaS服务),交互是client与third-server建立websocket连接,third-server使用http与server通信。

client发送消息给server:

client通过websocket发送消息给third-server,thrid-server收到后会以http的方式调用server

server发送消息给client:

server通过http调用third-server,然后third-server再通过websocket发送给client

这个方案本身应该OK,但文档中缺少细节,导致难以定位client没有收到消息的问题所在,从文档发现唯一有联系的大概是broadcast方法回调中返回了invalidTunnelIds(无效的信道ID),但代码中有对invalidTunnelIds做消息重发。

invalidTunnelIds疑点

看到invalidTunnelIds这个回调内容后非常好奇,因为我知道websocket消息发出去后并不能确定对方是否成功收到,那么wafer这里如何判断某个信道无效呢?

难道说invalidTunnelIds只是识别关闭了的目标信道,又或者wafer实现了一套类似TCP-ACK的机制,关于这个invalidTunnelIds,文档里面并没有额外说明,猜不出啥,因为难以确认这个细节,对定位问题产生莫大阻碍。

通过查看调试log,发现客户端websocket连接通信正常的情况下,invalidTunnelIds也有可能非空,猜测third-server有ACK机制+timeout,然后在timeout内没有收到客户端的ACK,随后当成无效信道返回给server,但这些猜测同样没找到相关文档证实。

意外收获

由于纠结invalidTunnelIds的具体含义,调试过程中有一个意外收获,发现同一个消息client出现收到两次的情况,这让我有些意外。

假设wafer信道功能有ACK机制,但在指定的timeout内third-server没有收到消息的ACK,当成无效信道返回给了server。client虽有收到消息,但由于ACK超时,third-server返回的invalidTunnelIds导致这条消息被重发,所以client又收到了一次。

测试过程中,发现这种情况也导致了客户端出现BUG,解决方案相对简单:“为每一条消息标记一个ID,客户端按收到过的消息去重”。

调试

理论分析过程中没有实质性收获,但判断问题原因还是wafer信道服务的不稳定,有猜测是因为websocket出现重连导致丢失消息,而且代码中关于ws连接异常的处理逻辑也不完善,遂在代码中加了一堆调试log,比如打印各种ws事件、收到消息等。

经过测试出现卡住情况后查看日志,发现确实是问题未收到,但是ws连接并无异常,同时从服务器的日志上看,消息有发出。这很意外,同时很无奈,ws连接没有异常,服务端的确向信道发送了消息,但客户端就是没收到,只能猜测是因为third-server不可靠。

问题处理

猜测是因为third-server不可靠导致client没有收到消息之后,做了一个尝试性修改:“针对每一个发送问题的消息,采用间隔300ms发送10次的策略”,因为客户端做了消息去重,所以不会造成业务逻辑BUG。经过这样的尝试性修改后再测试,测了近一个小时也没有出现一次收不到的情况。

而在没修改之前,测3-5分钟大概会出现一次消息收不到,修改后一小时不出现,间接证明third-server不可靠的可能性,而如果真的是third-server不可靠,从开发的角度来说,针对收不到消息的问题基本没辙。

这个项目我只是指导修改,把情况跟对方技术详细说明后,让他们先用这种低成本的修改方案试试,实际上这个修改方案针对的只是third-server可能不可靠,但并没有从根本上解决问题,也不优雅。

思考

抛开对wafer信道的可靠性怀疑,如果是client用websocket与server直连,通信是不是可靠的呢?答案是:“同样不可靠”,原因是因为连接可能出现异常,比如网络不稳定、网络环境切换。

假设server通过websocket协议向client发送一条消息A,但此时client确突然断网,过了两秒后client重新连接到服务器,但如果没有其它措施的话,client就错过了消息A。针对这种情况,需要一个类似TCP-ACK机制用于client向server反馈消息接收情况,同时server需要维护消息的状态,当消息没有被确认收到,client重新连接后需要补发这些消息。

有了ACK机制同样不能避免重复收到消息,上面的场景,假设client收到消息A回ACK的时候,网络异常导致ACK没有送达,随后自己异常触发重连,由于服务器没有收到消息A的ACK,故在client连接后还会收到一次消息A,所以client去重的逻辑同样需要。

假设ws连接是稳定的,即不考虑重连或异常断开等情况,server发出的消息是否可以保证client一定收到呢?我想是能够保证的,因为ws协议建立在TCP之上,TCP是可靠传输,只要协议处理上不出现问题,那么就能够保证server发出的消息,client能够收到。当然,TCP连接异常会导消息传输不可靠,但我指的前提就是不考虑连接异常情况。

针对wafer的信道模式,server与client中间多了个一个third-server,事情变的更复杂,比如server -> third-server的HTTP超时与异常处理,再比如third-server本身的可靠性等。这其中缺少的技术细节导致这个问题难以继续定位根本原因,虽然看起来是因为wafer信道的不稳定导致消息没有收到,但也有可能不是,自己最终没有更多精力和成本去寻找,只能作罢。

后记

对于小程序生态圈其实不算了解,只是听闻小程序对开发者越来越友好,门槛也越来越低,感觉这样的技术低门槛难以满足具有竞争力产品所需要的技术能力,却可以让更多的人或者说是程序员发挥其创造力,营造一个多元化,多样化的小程序生态。

虽然我喜欢这种生态,但好像现实情况恰恰相反...