在设备与服务器端定义的json类型的日志上报消息结构中,定义了字段msgId的字段作为唯一关键字,用于唯一标记单条上报消息。类似于手机的UUID。在测试过程中发现存在大量日志上报的时候,会出现实际本地产生日志条数,小于服务器端elastic-search服务器记录日志条数的现象,经分析是由于上传标记的唯一关键字中msgId重复,导致服务器端日志被顶掉的问题,特此记录。
原有流程下,日志上报由一个独立的本地进程提供服务,避免阻塞主服务的执行流程,而要执行日志上报的需求在多个进程中存在,故而对服务进行了简单的封装,由独立的进程通过接收进程间通信的方式来提供服务。消息接收模块收到消息后,启动消息处理线程,对消息进行封装处理,并添加上必要的头部信息,加入到队列中,由发送线程从队列中取出消息,发送给服务器。
与服务器之间交互的msgId约定为一个32位的随机数。在本地对序列号,时间戳和随机数构成的内容进行md5计算。
char szBuf[256] = {0};
char szMd5Buf[33] = {0};
snprintf(szBuf,sizeof(szBuf),"device%s-%lld-%u", srcId.c_str(),i64CurTime,XXX_Rand());
compute_buf_md5(szBuf,strlen(szBuf),szMd5Buf,sizeof(szMd5Buf));
XXX_Rand函数为公司提供的函数封装,即调用srand函数,并以当前开机到现在的启动时间为种子。
在开启了日志打印,对msgId的生成过程进行分析,发现如下结果。一般情况下,只会出现单条打印。
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-2729789-1022948994] szMd5Buf[24fe2f01491e25a650bdf36873275dce]
但在相当短的时间内同时接收到两个日志发送的请求时,会出现如下的打印。
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-2989915-1831772470] szMd5Buf[40390fc054545f055e9603681a9e6cc5]
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-2989915-1831772470] szMd5Buf[40390fc054545f055e9603681a9e6cc5]
可以看到时间戳与随机数是完全一样的。这个现象在双核设备中较为容易复现,在更低端的单核设备中,以同一套代码运行,基本不会出现。可以猜测在双核的情况下,因本地的消息处理线程几乎同时启动并进行消息处理。而这里作为srand函数的随机数的时间戳是毫秒级的时间戳,故而会出现了时间戳一致,同时随机数也一致的情况。
因这里是多线程并发时出现的问题,故而可以引入线程id作为要被MD5的源字符串的一部分。修改为如下结构
snprintf(szBuf,sizeof(szBuf),"device%s-%lld-%u-%lu", srcId.c_str(),i64CurTime,XXX_Rand(), pthread_self());
compute_buf_md5(szBuf,strlen(szBuf),szMd5Buf,sizeof(szMd5Buf));
重新进行测试,可以发现带上了线程id之后,即使出现3个线程并发,获取到随机数和时间戳一致的情况,也可以通过线程id进行区分,得到不同的计算md5之后的msgId。
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-6292510-852175900-3059578048] szMd5Buf[659296d7263566a8cd89fda52c8e6204]
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-6292510-852175900-3062723776] szMd5Buf[c640484c8a4cdcaa03fcfa48348356b3]
[INFO][Func:compute_msgId]: szBuf[deviceC15668877-6292510-852175900-3061675200] szMd5Buf[13a1a8af937dc4e99cd2c1d68e8d94f8]
以上便是解决多线程使用时间戳作为种子生成随机数重复导致的问题的起因和解决流程。