一个非必现问题的定位和反思
系统背景:
分布式消息架构,所有模块通过消息机制交互。
问题现象:
客户在界面执行业务的命令偶尔会失败。
问题分析:
1:表面看是消息无法发送,但经过消息平台定位,发现出错的时候消息内存被破坏。
2:由于不是必现,没有什么错误日志,只有走读业务流程代码。
问题定位:
1:业务流程会申请一个消息包,结构大体如下:
#define MAX_NUM 10
struct { short userIns[MAX_NUM];//用户实例数组 short userNum; //有效用户数 };
2:业务是实现一个用户监测功能,在一条消息中根据actionType取值不同表示启动和停止用户监测,代码流程如下:
pMsg->msgType = USER_MONITOR //用户监测消息类型
if(type == start) //启动监测 { pMsg->actionType = start; pMsg->userNum = getUserNum();
fillUserInsFun(pMsg); } else //停止监测 { pMsg->actionType = finish; }
SendMsg(pMsg)
在启动监测的时候需要填充userNum和userIns信息来通知底层启动监测的用户。但停止监测需求就是停止所有用户,只需要发送停止监测消息给底层,底层会停止所有用户的监测。所以userNum和userIns不再需要。这段代码一直在原有的系统中运行良好。
3:由于需要支持新的系统,并且支持分布式部署,原来的框架并没有对消息字节序处理过,于是新增了消息字节序转换模块。这就意味着现在我们出去的消息都要经过一个字节序转换模块处理了。对于用户监测消息处理代码如下:
for(loop=0;loop<pMsg->userNum;loop++) { htons(pMsg->userIns[loop]); }
把有效的用户实例信息进行网络字节序转换。但这个改动后问题就出现了,虽然是同一条消息,但userNum在监测停止中根本就没用到,在代码中是个随机值,值小于MAX_NUM就不会出问题,大与MAX_NUM就会对后面的消息包破坏导致异常。由于转换的消息太多,前期开发人员也不可能对每一个消息来仔细检查流程,导致了问题产生。
对该问题的反思:
1:由于需求的变化导致了一个较难定位的非必现问题,有办法让偶现成为必现吗?很简单,需要对分配的字段进行初始化,不能让它是个随机值。如果pMsg->userNum一开始就初始化为0,问题不就不会出现了吗。
2:虽然看起来问题解决了,但实际是隐藏了设计的缺陷,如果pMsg->userNum一开始被初始为一个非法值,问题又出现了,但至少问题是必现的了。
3:我们看到初始化一个非法值有助于发现问题,但问题的本质是什么呢?是该设计违反了单一职责,同一个消息承担了两种职能,而且消息体内容还不通用,无法应对变化。如果一开始就设计成两条消息也就不会出现后续的问题了。
结论:
1:初始化的重要性,非必现问题的定位难度往往很大。
2:初始化的值一定要是非法值,否则很可能隐藏BUG。
3:软件的变化无处不在,需要我们的设计考虑到后续的变化。
转载请注明原始出处:http://www.cnblogs.com/chencheng/archive/2013/01/11/2856133.html