时光似箭,岁月如梭,恍然间,已入职一周年了。这一年应该学习了不少东西,但决定要好好总结一下自己的时候,却不知从而谈起。
这一年来,编写过 CGI,开发后台 middle,写过批处理程序,编写过 shell 脚本,线上版本迁移,ios Demo 开发,参与过手 Q 红包项目,这一年,很忙,但也很充实。甚至自嘲长期处于一种“忙成狗,累成牛”的状态。
一年了,似乎也没学到多少东西,还有很多知识等着自己去汲取,去挖掘,继续努力,不断前行。工作中遇到的一切,几乎都是从 0 开始,故难免会走很多弯路,也曾踩过无数的坑。接下来,我与大家谈谈平日里在项目中遇到的问题以及待优化之处。其中,有些很诡异的问题未定位,欢迎各位解答。若有异议,欢迎拍砖哈!!!
接下来,比较常见的问题,放在 Comm 部分,接下来 Middle 开发中遇到的问题,随后分别介绍 CGI、批处理、Shell、Mysql 和 IOS 中遇到的问题。
问题描述:相信很多童鞋都遇到过类似的问题,即当 Xml 文件中存在多个同样节点时,则它们之间不能有任何注释,否则读取 xml 失败。例如:在较为常见的代码中,取下一个节点使用 NextSibling【NODE_NEXT】方法。如下若是,读取 xml 文件失败,导致 core,且对应的 server 会被 middled 进程无限次拉起。
TiXmlNode* NODE_NEXT(TiXmlNode* pNode)
{
return pNode->NextSibling();
}
string NODE_ATTR(TiXmlNode* pNode, const char* attribute)
{
return pNode->ToElement()->Attribute(attribute) ? pNode->ToElement()->Attribute(attribute) : "";
}
pTransNode = pTransNode->FirstChild("transaction")->FirstChild("variable");
while (pTransNode)
{
string nodeName = NODE_ATTR(pTransNode, "name");//若注释,则会报错
pTransNode = NODE_NEXT(pTransNode);
}
<transaction>
<variable name="spid_list" value="2000000501"></variable>
<!--- 该处存在问题,会发生 core -->
<variable name="watch_word" value="abcdefg"></variable>
</transaction>
问题原因:循环读取相邻节点时,则会读取下一行注释,而 Node->ToElement()
会 core。
解决方法:使用 NextSiblingElement
方法获取相邻节点,编译所有涉及读取 XML 文件的项目并做出修改。
问题描述:相信很多人拉取代码分支时,都收到过自动化编译的高危函数告警。其中存在 strcpy、sprintf、strcat、memory leak、野指针等
问题原因:历史遗留
解决方法:
1、改用 snprintf 安全函数,其能够保证标缓冲区末尾为’\0’,故使用 snprintf(szBuf, sizeof(szBuf), srcStr)
;若使用 snprintf(szBuf, sizeof(szBuf)-1, srcStr);
,则会减少一个空间区域。
2、改用 strncpy,其不会在缓冲区最后添加结束符。为防止越界操作,故常使用时有两种用法:
1) char szBuf[32] = {0};//
全部初始化
strncpy(szBuf, src, sizeof(szBuf)-1);//
仅考虑前 sizeof(szTime)-1
,故最后一个字节为 \0
2)
char szBuf[32];
strncpy(szBuf, src, sizeof(szBuf)-1);
szBuf[32] = ‘\0’;
问题描述:针对 url 请求串,service 一般都将其读取至 map 中,方便后续处理。若进行 md5 签名时,一般使用类似 string map2str(map& reqMap)
得到 ASCII 升序的字符序列,继续计算 md5 值,签名也比对成功。当一段时间后,签名验证却未能通过。
问题原因:设 map 为(param1,abc==),(param2,100)
,若采用通用的 map2str 方法,其实得到字符串为 param1=abc%3D%3D¶m2=100
。或许还存在标准 URL 编解码的问题,在此不再赘述。也许你会问,什么情况下会遇到=&等特殊字符。
1、url 嵌套,如 (reqText, uin=boy&age=20&sex=1)
2、字符串进行 Base64 编码。Base64 要求把 3 个 8 位字节(3×8=24)转化为 4 个 6 位的字节(4×6=24),之后在 6 位的前面补两个 0,形成 8 位一个字节的形式。 如果剩下的字符不足 3 个字节,则用 0 填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现 1 或 2 个'='。
解决方法:1、尽可能打印关键 debug 日志 2、重新封装 genSign(map& reqMap) 函数,对源串不编码、不转义。 3、牢记签名一般将 key 按 ASCII 升序 [map 即可实现],末尾追加“key=”。然后进行 md5。
问题描述:因机器更换等原因,需要对线上运行的服务进行迁移。当提供了迁移说明文档,运维按照说明文档执行时,却出现了很多未预料到的情况。
问题原因:
1、数据库无访问策略
2、多台机器上的服务配置不完全一致
3、灰度方案考虑不全面
4、mysql 版本不一致 [ 很奇葩]
5、业务异常考虑不全面
解决方法:
1、与运维多次确认网络访问策略并进行实地验证
2、反复对比配置,确认符合预期
3、在开发环境进行充分的验证
4、加入告警,跟踪实时日志,以便异常出现时能够及时发现
问题描述:1、在 rsync 服务端所在机器,ps 未查询到 rsync 进程。
2、密码文件未起作用
问题原因:1、rsync 同步文件有两种方式 2、密码文件属性必须为 600
解决方法:
1、拷贝命令
rsync -av --port=873 --password-file=./passwd_rsync ${filename} egbertzhang@10.12.196.190::backup
其中,backup 为/etc/rsync.conf
中的模块名,路径为 path=/data/egbertzhang/
密码文件属性必须为:
-rw----- 1 egbertzhang users 7 2015-06-04 10:00 passwd_rsync
2、通过 ssh 登陆的方式
rsync -ave "ssh -p 36000 " ${filename} egbertzhang@10.12.196.190:/data/egbertzhang/
然后输入登陆密码就传送 ok。
若想通过脚本传文件或者不想每次都手工输入登陆密码,则可使用 expect 方法,如下:
./expass.exp "rsync -ave \"ssh -p 36000 \" ${filename} egbertzhang@10.12.196.190:/data/egbertzhang/tmp" "${passwd}"
其中 expass.exp 实现不在此赘述,见附件。
问题描述:设计到月表时,一般情况下,都会根据当前时间戳获取上一个月的月份,简单实现如下:
curTime = time(NULL);
localtime_r(&curTime, &tm_now);
tm_now.tm_mon-=1;
mktime(&lastTime);
若当前时间为 2014-12-31 23:59:59,则转换后获取的时间为 2014-12-01 23:59:59,即当时 12 月,并不是预期中的 11 月份。
问题原因:11 月有 30 天,而 12 月份有 31 天
解决方法:将当前时间 day 置为 1 日,然后进行相减运算。
问题描述:查询当前月表后,获取当前时间,设为 2015-4-30 23:59:59,故需要查询 4 月份的数据库表,若无对应的数据记录,且 DB 查询时间超过 1s[ 此情况很常见]此时,时间已经大于 2015-5-1 00:00:00。因此查询前一个月库表时,实际上会继续查询 4 月份的数据。因此,3 月份的数据永远也不会被覆盖到。若该业务逻辑依靠月表保证重入逻辑,则可能存在严重问题。
// 查当月
string month = getMonthFromNow(0);
int flag = queryPayRelation(month);
if (flag == 0)
{
// 往前查一个月
month = getMonthFromNow(-1);
flag = queryPayRelation(month);
if (flag == 0)
{
// 往后查一个月
month = getMonthFromNow(1);
flag = queryPayRelation(month);
}
}
问题原因:历史问题
解决方法:以一个固定时间为基准,左右偏移各一个月。
问题描述:1、脚本提示错误,但是仔细检查后未发现异常 2、xml 标签读取失败 3、输出日志顺序混乱
问题原因:1、全角符号问题,不易发现 2、XML 标签不对应 3、涉及标准输入输出时,将 stderr 重定向到标准输出,如 “> /tmp/test.log 2>&1”
解决方法:细心、细心、再细心
问题描述:1、新建 server 时,发现 service.so 无法被加载 2、打印 log,但是文件大小为 0
问题原因:
1、新建 server 时,注意事项
l 在 trpc.conf 中新增配置项
l load all
l 修改日志级别,setlog server debug
l xxx_server,必须生成 xxx_service.so.
l 需要手动 start server
解决方法:
1、见注意事项
2、确定是否有写权限?server 是否运行打印逻辑?最后一种情况下可以考虑了,那就是磁盘空间满了。这么小的概率却被我撞了 n 次,包括 DB 插入数据报错,其他都是 log 中无内容。
问题描述:参与过的一些项目中,sql 查询经常会 core,常见的有以下三种:
1、
strncpy(stOrder.recv_name, row[6] : "", sizeof(stOrder.recv_name));
stOrder.status = row[7] : 0
2、
strncpy(stOrder.recv_name, row[6] ? row[6] : "", sizeof(stOrder.recv_name));
stOrder.status = atoi(row[6]) : 0
3、
strncpy(stOrder.send_name, row[5]? row[5] : "", sizeof(stOrder.recv_name));
strncpy(stOrder.recv_name, row[6] ? row[6] : "", sizeof(stOrder.send_name));
stOrder.status = row[7] ? atoi(row[7]) : 0
问题原因:
1、有些数据库创建时,varchar 字段 default NULL,int 字段 default NULL.导致取出的字段值 row[x]
为 NULL,故会 core
2、row 下标写串了,随着字段的增多,且不断修改,很容易出现该问题,由于 row[6]
可能为 NULL,而 atoi(row[6])
会 core
3、字段名写串了,当数据字段长度不一致时,会发生阶段,导致数据错误。
解决方法:
1、建议数据库表创建时,varchar default “”
, int default 0
,或者使用 NOT NULL,尽可能不使用 deafult NULL.
2、server 中拼写 sql 语句时,建议都使用 row[x] ? atoi(row[x]) : 0 / row[x] ? row[x] : “”
判空
3、建议使用类对象,而不是 C 的 struct 存储。如下所示:
SpOrder.m_spid = row[iCurIndex] ? row[iCurIndex++] : 0;
SpOrder.m_sp_uid = row[iCurIndex] ? atoi(row[iCurIndex++]) : 0;
这样解决了上述的 core 问题。
问题描述:在做 tlinux64 版本的迁移过程中,发现各个模块使用的 xml 库不统一。有使用 libminixml.a,也有使用 libtinyxml.a,为方便起见,故将两个库全部参与链接。当时解析 xml 时无故 core。
问题原因:至今未查明原因,可能不兼容
解决方法:后续都统一使用 libtinyxml.a,对应的头文件为 tinyxml.h
问题描述:对于 linux 基础不扎实的我来讲,且刚入职不久,遇到 server 重启问题,而且没有 core dump 文件。
问题原因:1、或许担心 core 文件太大,为防止 server 进程重复被拉起,为避免硬盘空间被大量占用,大多数情况下,core dump 开关处于关闭状态。若不分析代码的话,不容易发现问题。
2、日志句柄未分配空间
DECLARE_SO_INIT()
{
try
{
// 读取配置项
gPtrConfig = new GlobalConfig("*.xml");
// 初始化日志指针
gPtrAppLog = new CftLog();
..........
}
catch(CException& e)
{
gPtrAppLog->debug("server start ...");
throw;
}
}
解决方法:
1、查看 shell 占用的资源 ulimit -a
2、打开 core dump 开关 ulimit -c unlimited
很多情况下,core dump 开关未打开的
ulimit -c 1024 打开 coredump
3、进程启动前,尽量使用 trpc_debug_log 打印,catch 用日志句柄打印时,至少应该判空
问题描述:曾经遇到很郁闷的问题,在大量的代码中,很难发现问题,如下:
Parameters paraTmp;
paraTmp["host"] = “127.0.0.1”;
......
结果发现 paraTmp[“host”]为空,然后打印 log,printf(“host=%s”, paraTmp["host"]);发现依旧为空。
问题原因:其实 Parameters 类是这样重载 []操作符的
string operator[]( const string& key )const
{
return m_map.find(key) != m_map.end() ? m_map[key.c_str()] : “”;
}
因此,paraTmp["host"] = “127.0.0.1”
;仅仅修改的临时 string
解决方法:将返回类型用 const 修饰。
问题描述:有时候,调用其他 service 接口,莫名其妙的报错,尽然说缺少参数。
问题原因:url 请求串未加密,调用加密接口。加密 url 请求串后调用了无密接口。
解决方法:1、先确认参数名是否拼错? 2、确认有密接口还是无密接口 3、看被调用服务日志
问题描述:春节前,为备战微信红包,核心系统进行了扩容,而目前面临缩容问题。而我们的对账平台可以支持逻辑 DB 或者实 ip 扫描数据库,从而继续业务对账。但是有时候在逻辑 DB 时,不确定应该选用哪个。如下图所示:
同样是读取 c2c_db_05.t_user_order_5
表,但究竟选用 NewUserOrder/OrderDBOne/OrderDBTwo,还是选择 UserOrderDBOne 呢?
问题原因:DBA 的数据库管理系统供多个业务使用,故场景不同,库表也不一致。
解决方法:请教 DBA
NerUserOrder:仅仅保留近 3 个月的数据
OrderDBOne:主要是订单数据,即 t_order。而其中的 t_user_order 可能之前的账务组需要,不必考虑
UserOrderDBOne:订单拆分,数据来源于 t_order。可能会做迁移,对业务透明。
问题描述:CGI 的逻辑时,调用一个脚本程序将文件 rsync 到目标机器,成功后通知页面执行成功。而根据新增需求实现,部分情况下,调用新脚本程序 rsync 到目标机器,其他继续走老逻辑。最后发现,走新逻辑,CGI 正常返回,但是页面无法正常显示。而老的逻辑则可以。由于 apache 重定向了标准输入输出,故主要定位方向是查找新逻辑输出的多余数据。
问题原因:CGI 调用新的 shell 脚本将文件 rsyn 到目标机器,但未重定向返回结果日志,从而导致返回给页面时多出很多 rsync 的结果数据,不符合 http 请求响应报文格式,故报错。
解决方法:对比新旧脚本,新增 rsync ... > /tmp/rsync.log 2>&1
问题描述:常住内存的批处理程序,中间中断一段时间未发现。
问题原因:因异常逻辑触发隐藏 bug,导致 core,随后没有机制可以保证再次启动。
解决方法:1、即使常驻内存的批处理程序也需要定期检测是否运行中,若为运行,则启动
2、批处理程序增加文件锁机制,crontab 配置定期启动批处理程序
PS:脚本中需要写为绝对路径。若需要相对路径,则需设置环境变量。
问题描述:select 多列时,若将结果集重定向到一个文件中,结果集并不是多行。
db_result="select Fconfig_id, Ftask_name from db.t_table limit 2;";
echo $db_result >> $file_name / echo ${db_result} >> $file_name
//2001 2 500 10101 2 500 10102 2 500
问题原因:被单引号''括住的内容,将被视为单一字串。在引号内的代表变数的$符号,没有作用,也就是说,他被视为一般符号处理,防止任何变量替换。
name=tom; echo '$tom' # $tom
被双引号""括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引数的处理方式不同。
name=tom echo "$tom" # tom
解决方法:为防止 \t
,\n
等特殊字符会被转义,需要加双引号,即 echo "$db_result" >> $file_name
//2001 2 500
//10101 2 500
//10102 2 500
问题描述:第一次写 shell 脚本时,使用了很多 function 方法,也定义了很多变量。当其中一些变量被无故的覆盖了。
问题原因:shell 中变量默认为全局变量,且全局作用。
解决方法:若需要局部变量,则需要使用关键字 local 声明。
问题描述:若计算一个字符串"md5test",则使用命令 echo md5test | md5sum | awk '{print $1}'
,得到 md5 值为 689a850ebe8e2cf48429c9f9879e4300
。但是在代码中得到的 md5 值是 82da61aa724b5d149a9c5dc8682c2a45
。莫非代码有 bug?
问题原因:使用 echo 时,不加-n 选项,字符串默认会包含\n。
解决方法:增加-n 选项,如 echo -n 'md5test' | md5sum | awk '{print $1}';
1、Shell 函数返回值必须是字符串。
2、字符串赋值时,=号左右不能加空格
expr $g_line_no + 1
;#+ 号左右必须加空格,否则当做字符串处理。shell 内所有符号【赋值=除外】都需要有空格。如 lt gt 等
3、val1="test01";#
带双引号
val2='test02';#
不带
shell 中,如果用单引号,即变量不带有“”,即只是字符串的内容。
4、在 shell 脚本中,若有 while、done 语句,在 done 关键字后需要加换行符。如果需要换行,不能在行末尾加空格,否则空格将会被转义。
5、sz filename
如果 filename 最近未修改过。那么发送到本地时,修改时间不变。故不能仅仅按照修改时间排序。
6、赋值操作符
1) "="
在 makefile 中,make 会将整个 makefile 展开后,再决定变量的值。即变量的值将会是整个 makefile 中最后被指定的值,如下所示:
x = value
y = $(x) bar
x = xyz
在上例中,y 的值将会是 xyz bar ,而不是 value bar
2) ":="
变量的值决定于它处于 makefile 中的位置,而不是整个 makefile 展开后的最终值。
x := value
y := $(x) bar
x := xyz
在上例中,y 的值将会是 value bar ,而不是 xyz bar
问题描述:如 2.2 所述,数据库创建时字段默认 NULL 的话,则使用 mysql API 得到的 row[x]为 NULL。
问题原因:default NULL,任何类型都会插入空指针
NOT NULL,则 varchar 类型会插入空字符串,而 int 类型会插入 0
解决方法:为安全起见,建议 varchar default “”,int default 0,或者使用 NOT NULL.
问题描述:如下所示,当记录不存在时,sum(x) 返回的不是 0,而是 NULL,与自己预期的不一致。
mysql> select Fsql_limit from cft_db_check.t_task_conf;
+------------+
| Fsql_limit |
+------------+
| 500 |
| 500 |
| 500 |
+------------+
mysql> select count(Fsql_limit),sum(Fsql_limit) from cft_db_check.t_task_conf;
+---------------------+--------------------+
| count(Fsql_limit) | sum(Fsql_limit) |
+---------------------+--------------------+
| 3 | 1500 |
+----------------------+-------------------+
mysql> select count(*),sum(Fsql_limit) from cft_db_check.t_task_conf where Fsql_limit = 1000;
+----------+-----------------+
| count(*) | sum(Fsql_limit) |
+----------+-----------------+
| 0 | NULL |
+----------+-----------------+
问题原因:count 永远会返回整型,sum 在无符合条件记录的情况下会返回 NULL,而不是 0.
解决方法:使用 SELECT COALESCE(SUM(Fsql_limit),0) ...
问题描述:看一下下面的 update 语句,
update c2c_db_inc.t_spm_batopt set Fstatus=1,Fspid='1215224601'and Fmodify_time=now() where Fbatch_id='201506010187728312';
这个是简单的更新语句,至此,你看出什么问题了么?我找了 n 个人看,但都没有看出有什么问题。其实,更新成功了,但是结果却是未预料到的。
mysql> update c2c_db_inc.t_spm_batopt set Fstatus=1,Fspid='1215224601'and Fmodify_time=now() where Fbatch_id='201506010187728312';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
执行成功后,Fstaus 设置为 1,ok,没问题。但是,你会发现,Fspid 被设置成'0',而 Fmodify_time
根本没有得到更新。
问题原因:多个设置域应该用逗号,分开,至于为何不报错,且 Fspid 设置为'0',原因未知。莫非是 Mysql 的 bug?
解决方法:
update c2c_db_inc.t_spm_batopt set Fstatus=1,Fspid='1215224601', Fmodify_time=now() where Fbatch_id='201506010187728312';
1 暂时未整理,待续
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。