前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手 Q 红包工程师过去一年踩过的坑

手 Q 红包工程师过去一年踩过的坑

原创
作者头像
张学林
修改2017-06-19 18:56:08
1.3K0
修改2017-06-19 18:56:08
举报
文章被收录于专栏:张学林的专栏张学林的专栏

时光似箭,岁月如梭,恍然间,已入职一周年了。这一年应该学习了不少东西,但决定要好好总结一下自己的时候,却不知从而谈起。

这一年来,编写过 CGI,开发后台 middle,写过批处理程序,编写过 shell 脚本,线上版本迁移,ios Demo 开发,参与过手 Q 红包项目,这一年,很忙,但也很充实。甚至自嘲长期处于一种“忙成狗,累成牛”的状态。

一年了,似乎也没学到多少东西,还有很多知识等着自己去汲取,去挖掘,继续努力,不断前行。工作中遇到的一切,几乎都是从 0 开始,故难免会走很多弯路,也曾踩过无数的坑。接下来,我与大家谈谈平日里在项目中遇到的问题以及待优化之处。其中,有些很诡异的问题未定位,欢迎各位解答。若有异议,欢迎拍砖哈!!!

接下来,比较常见的问题,放在 Comm 部分,接下来 Middle 开发中遇到的问题,随后分别介绍 CGI、批处理、Shell、Mysql 和 IOS 中遇到的问题。

1 Comm

1.1 XML 解析

问题描述:相信很多童鞋都遇到过类似的问题,即当 Xml 文件中存在多个同样节点时,则它们之间不能有任何注释,否则读取 xml 失败。例如:在较为常见的代码中,取下一个节点使用 NextSibling【NODE_NEXT】方法。如下若是,读取 xml 文件失败,导致 core,且对应的 server 会被 middled 进程无限次拉起。

代码语言:javascript
复制
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 文件的项目并做出修改。

1.2 高危函数

问题描述:相信很多人拉取代码分支时,都收到过自动化编译的高危函数告警。其中存在 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)

代码语言:javascript
复制
  char szBuf[32];

  strncpy(szBuf, src, sizeof(szBuf)-1);

  szBuf[32] = ‘\0’;

1.3 签名

问题描述:针对 url 请求串,service 一般都将其读取至 map 中,方便后续处理。若进行 md5 签名时,一般使用类似 string map2str(map& reqMap) 得到 ASCII 升序的字符序列,继续计算 md5 值,签名也比对成功。当一段时间后,签名验证却未能通过。

问题原因:map 为(param1,abc==),(param2,100),若采用通用的 map2str 方法,其实得到字符串为 param1=abc%3D%3D&param2=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.4 服务迁移问题

问题描述:因机器更换等原因,需要对线上运行的服务进行迁移。当提供了迁移说明文档,运维按照说明文档执行时,却出现了很多未预料到的情况。

问题原因:

1、数据库无访问策略

2、多台机器上的服务配置不完全一致

3、灰度方案考虑不全面

4、mysql 版本不一致 [ 很奇葩]

5、业务异常考虑不全面

解决方法:

1、与运维多次确认网络访问策略并进行实地验证

2、反复对比配置,确认符合预期

3、在开发环境进行充分的验证

4、加入告警,跟踪实时日志,以便异常出现时能够及时发现

1.5 rsync 配置

问题描述:1、在 rsync 服务端所在机器,ps 未查询到 rsync 进程。

2、密码文件未起作用

问题原因:1、rsync 同步文件有两种方式 2、密码文件属性必须为 600

解决方法:

1、拷贝命令

代码语言:javascript
复制
rsync -av --port=873 --password-file=./passwd_rsync ${filename} egbertzhang@10.12.196.190::backup

其中,backup 为/etc/rsync.conf 中的模块名,路径为 path=/data/egbertzhang/

密码文件属性必须为:

代码语言:javascript
复制
-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 方法,如下:

代码语言:javascript
复制
./expass.exp "rsync -ave \"ssh -p 36000 \" ${filename} egbertzhang@10.12.196.190:/data/egbertzhang/tmp" "${passwd}"

其中 expass.exp 实现不在此赘述,见附件。

1.6 时间转换

问题描述:设计到月表时,一般情况下,都会根据当前时间戳获取上一个月的月份,简单实现如下:

代码语言:javascript
复制
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 日,然后进行相减运算。

1.7 跨月查询

问题描述:查询当前月表后,获取当前时间,设为 2015-4-30 23:59:59,故需要查询 4 月份的数据库表,若无对应的数据记录,且 DB 查询时间超过 1s[ 此情况很常见]此时,时间已经大于 2015-5-1 00:00:00。因此查询前一个月库表时,实际上会继续查询 4 月份的数据。因此,3 月份的数据永远也不会被覆盖到。若该业务逻辑依靠月表保证重入逻辑,则可能存在严重问题。

代码语言:javascript
复制
// 查当月

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.8 诡异问题

问题描述:1、脚本提示错误,但是仔细检查后未发现异常 2、xml 标签读取失败 3、输出日志顺序混乱

问题原因:1、全角符号问题,不易发现 2、XML 标签不对应 3、涉及标准输入输出时,将 stderr 重定向到标准输出,如 “> /tmp/test.log 2>&1”

解决方法:细心、细心、再细心

2 Middle

2.1 新建 server

问题描述: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 中无内容。

2.2 SQL

问题描述:参与过的一些项目中,sql 查询经常会 core,常见的有以下三种:

1、

代码语言:javascript
复制
strncpy(stOrder.recv_name, row[6] : "", sizeof(stOrder.recv_name)); 

stOrder.status = row[7] : 0

2、

代码语言:javascript
复制
strncpy(stOrder.recv_name, row[6] ? row[6] : "", sizeof(stOrder.recv_name)); 

stOrder.status = atoi(row[6]) : 0

3、

代码语言:javascript
复制
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 存储。如下所示:

代码语言:javascript
复制
SpOrder.m_spid = row[iCurIndex] ? row[iCurIndex++] : 0;

SpOrder.m_sp_uid = row[iCurIndex] ? atoi(row[iCurIndex++]) : 0;

这样解决了上述的 core 问题。

2.3 xml 解析库

问题描述:在做 tlinux64 版本的迁移过程中,发现各个模块使用的 xml 库不统一。有使用 libminixml.a,也有使用 libtinyxml.a,为方便起见,故将两个库全部参与链接。当时解析 xml 时无故 core。

问题原因:至今未查明原因,可能不兼容

解决方法:后续都统一使用 libtinyxml.a,对应的头文件为 tinyxml.h

2.4 core 调试

问题描述:对于 linux 基础不扎实的我来讲,且刚入职不久,遇到 server 重启问题,而且没有 core dump 文件。

问题原因:1、或许担心 core 文件太大,为防止 server 进程重复被拉起,为避免硬盘空间被大量占用,大多数情况下,core dump 开关处于关闭状态。若不分析代码的话,不容易发现问题。

2、日志句柄未分配空间

代码语言:javascript
复制
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 用日志句柄打印时,至少应该判空

2.5 封装问题

问题描述:曾经遇到很郁闷的问题,在大量的代码中,很难发现问题,如下:

Parameters paraTmp;

paraTmp["host"] = “127.0.0.1”;

......

结果发现 paraTmp[“host”]为空,然后打印 log,printf(“host=%s”, paraTmp["host"]);发现依旧为空。

问题原因:其实 Parameters 类是这样重载 []操作符的

代码语言:javascript
复制
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 修饰。

2.6 加密/无密 接口

问题描述:有时候,调用其他 service 接口,莫名其妙的报错,尽然说缺少参数。

问题原因:url 请求串未加密,调用加密接口。加密 url 请求串后调用了无密接口。

解决方法:1、先确认参数名是否拼错? 2、确认有密接口还是无密接口 3、看被调用服务日志

2.7 缩容扩容

问题描述:春节前,为备战微信红包,核心系统进行了扩容,而目前面临缩容问题。而我们的对账平台可以支持逻辑 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。可能会做迁移,对业务透明。

3 CGI

3.1 CGI 输出

问题描述:CGI 的逻辑时,调用一个脚本程序将文件 rsync 到目标机器,成功后通知页面执行成功。而根据新增需求实现,部分情况下,调用新脚本程序 rsync 到目标机器,其他继续走老逻辑。最后发现,走新逻辑,CGI 正常返回,但是页面无法正常显示。而老的逻辑则可以。由于 apache 重定向了标准输入输出,故主要定位方向是查找新逻辑输出的多余数据。

问题原因:CGI 调用新的 shell 脚本将文件 rsyn 到目标机器,但未重定向返回结果日志,从而导致返回给页面时多出很多 rsync 的结果数据,不符合 http 请求响应报文格式,故报错。

解决方法:对比新旧脚本,新增 rsync ... > /tmp/rsync.log 2>&1

4 批处理

4.1 crontab 配置

问题描述:常住内存的批处理程序,中间中断一段时间未发现。

问题原因:因异常逻辑触发隐藏 bug,导致 core,随后没有机制可以保证再次启动。

解决方法:1、即使常驻内存的批处理程序也需要定期检测是否运行中,若为运行,则启动

2、批处理程序增加文件锁机制,crontab 配置定期启动批处理程序

PS:脚本中需要写为绝对路径。若需要相对路径,则需设置环境变量。

5 Shell

5.1 导出 DB 记录

问题描述:select 多列时,若将结果集重定向到一个文件中,结果集并不是多行。

代码语言:javascript
复制
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

代码语言:javascript
复制
//2001 2        500

//10101        2        500

//10102        2        500

5.2 变量作用域

问题描述:第一次写 shell 脚本时,使用了很多 function 方法,也定义了很多变量。当其中一些变量被无故的覆盖了。

问题原因:shell 中变量默认为全局变量,且全局作用。

解决方法:若需要局部变量,则需要使用关键字 local 声明。

5.3 MD5 计算

问题描述:若计算一个字符串"md5test",则使用命令 echo md5test | md5sum | awk '{print $1}',得到 md5 值为 689a850ebe8e2cf48429c9f9879e4300。但是在代码中得到的 md5 值是 82da61aa724b5d149a9c5dc8682c2a45。莫非代码有 bug?

问题原因:使用 echo 时,不加-n 选项,字符串默认会包含\n。

解决方法:增加-n 选项,如 echo -n 'md5test' | md5sum | awk '{print $1}';

5.3 Other

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 中最后被指定的值,如下所示:

代码语言:javascript
复制
x = value

y = $(x) bar

x = xyz

在上例中,y 的值将会是 xyz bar ,而不是 value bar

2) ":="

变量的值决定于它处于 makefile 中的位置,而不是整个 makefile 展开后的最终值。

代码语言:javascript
复制
x := value

y := $(x) bar

x := xyz

在上例中,y 的值将会是 value bar ,而不是 xyz bar

6 Mysql

6.1 库表创建

问题描述:如 2.2 所述,数据库创建时字段默认 NULL 的话,则使用 mysql API 得到的 row[x]为 NULL。

问题原因:default NULL,任何类型都会插入空指针

NOT NULL,则 varchar 类型会插入空字符串,而 int 类型会插入 0

解决方法:为安全起见,建议 varchar default “”,int default 0,或者使用 NOT NULL.

6.2 count/sum

问题描述:如下所示,当记录不存在时,sum(x) 返回的不是 0,而是 NULL,与自己预期的不一致。

代码语言:javascript
复制
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) ...

6.3 update

问题描述:看一下下面的 update 语句,

代码语言:javascript
复制
update c2c_db_inc.t_spm_batopt set Fstatus=1,Fspid='1215224601'and Fmodify_time=now() where Fbatch_id='201506010187728312';

这个是简单的更新语句,至此,你看出什么问题了么?我找了 n 个人看,但都没有看出有什么问题。其实,更新成功了,但是结果却是未预料到的。

代码语言:javascript
复制
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?

解决方法:

代码语言:javascript
复制
update c2c_db_inc.t_spm_batopt set Fstatus=1,Fspid='1215224601', Fmodify_time=now() where Fbatch_id='201506010187728312';

7 iOS

1 暂时未整理,待续

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 Comm
    • 1.1 XML 解析
      • 1.2 高危函数
        • 1.3 签名
          • 1.4 服务迁移问题
            • 1.5 rsync 配置
              • 1.6 时间转换
                • 1.7 跨月查询
                  • 1.8 诡异问题
                  • 2 Middle
                    • 2.1 新建 server
                      • 2.2 SQL
                        • 2.3 xml 解析库
                          • 2.4 core 调试
                            • 2.5 封装问题
                              • 2.6 加密/无密 接口
                                • 2.7 缩容扩容
                                • 3 CGI
                                  • 3.1 CGI 输出
                                  • 4 批处理
                                    • 4.1 crontab 配置
                                    • 5 Shell
                                      • 5.1 导出 DB 记录
                                        • 5.2 变量作用域
                                          • 5.3 MD5 计算
                                            • 5.3 Other
                                            • 6 Mysql
                                              • 6.1 库表创建
                                                • 6.2 count/sum
                                                  • 6.3 update
                                                  • 7 iOS
                                                  相关产品与服务
                                                  云数据库 SQL Server
                                                  腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                                                  领券
                                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档