蓦然回首

1    开发环境问题... 5

1.1      SuSevi乱码问题... 5

1.2      Vi技巧... 5

1.3      转义转义... 5

1.4      令人头疼的^M(/r) 7

1.5      Tcpdump误区... 7

1.6      date详解... 9

1.7      找回被删除文件的方法... 10

2    编译链接问题... 11

2.1      变量名或者函数找不到... 11

2.2      接顺序问题/函数找不到... 11

2.3      宏定义的语法错误... 12

3    运行时问题... 12

3.1      无法执行... 12

3.2      CORE定位... 12

3.3      内存泄漏... 15

3.4      头文件和实现库不匹配... 19

3.5      运行状态分析工具列表... 19

4    网络问题... 19

4.1      Broken pipe. 19

4.2      消息队列阻塞... 20

5    线程、进程问题... 20

5.1      非线程安全的api(或不可重入) 20

5.2      多个线程问题... 21

5.3      加锁之殇... 21

5.4      各种进程线程模式... 21

6    数据库使用问题... 22

6.1      公司数据库安装指导... 22

6.2      慢查询(耗时查询)... 23

6.3      存储过程(mysql) 23

6.4      数据连接... 24

6.5      Mysql编码问题(ZZ自未知作者)... 24

7    Web开发问题... 25

8    ACE使用问题... 26

8.1      事件处理器 ACE_Event_Handler 26

8.2      dev poll / epoll 模式使用... 26

8.3      mysql5.0.45版本数据库的冲突问题... 27

8.4      调整ACE默认的时间格式%D的说明... 27

9    C++是促进脑死亡的最佳方式... 29

9.1      注意参数的型别和函数参数的匹配... 29

9.2      雪崩效应造成处理阻塞... 30

9.3      dlsvrd日志时间出错... 32

10        设计规范... 35

10.1    函数... 35

10.2    日志... 36

11        后记... 36

11.1    作者介绍... 36

11.2    参考文档... 36

11.3    致谢... 36

 


你是否曾经因为某个问题彻夜难眠,加班定位问题到很晚?

你是否曾经因为某个问题茶饭不思,该吃饭的时间还想着要解决这个问题?

你是否曾经因为某个问题导致项目延迟?

……

一直以来,就想整理一下四年来工作中所遇到的所有(维护,开发方面)问题,对这些问题进行总结,探讨某类问题处理的通用方法和解决思路,使得过去的经验能够沉淀下来,给自己(也希望给别人)能带来点好处,在以后的工作中少走弯路,简化工作中遇到的问题,提高工作效率,能够把更多的时间用于……

众里寻他千百度,蓦然回首……

 


1          开发环境问题

本文主要介绍一下man手册讲解的不够详细或者baidu搜索不到的内容。一般的问题大家还是先用man帮助处理,或者网上搜索一下,一般都能很容易得到解决。

1.1          SuSevi乱码问题

案例一 2008-06-15 TC运营环境的特例

现象描述:有时候编辑文件时看到屏幕上有乱码,修改某行之后,vi再次打开发现刚才修改的内容没有生效。

原因说明:这个通常可能是因为乱码导致你修改的行并不是你看到的行,所以重新vi编辑发现想修改的行没有任何变化。

解决办法:

/etc/sysconfig # vi /etc/sysconfig /language

RC_LANG="zh_CN.UTF-8" 改成 “en_US.ISO-8859-1”

RC_LC_ALL=””  改成  "en-US"  

两个变量的作用域不太一样,可以仔细看看文档里面的详细说明

 

重新登录或者 source /etc/sysconfig/language 文件使得配置生效

 

1.2          Vi技巧

基本的技巧请看vi的手册,这里讲述几个很有用,但是很少有手册讲到的技巧。

J合并行

d+[[ 删除第一行到当前行

d+]] 删除当前行到最后行

 

1.3          转义转义

“” ‘’ `` 这些引用各有各的含义,转义的方式也不一样。在开发运维使用中很容易出现一些意想不到的问题。

 

案例一 grep 的问题 2008-07-18

MNET…abs> grep HEADMARK ./res/Web.list.log | awk –F//t ‘$5==”NTPDATE_CHK_WRONG”’ | wc –l

2

MNET…abs>temp=` grep HEADMARK ./res/Web.list.log | awk –F//t ‘$5==”NTPDATE_CHK_WRONG”’ | wc –l `

MNET…abs>echo $temp

0

 

从上面看出``执行之后的结果和不用``执行的结果不同。

 

问题原因:

反引号在取命令的时候,需要解析一个/,然后awk执行的时候,也会解析一个/,所以temp=`***`这样会解析掉2/,这样在命令真正执行的时候,就把awk -F//t 变成了 awk -Ft,改成 awk -F///t就好了。

 

案例二 awk输出单引号等特殊字符的问题 2008-10-28

为了把xls的内容(有中文文本等)生成sql语句,把相关信息导入数据库。(本人比较土,不会用xls直接导入数据库mysql),所以打算用awk处理一下,但是要生成的sql语句在有字符串的时候必须带单引号’’,用单引号引起来,不然语法会有问题,所以遇到了如下问题:

1.怎么把shell环境变量传递给awk

2.怎么在awk里面输出单引号’’

 

第一个问题相对简单,很多书籍应该都有awk接收外部参数这方面的说明,第二个问题网上和书籍上资料相对较少。样例代码如下:

init_data.sh

 

GAME_TYPE=$1

FILE_NAME=$2

cat $FILE_NAME | awk '{

    mid=0;

    if ( $5==1 || $6==1 || $7==1) mid='"$GAME_TYPE"'*10000+$1;

    print "insert into dbHonor.tbEventCfg values " "(" '"$GAME_TYPE"'*10000+$1 ", '"$GAME_TYPE"', 1, " mid ", " $2 "," "'/''"$3"'/''" "," "'/''"$4"'/''" ");";

    if ( $5==1 ) {rank=1;name="黄金徽章";}

    if ( $6==1 ) {rank=2;name="白银徽章";}

    if ( $7==1 ) {rank=3;name="青铜徽章";}

    if ( $5==1 || $6==1 || $7==1) print "insert into dbHonor.tbMedalCfg values " "(" '"$GAME_TYPE"'*10000+$1 "," "'/''"name"'/''" "," '"$GAME_TYPE"' "," rank "," "'/''""'/''" "," "'/''""'/''" ");";

    }' > init_data.sql

 

此段代码涉及到了awk的相对复杂的逻辑代码,分段,逻辑判断,数学运算操作,变量,特殊字符转义等(比如单引号’’ $等特殊字符, / 转义不需要再双重应用"//"即可),所以保存下来作为将来参考使用。

 

1.4          令人头疼的^M(/r)

^M(/r)这个winlinux平台存在差异的特殊字符给开发和运维都带来很多意想不到的问题。令人十分头疼。如果你从win上传递到linux系统的文件不能正常执行,awk grep sedlinux下的这些命令执行异常,或者本来好好的程序无法正常编译连接,那么你可以毫不犹豫的先怀疑它,谁叫它长的丑令人讨厌呢。(^M说:长的丑又不是我的错。我说:没错,但是你不要总出来吓人嘛J

 

案例一:2008-8-29 一位同事在开发中遇到如下的提示信息

../include/pet_error_code_function_implement.h:185: stray '/' in program

正常来说提示 stray 的问题都是因为有特殊字符,但是这个提示根本没提示有什么字符,不是很明显,所以比较奇怪。

根据以往的经验,估计是有不可见字符了,然后用vim看了一下文件,发现果然是有^M了,截图如下

 

解决方法:传递文件时采用ascii方式传递,或者把文件进行dos2uinx转换(比如:cat file.txt | tr -d '/r')。

经验总结:用gcc 2.95版本编译程序时时,如果程序文件中存在这些特殊字符会导致无法进行正常编译。需要去掉才能正常编译。gcc高版本3.3.4以上好像不受影响。

 

1.5          Tcpdump误区

有时候按照tcpdump的手册和网上很多资料进行监控时可能监控不到信息,比如:

172.16.225.110执行监控(期望监听本地的5694端口):

tcpdump -vv host 172.16.225.110 and port 5694

默认监听的是eth1网卡(接口)上的数据,如果是本地和本地的网络通信,则监听不到任何消息。必须用 -i 参数指定 lo 接口,比如:

tcpdump -vv -ilo host 172.16.225.110 and port 5694

 

tcpdump 的输出结果介绍
     下面我们介绍几种典型的tcpdump命令的输出信息
   (1) 数据链路层头信息
   使用命令#tcpdump --e host ice
   ice 是一台装有linux的主机,她的MAC地址是0902758AF
1A
   H219是一台装有SOLARICSUN工作站,它的MAC地址是8020795B46;上一条命令的输出结果如下所示:

21:50:12.847509 eth0 < 8:0:20:79:5b:46 0:90:27:58:af:1a ip 60: h219.33357 > ice.telnet 0:0(0) ack 22535 win 8760 (DF)
  分析:215012是显示的时间, 847509ID号,eth0 <表示从网络接口eth0 接受该
数据包,eth0 >表示从网络接口设备发送数据包, 8:0:20:79:5b:46是主机H219MAC地址,
表明是从源地址H219发来的数据包. 0:90:27:58:af:1a是主机ICEMAC地址,表示该数据包的
目的地址是ICE . ip 是表明该数据包是IP数据包,60 是数据包的长度, h219.33357 > ice.
telnet
表明该数据包是从主机H21933357端口发往主机ICETELNET(23)端口
. ack 22535
表明对序列号是222535的包进行响应. win 8760表明发送窗口的大小是
8760.
  (2) ARP包的TCPDUMP输出信息

  使用命令#tcpdump arp 得到的输出结果是:
  22:32:42.802509 eth0 > arp who-has route tell ice (0:90:27:58:af:1a)
  
22:32:42.802902 eth0 < arp reply route is-at 0:90:27:12:10:66 (0:90:27:58:af:1a)
  分析: 22:32:42是时间戳, 802509ID, eth0 >表明从主机发出该数据包, arp表明是

ARP
请求包, who-has route tell ice表明是主机ICE请求主机ROUTEMAC地址。 0:90:27:5
8:af:1a
是主机ICEMAC地址。

  (3) TCP包的输出信息
  用TCPDUMP捕获的TCP包的一般输出信息是:
  src > dst: flags data-seqno ack window urgent options
  src > dst:表明从源地址到目的地址, flagsTCP包中的标志信息,S SYN标志, F (F
IN), P (PUSH) , R (RST) "." (
没有标记); data-seqno是数据包中的数据的顺序号, ack是下次期望的顺序号, window是接收缓存的窗口大小, urgent表明数据包中是否有紧急指针. Options是选项.
  (4) UDP包的输出信息
  用TCPDUMP捕获的UDP包的一般输出信息是:
  route.port1 > ice.port2: udp lenth
  UDP十分简单,上面的输出行表明从主机ROUTEport1端口发出的一个UDP数据包到主机ICEport2端口,类型是UDP 包的长度是lenth

 

1.6          date详解

曾经有不少人问到过shell里面用date怎么获得一天前(两天后)等的时间,

今天正好要做一个有关监控的东西需要用的时间,所以顺便总结一下

也许有不少人知道可以使用类似

date --date='yesterday' "+%Y-%m-%d %H:%M:%S"

等格式,但是yesterdayyesterday 呢?再往前一天呢?

前一天何其多,英文单词都有表示吗?

 

也许又有人知道可以使用类似

date -d "1 days ago" +"%Y-%m-%d" 

date -d "1 hours" "+%Y-%m-%d %H:%M:%S"

等格式,不错,这样差不多就能解决所有的时间显示问题了,但是总感觉不是很好

 

man date 的时候大家应该都见过这一行

-d, --date=STRING

              display time described by STRING, not `now'

虽然是这样的简单一行说明,但是有多少人能真正了解这个背后所隐藏的秘密呢?下面听笔者详细道来:)

 

STRING 的格式有几种?都可以用那些形式表示?

据说没有人知道,因为知道的人都没有把它记录下来

 

据本人的了解除了上面的之外,最好的一种就事这样

("+/- num unit")/{1,/}

/{1,/} 表示前面()里面的格式匹配一次以上(不是很标准的表示)

+表示将来的时间 -表示过去的时间

num 是你想要的数目

unit 是时间单位,具体可以取那些值?告诉你,可以取从年到秒的常用的时间单位

     year/years

     month/months

     week/weeks

     day/days

     hour/hours

     minute/minutes/min

     second/seconds/sec

因为系统本身的限制,到了19012038之外的年份可能就无效,大家可要注意哦,不要到时候说没提醒:)

下面为举例说明:

 

1 1个月零10天后(40天后)的时间:

root@darkstar:/usr/local/ipgw# date -d "+1month +10day" "+%Y-%m-%d %H:%M:%S"

2006-07-25 11:07:03

root@darkstar:/usr/local/ipgw# date -d "+40day" "+%Y-%m-%d %H:%M:%S"       

2006-07-25 11:08:33

 

2 明年昨日的时间

root@darkstar:/usr/local/ipgw# date -d "+1year -1day" "+%Y-%m-%d %H:%M:%S"

2007-06-14 11:10:02

 

其实应该还有很多秘密是没有了解到的,希望有兴趣的有心人能指点一二:)

 

date -d "+%Y-%m-%d %H:%M:%S"

 

通过timestamp计算时间

date -d "1970-1-1 +1190858275sec"

 

显示时间蹉 %s

date "+%s"

 

1.7          找回被删除文件的方法

(前提是该文件必须被使用)

1lsof | grep delete,可以看到删除的文件名,以及被哪个进程锁定

2ps axu | grep "进程名"

3cd /proc/"pid"/fd/,找到相应的文件cp回原目录即可

 

2          编译链接问题

2.1          变量名或者函数找不到

系统告诉你找不到那就是找不到,找不到的原因可以有很多,但是肯定是找不到,一定要相信编译器告诉你的信息。

案例一:20080729 一位同事在编译程序时出现这样的提示

supper_user.cpp:15: error: syntax error before `*' token

supper_user.cpp:17: error: syntax error before `*' token

supper_user.cpp:28: error: syntax error before `::' token

supper_user.cpp:36: error: syntax error before `::' token

supper_user.cpp:42: error: ISO C++ forbids declaration of `prevPos' with no

从提示字面理解是*分割符的语法错误,这种错误比较明确可以判断是因为*前面的数据类型找不到定义,然后编译器认为语法错误了。经过确认之后是因为cpp文件中没包含需要的.h头文件的数据结构声明,加入.h头文件之后此问题解决。

案例二:no matching function

 

 

2.2          链接顺序问题/函数找不到

案例一:

20050613 因为互娱支付中心的开发,使用到公司的portal api,此api中使用的库自带了tlib库,用其他的库可能不兼容,此非重点,重点的是连接库的时候总提示某个函数找不到,用nm之类的工具查看,明明指定连接的库里面有这个函数,甚是迷惑,调试了半天,问了一下导师,才知道原来是其中的两个库之间存在依赖关系,被依赖的库放错了顺序,放到了前面了。顺序调换之后问题解决。

 

案例二:

20080709 编译商城svr的程序,编译商城svr ace5.6版本的程序,链接时出现类似这样的提示

../../easyace//libeasyace5.6.a(ace_app.o)(.text+0x12e0): In function `ACEApp::init_log(int)':

/usr/local/ace_using/easyace/ace_app.cpp:263: undefined reference to `ACE_Logging_Strategy::ACE_Logging_Strategy[in-charge]()'

../../easyace//libeasyace5.6.a(sys_conf.o)(.text+0xcd): In function `SysConf::load_conf(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':

/usr/local/ace_using/easyace/sys_conf.cpp:19: undefined reference to `ACE_Configuration_Heap::ACE_Configuration_Heap[in-charge]()'

../../easyace//libeasyace5.6.a(sys_conf.o)(.text+0x12e):/usr/local/ace_using/easyace/sys_conf.cpp:20: undefined reference to `ACE_Configuration_Heap::open(unsigned)'

 

提示ACE里面的一些库找不到,ACE的库理论上是正常的,nm之类的工具查看了一下确实有这些函数,出现这个提示明显可以判断是因为库的顺序放错了,如下

LIBS= -static … -lACE -leasyace5.6 -lnsl -lm -lz -lc -ldl -lpthread -lrt –lepoll

上面的easyace5.6的库是基于ACE的库产生的,因为被依赖的需要放在后面,把ACEeasyace5.6的库顺序调换之后,问题解决。

2.3          宏定义的语法错误

 

 

3          运行时问题

3.1          无法执行

无法执行的情况主要有以下几个方面:

1          是否有执行权限,如果没有,用 chmod +x 之类的命令加上

2          是否是二进制文件,如果可执行程序通过win传递,传递方式采用了ascii方式的话,那就会导致程序无法执行了,因为格式会变化。

3          传输的文件是否完整,如果传输格式没问题,也有可执行权限,如果还是无法执行,可能文件本身不完整了,可以采用md5sum进行文件校验

 

3.2          CORE定位

出现core的情况,比较常见的触发原因有几个,下面进行简单的介绍。

SIGSEGV 段错误(信号11),说白了就是访问了非法内存地址,无论是超出进程范围的(地址越界)还是系统不存在的内存访问(指针指向空)

SIGABRT 检测异常(信号6 调用了abort()函数导致,最常见的是对释放的内存(free())再次进行释放,或者内存分配失败等原因的时候会触发

SIGBUS 本意是指总线错误(信号7),一般出现在当你访问一段非法地址,并且这段地址物理上是不存在的。它类似于SIGSEGV,后者也是访问非法地址,但是这个地址是虚拟地址空间的地址,SIGBUS正好相反,由于某些机器上,物理空间存在不连续的情况,访问到这些hole时,就会产生这个信号。硬件故障。

SIGILL 一般是硬件方面的问题,比如用%s格式直接输出string类型的(没有通过c_str()转换)一般会导致这个信号并退出进程(一般编译时就会提示cannot pass objects of non-POD type …call will abort at runtime)。

具体的信号查看方式请使用 kill -l 命令(对于信号的问题请详细查看《unix环境高级编程》的介绍)

[aas@~]#  kill –l

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL

 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE

 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2

13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT

17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP

21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU

25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH

29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN

35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4

39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8

43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12

47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14

51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10

55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6

59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2

63) SIGRTMAX-1  64) SIGRTMAX

 

案例一:告警平台曾经出现过这样的core信息

root@darkstar:/usr/local/alarmplat/bin# gdb alarmplatd core.6288

GNU gdb 6.3

Copyright 2004 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "i486-slackware-linux"...Using host libthread_db library "/lib/libthread_db.so.1".

 

Core was generated by `40]iptables中发现非法udp规则32770'.

Program terminated with signal 11, Segmentation fault.

#0  0x0815becf in memcpy ()

(gdb) bt

#0  0x0815becf in memcpy ()

#1  0x08057f07 in CAlarmRecvHandle::processMsg (this=0x40300fd8) at OS_NS_string.inl:35

#2  0xd673656c in ?? ()

#3  0xcfa2b7d0 in ?? ()

 

从上面的信息可以看出是因为信号11出现程序终止,出现这个信号的原因是访问越界,从堆栈信息可以看出大概是processMsg函数里面的memcpy存在访问越界的问题,找到代码中此行确实没有判断拷贝的消息长度是否超过szContent内存的大小限制。

memcpy(szContent, szMsgRcvBuf_ + PROTOCOL_HEADER_LENGTH, iCurrentMsgLen_ - 12);

修改加上判断之后,问题解决。

 

案例二:

2008-7-30 有个同事编译程序时看到提示如下

make: warning: Clock skew detected. Your build may be incomplete.

 

程序一执行就出现core down的情况,这个提示的意思是因为文件可能比操作系统现在的时间还新,导致时钟错乱,编译不一定顺利完成。也就是说你的声明文件的数据结构和实现(定义)的数据结构或者函数地址可能是不匹配的。这样程序一旦执行,很有可能就异常退出。

make clean 一次程序,然后重新编译连接你的应用,问题应该能解决。

 

案例三:文件过大的问题,信号25

防沉迷有一天出现coredown,查看core信息如下:

[aas@bin]#gdb aas_connect /data/corefile/core_aas_connect_1246686328.24608

GNU gdb 6.6

Copyright (C) 2006 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "i586-suse-linux"...

Using host libthread_db library "/lib/libthread_db.so.1".

Core was generated by `aas_connect'.

Program terminated with signal 25, File size limit exceeded.

#0  0x08073acd in write ()

(gdb) where

#0  0x08073acd in write ()

#1  0x08136a00 in ?? ()

#2  0x0806601f in _IO_new_file_write ()

#3  0x0806550e in new_do_write ()

#4  0x080654b6 in _IO_new_do_write ()

#5  0x08065007 in _IO_new_file_close_it ()

#6  0x08061828 in fclose ()

#7  0x0804abb5 in Log (pstLogFile=0xbf8b73cc, iLogTime=1, sFormat=0x80a6d46 "listen IP=%s, port=%d, fd=%d") at oi_log.c:111

#8  0x0804a477 in main (argc=1, argv=0xbf9364e4) at aas_connect.c:988

 

 

 

3.3          内存泄漏

内存是否有泄漏是比较容易确认的,比较高端的系统本身有内存泄漏检测工具,比如valgrind等,其实没有这些工具我们一样可以轻松定位内存泄漏的问题,借助topvmstat等系统自带的工具即可。

案例一:2008-06 前不久dnf商城svr的应用的问题。

进程信息(top查看) top -p 7377

进程信息截图说明运行的程序占用了太多内存(因为内存泄漏)。

然后再看了一下开发环境的代码,果然看到在业务逻辑处理的代码中存在资源没释放的问题,比如:

1   MYSQL_RES* game_res = NULL;

2   ret_ = db_access_->exec_query(sql, game_res);

3   if(0 != ret_)

4   {

5       ACE_DEBUG((LM_INFO, "[%D] Warnning: execute sql=%s failed, ret=%d/n", sql, ret_));

6       return ret_;

7   }

8   int row_num = mysql_num_rows(game_res);

9   if(0 == row_num)

10  {

11      ACE_DEBUG((LM_INFO, "[%D] Warnning: get res num rows, row_num=%d/n", row_num));

12      sprintf(ack, "level=0");

13      return ret_; //没角色数据

14  }

...

获取结果成功之后的第13ret返回时没有把资源给释放掉,缺少 mysql_free_result(game_res); 调用,会存在mysql结果集的内存资源没有释放。

away把这些情况跟bwar说了一下之后,bwar按照建议把这些地方都补好了,然后更新到运营环境里面,本就以为万事大吉了,也没太注意看其他地方是否有内存泄漏的可能。

第二天2008-7-17下午上班后,找bwar再了解了一下商城svr目前的情况,得知37 39两台机器还是有可当,存在内存泄漏时,纳闷了一会,核对了数据库操作的每段代码,确实经过bwar修改之后数据库操作的部分应该不会存在内存泄漏的可能。然后聊天如下:

 

awayfang(方学维) 14:12:26

今天商城svr还有问题吗?调试一下看看什么问题

bwarliao(廖宝华) 14:24:11

刚发现一个问题,好像只有36、37、39这三台server会经常core,尤其是37和39,这两台server都是做了插入操作,其它的都只是查询,但访问量相差并不是十分大的

bwarliao(廖宝华) 14:28:18

只做查询的server 38 几乎没core过

awayfang(方学维) 14:29:43

top看看分别占用多大

awayfang(方学维) 14:34:36

37 39 都有内存泄漏啊,看看top就知道了

awayfang(方学维) 14:34:58

37 39 也都是最新版本吗?

awayfang(方学维) 14:35:07

和38,功能上有什么差别?

bwarliao(廖宝华) 14:35:07

37 39 都用了这个函数int DNFDbOper::send_item(QModeMsg &req, char* ack)

awayfang(方学维) 14:36:26

if (iconv(cd,&pin, &inlen, &pout, &outlen)==-1)

    {

        return errno;

    }

    iconv_close(cd);

 

估计你这里有问题,出错时,没close

awayfang(方学维) 14:36:59

cd = iconv_open(to_charset,from_charset);

这个好像是分配资源的

bwarliao(廖宝华) 14:57:36

37更新了,似乎没有内存增长的迹象

那个函数alex用了好久了,我直接拿过来都没怎么细看

typedef void *iconv_t; 确实是个内存操作,比较隐蔽

bwarliao(廖宝华) 14:59:05

看来写server和写一般的程序区别还是很大啊,这个内存泄漏在我们跑一段时间就停下来的程序确实不容易发现,也几乎不会有什么问题

awayfang(方学维) 14:59:15

那应该没什么大问题了

awayfang(方学维) 14:59:59

做svr程序可是长久的事情,普通程序,功能没问题就行了,内存泄漏没关系,进程退出什么事都没了(资源自动被操作系统回收),svr程序可不能有任何问题。

至此,DNF商城svr出现可当的问题应该算是彻底解决了,多谢bwar提供了很多有用的信息,才能这么顺利解决。

问题总结:

正常情况下,框架本身占用的常驻内存(RES)资源从10M-20M左右不等,连接数越多,处理连接的处理器对象越多,内存占用越大,但不可能达到上百M

正常的截图如下

 

 

 

 

DNF商城的某个机器(37)上看到的情况是这样的

这个结果看来是明显有内存泄漏了,不然一个进程怎么会占用603M的常驻内存。

 

内存泄漏的具体原因也不难定位,有指针的地方就可能会有内存分配,你不动态分配内存空间,不代表别人(你调用的api接口)不动态分配,所以编程时一定要仔细检查,判断是否有问题的方法也简单,程序运行之后看看资源占用情况即可。

例如:

iconv_t cd;

    memset(outbuf,0,outlen);

    char *pin = const_cast<char *>(inbuf);

    char *pout = const_cast<char *>(outbuf);

 

    cd = iconv_open(to_charset,from_charset);

    if (cd==0) return errno;

    if (iconv(cd,&pin, &inlen, &pout, &outlen)==-1)

    {

        return errno;

    }

iconv_close(cd);

这段文本转换的函数,之前也没有用过,看到前面有open操作,后面有close操作,根据经验判断猜想应该是有资源分配的,所以推测应该是这里的问题,iconv转换失败的时候没有进行close操作,导致内存泄漏。

3.4          头文件和实现库不匹配

如果头文件的数据结构的同名字段和实现文件的(lib)里面的长度不匹配(比如char数组的长度)就会导致程序一启动执行到这个地方就出现core的情况。

 

3.5          运行状态分析工具列表

程序运行时我们怎么分析系统是否正常,具体都有哪些常用的工具呢?

gdb attach

strace 查看进程执行到了哪个系统调用

例如:

strace /usr/local/nginx/sbin/im_fcgi 可以查看到运行的im_fcgi具体正在执行哪些系统调用

 

ltrace 查看库的系统调用情况

例如:

 

4          网络问题

网络问题产生的原因是因为对网络知识没有彻底理解造成的,建议好好学习一下《Unix Network Programing》,吃透这本书(建议至少好好看三遍)才真正能说懂得网络编程了。

4.1          Broken pipe

现象:屏幕出现broken pipe的提示,程序被终止(core down)

原因:向对端(读端)已经关闭连接的socket管道写入数据造成的,系统错误号为32(EPIPE)。这个产生的可能很大部分是因为对端(读端)可能已经关闭了这个socket(调用了close函数),但是你还是继续向这个socket句柄send数据,这样就会导致这种情况的发生。

案例一:200810月份,一位同事做的网络程序,有段代码如下

    while (-1 == peer_.send_n(mblk->rd_ptr(), ACE_OS::strlen(mblk->rd_ptr())))

    {

        peer_.close();      // 关闲原链接

        if (-1 == reconnect())

        {

            ACE_DEBUG ((LM_ERROR,

                "@Error[%D](%N,%l) I can't REconnect ! /n"));

            return -1;

        }

    }

peer_ ACE_SOCK_Stream的类,封装了socket里面数据流的概念,可以用来操作收发消息(send,recv),此代码一开始就发送数据,如果是对端已经关闭的连接则会导致broken pipe问题的发生。ACE中也可以用MSG_NOSIGNAL标识来防止程序因为这个原因退出。

 

4.2          消息队列阻塞

netstat  可以查看消息队列的阻塞情况。具体参考相关的man手册

 

5          线程、进程问题

线程,进程的安全问题说起来简单,有过相关经验的人都知道是怎么回事,可是哪些系统api是线程安全的,哪些不是并不是每个人都能知道的,也不是那么容易知道的。自己的多线程模式,没有处理好一不小心就掉了下去,下面分别从几个方面说明一下线程的安全。

5.1          非线程安全的api(或不可重入)

Linux/Unix系统里面不少api是非线程安全的,如果不了解api实现原理很难知道是否是线程安全的,但是既然从原理上推断不了结论,那么我们就从现象上推断结论。一般来说,现在的系统api如果对应的函数不是线程安全的,都会有一个_r的线程安全函数与其对应。所以从这点来看,只要我们 man 这个函数看到有同名_r的函数,那么可以判断这个函数是非线程安全的。

比如man localtime发现有这样的函数

char *asctime(const struct tm *tm);

 char *asctime_r(const struct tm *tm, char *buf);

 

 char *ctime(const time_t *timep);

 char *ctime_r(const time_t *timep, char *buf);

 

 struct tm *gmtime(const time_t *timep);

 struct tm *gmtime_r(const time_t *timep, struct tm *result);

 

 struct tm *localtime(const time_t *timep);

 struct tm *localtime_r(const time_t *timep, struct tm *result);

由此我们可以断定以上的不带_r的函数都不是线程安全的。

注:库的线程安全性也可以类推

有些是不可重入的(可重入的函数在unix环境高级编程一书的10.6章节有详细说明)。这样对多线程编程存在很大的安全隐患。

 

案例一:

 

 

 

5.2          多个线程问题

下面举一个例子:20087月,有个同事在测试时,发现正常测试时,系统正常运行,但是一旦很大的压力,程序就会出现core down的情况,很纳闷,找了比较久都没找到原因。查看core文件,大概说明core的位置点如下:

 

// put receive message to module: Logp_Logging_Task

if (Logp_Logging_Task::instance()->put (host_name_mblk) == -1)

{

    // release all the contination ACE_Message_Block

    host_name_mblk->release ();

    ACE_DEBUG ((LM_ERROR,"@Err  [%N,%l] call Logp_Logging_Task::instance()->put() error!/n"));

    return 0;

}

 

ACE_DEBUG((LM_DEBUG, "@Deb  [%l] received message from:[%s]/n", host_name_mblk->rd_ptr()));

 

线程模式简单介绍一下:Logp_Logging_Task 是工作线程,主线程调用其 put 函数把消息放到其内部的消息队列中,然后工作线程会对主线程放进来的消息进行主动处理。

这个地方的问题就在红色部分的代码,单看起来是不会有什么问题的,

5.3          加锁之殇

一旦谈到线程,进程安全就必然涉及到进程锁,线程锁,保证多进程,多线程访问有限资源时,对有限资源进行的串行访问保护机制。

经常听到有人抱怨,这个程序怎么挂死,没什么反应了,进程还在,就是没有任何本来应该有的输出了。这种原因很可能是因为你的进程内部代码产生了死锁了。

 

避免某个锁释放之前再次调用获取该锁的函数。

尽量晚加锁,让锁的生命周期尽量缩短。

 

5.4          各种进程线程模式

Slackware 10.1.0上的测试

THR_NEW_LWP

 

THR_SCOPE_PROCESS (the old M:N model) has gone extinct.

没有启动线程池

 

THR_SCOPE_SYSTEM

 

SUSE Linux Enterprise Server 10 (i586)

THR_NEW_LWP

 

 

6          数据库使用问题

6.1          公司数据库安装指导

1.获得二进制安装包,一般放到/usr/local/目录,解包

2.创建mysql用户,并建立如下目录,修改相关的属主

    mkdir -p /data/mysqldata/innodb/log/

    mkdir -p /data/mysqldata/innodb/data/

    mkdir -p /data/mysqllog/binlog

    chown -R mysql /data/mysql*

    chgrp -R mysql /data/mysql*

3.从现网环境获取 my.cnf, 存放在/etc/,修改 bind-address=xxxx 指定自己的服务器 ip 即可

4.执行安装

    ./scripts/mysql_install_db --user=mysql

5.启动 mysql

    ./bin/safe_mysqld --user=mysql &

6.因为数据空间创建需要时间,等半个小时左右吧,即完成安装

 

6.2          慢查询(耗时查询)

数据库的全量查询或者like等文本内容匹配的查询都是性能很差的慢查询,随着数据量越大,耗时会越久。比如告警平台的系统里面有一段这样的代码:

 

   snprintf(szSqlBuf, MAX_SQL_BUF_LEN - 1,

       "select count(*) from %s.%s where desuser = '%s' and content = '%s' /

       and (unix_timestamp(NOW()) - unix_timestamp(gettime)) between 0 and %d;",

       szAPlatDbName_, szAPlatTblAlarm_, stAlarm.szDesUser,

       stAlarm.szContent, iRepeatTime

       );//最近多少天保持不重复

 

这个数据库语句用到了文本的匹配处理,并且是对数据库内容的全量数据进行操作,随着数据量的增大,这个性能的消耗将是非常可观的。对系统的性能影响会很大。

 

6.3          存储过程(mysql)

Mysql支持存储过程从5.0版本才开始,所以之前的老版本是没有这么“高级”的特性的。在连接模式上多了两种模式:

CLIENT_MULTI_STATEMENTS 通知服务器,客户端可能在单个字符串内发送多条语句(由‘;’隔开)。如果未设置该标志,将禁止多语句执行。

CLIENT_MULTI_RESULTS 通知服务器,客户端能够处理来自多语句执行或存储程序的多个结果集。如果设置了CLIENT_MULTI_STATEMENTS,将自动设置它。

mysql_next_result() 解决存储过程多结果集的问题。

 

案例一:2009-03-31

问题描述:mysql存储过程执行的问题,有些执行正常,换成另外一个就有问题了,提示大概如下:

Error:mysql_query failed PROCEDURE wgdb_s1.pro_gm_activity_exp_interface can't return a result set in the given context    SQL:call wgdb_s1.pro_gm_activity_exp_interface(5,'2009-03-31','2009-04-01',15,23,@ret)

解决办法mysql_..._connect(...)最后一个参数是设置连接标志的取值写上 CLIENT_MULTI_RESULTS 即可

问题原因:未知,从解决结果看可能是结果集的问题,这个sp返回了多个结果集,导致连接模式不匹配提示错误。

 

6.4          数据连接

 

案例一:

问题现象:

Tencent:~ #  mysql

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/data/mysqldata/mysql.sock' (2)

Tencent:/data # mysql -h172.27.130.48 -uroot

ERROR 2003 (HY000): Can't connect to MySQL server on '172.27.130.48' (111)

 

6.5          Mysql编码问题(ZZ自未知作者)

最近遇到mysql著名的编码问题,经过一番追究和官方文档帮助,终于解决,并总结出些经验:

一.原理

影响mysql乱码的问题的根本原因包含字符集和校验规则

mysql编码和校验规则分为四个级别:服务器级、数据库级、表级和连接级

这四个级别的编码和校验规则都有两个值:默认值和用户设置值

服务器级:默认值在my.cnf中确定,这是由编译安装时配置定下来的

设置值是在运行mysql时通过 --default-character-set修改

数据库:默认值继承自服务器级 设置值在create database中指定

表级:默认值继承自数据库级,设置中在create table中指定

连接级:默认值由配置文件[mysql]节点配置值决定,设置在set namesset character_set_connection  x)中指定.

mysql的查询或者更新或者插入过程如下:

1.客户段用character_set_client的字符集发送sql

2.服务器接收到sql语句后用character_set_connection的编码转换sql语句,并执行

3.将结果以character_set_results的编码返回给客户端

因此如果用set names xxxz将这三个编码统一到xxxx,基本上可以避免大部分的乱码问题.

二.遇到的问题

A Database Error Occurred

Error Number: 1267

Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation 'like'

select * from HostBase where ServiceArr like '%QQ幻想%'

这是一个查询报错,由mysqlapi报出.查询前set names utf8了。

为啥会出现这个问题呢?从报错来看,可以知道默认的校验规则latin1_swedish_ci和设置的规则utf8_general_ci发生冲突。从sql语句来说,问题肯定出现在中文的"幻想"的校验规则上,因为英文字母无论是latin1utf8都是一样。这个数据库表的编码是latin1,ServiceArr没指定编码,因此继承表的编码latin1,因此校验规则必然是latin1_swedish_ci,对于sql语句中的'幻想'mysql有如下的规定,如果字符串没指定编码,那么编码继承自character_set_connectio,也就是utf8,由校验规则的优先级原理,ServiceArr like '%QQ幻想%'用的是ServiceArr的校验规则latin1_swedish_ci.一个字符集上的校验规则是不能用在其他字符集上的,因此发生以上错误。

三.解决办法

知道原理和问题产生的原因,解决起来就比较容易了

只需将sql转换为latin1,然后在set names latin1即可:)

,总结

mysql的编码比较复杂,但对使用而言,只要保证如下两点可以解决大多数问题

1.我们传给mysql服务器的编码和从mysql服务器中取出来的编码是一致的

也就是set namse xxxx;

2.应用程序编码最好也和第一步中的xxxxx一致。

 

7          Web开发问题

案例一:http头部问题

HTTP 1.0 …  缺少导致客户端解析异常

 

 

8          ACE使用问题

8.1          事件处理器 ACE_Event_Handler

案例一: 2008-6  非时间驱动的处理器(网络IO事件处理器等)

有位同事在实现udp的一个网络服务器模型时,网络通信模块的时间处理器直接基于ACE_Event_Handler实现,模型完成之后发现客户端发送的消息,此udp服务器无法响应任何消息,

经过定位之后发现原来是因为这个时间处理器的类没有实现下面的这个函数。

virtual ACE_HANDLE get_handle (void) const;

总结:非时间驱动的处理器必须要重载get_handle()函数(c++网络编程这本书里面有明确的说明,大家平时看书还是要仔细体会J),不然事件回调函数handle_...之类的函数无法被Reactor框架正常回调。

 

案例二:2009-5-12 dynamic_cast 转换异常

ISGWIntf* intf = dynamic_cast<ISGWIntf*>(eh);

 

动态编译的时候遇到的问题,此问题暂时没解决。

 

8.2          dev poll / epoll 模式使用

案例一:

2008718 slackware下商城svr框架要支持epoll模式,通过编译好支持epollACE库之后,链接应用程序时正常,启动后会出现如下提示:

ACE_Dev_Poll_Reactor::open failed inside ACE_Dev_Poll_Reactor::CTOR: Invalid argument

ISGWApp init ACE_Dev_Poll_Reactor failed, errno:22|Invalid argument, ret=0

经过和sailzeng的讨论,发现slackware这个操作系统很特殊(好在及时咨询,不然要搞死),操作系统里面特别有个epoll库(/usr/lib/libepoll.a),连接程序时需要明确指定连接进来才行,在makefile文件中加入 -lepoll 进行链接问题解决(不使用ace,直接使用epoll,在slackware10.1下面仍然是需要另外链接这个库的)

 

案例二:

2008723 有位同事在slackware10.1操作系统上使用ACEepoll模式时,一切按照正常的使用方式使用,其他几个操作系统开发使用都没问题,唯独这台机器(gcc3.3.4),运行时仍然出现core的情况,core信息如下

 

#0  0x00000000 in ?? ()

#1  0x080787d5 in ACE_Dev_Poll_Reactor::schedule_timer (this=0x8204ed8, event_handler=0x82059e8, arg=0x0, delay=@0xbfd19138, interval=@0xbfd19140)

    at Timer_Queue_T.inl:205

#2  0x0808f742 in ACE_Reactor::schedule_timer (this=0xbfd19138, event_handler=0x82059e8, arg=0x0, delta=@0xbfd19138, interval=@0xbfd19140)

    at ../../ace/Reactor.cpp:689

#3  0x08084222 in ACE_Logging_Strategy::init (this=0x82059e8, argc=9, argv=0x8205918) at Time_Value.inl:81

#4  0x0805bd28 in ACEApp::init_log (this=0xbfd19630, log_num=-1) at ace_app.cpp:263

#5  0x0805b164 in ACEApp::init (thi

s=0xbfd19630, argc=1, argv=0xbfd19974) at ace_app.cpp:146

#6  0x08058a2e in main (argc=1, argv=0xbfd19974) at iigw_svrd.cpp:22

 

此问题比较特别,可能跟操作系统版本有关,因为没有时间也没有特意去看,所以此问题最终什么原因还没搞清楚。

8.3          mysql5.0.45版本数据库的冲突问题

案例一: 前段时间业务在使用我们的ACE svr框架时,因为数据库版本的问题导致编译链接无法完成,提示信息如下

/usr/local/mysql/lib/libmysqlclient.a(ssl.o):(.gnu.linkonce.d.__vt_Q25yaSSL7Message+0x8): undefined reference to `__pure_virtual'

/usr/local/mysql/lib/libmysqlclient.a(ssl.o):(.gnu.linkonce.d.__vt_Q25yaSSL7Message+0xc): undefined reference to `__pure_virtual'

/usr/local/mysql/lib/libmysqlclient.a(ssl.o):(.gnu.linkonce.d.__vt_Q25yaSSL7Message+0x10): undefined reference to `__pure_virtual'

/usr/local/mysql/lib/libmysqlclient.a(ssl.o):(.gnu.linkonce.d.__vt_Q25yaSSL7Message+0x14): undefined reference to `__pure_virtual'

 

此问题暂时未解决,可能和mysql的库编译相关。

8.4          调整ACE默认的时间格式%D的说明

ACE默认的日志格式为  [Thu Dec 10 2009 16:17:17.897167]

看起来会比较别扭,不舒服,如果不修改ACE库的源码好像没什么好办法能够解决,运维同事一直感觉不爽,

所以最近就对ACE的源码进行了修改,并重新编译了新的ACE相关库,具体修改说明如下:

 

ACE的日期格式是在ACE.cppACE::timestamp函数中实现的,为了不对ACE产生大的影响,只是简单了加了一段宏定义,如下:

 

#if defined (WIN32)

  // Emulate Unix.  Win32 does NOT support all the UNIX versions

  // below, so DO we need this ifdef.

  …

  return &date_and_time[15 + (return_pointer_to_first_digit != 0)];

#elif defined (ACE_USE_MY_TIMESTAMP) //add by awayfang 2009-12-11

  ACE_Time_Value cur_time = ACE_OS::gettimeofday ();

  ACE_Date_Time date_time(cur_time);

 

  ACE_OS::sprintf (date_and_time,

                   ACE_TEXT ("%04d-%02d-%02d %02d:%02d:%02d.%06d"),

                   date_time.year(),

                   date_time.month(),

                   date_time.day(),

                   date_time.hour(),

                   date_time.minute(),

                   date_time.second(),

                   cur_time.usec ()

                   );

  return &date_and_time[10 + (return_pointer_to_first_digit != 0)];

#else  /* UNIX */

 

因为用到了ACE_Date_Time,所以ace.cpp文件头部需要增加  #include "ace/Date_Time.h" //add by awayfang 2009-12-11

 

按照编译ACE库的流程,修改 ace/config.h ,增加 #define ACE_USE_MY_TIMESTAMP 进行make & make install 操作即可

 

修改后的日志格式为:

[2009-12-11 13:45:36.369405] init app succeed

[2009-12-11 13:45:36.369420] write pid file ./isgw_svrd.pid

[2009-12-11 13:45:36.369520] write pid file succeed

[2009-12-11 13:45:36.369535] enter daemon main pid: 24067

[2009-12-11 13:45:36.369549] within daemon main pid: 24067

 

 

9          C++是促进脑死亡的最佳方式

此章节是专门用来整理一位朋友sailzeng(曾星)给我们共享的一些经验,本章节内容全部为sailzeng编写或者整理,如有转载请说明

9.1          注意参数的型别和函数参数的匹配

检查COMMLIB部分,发现GCS部分一个告警。

pet_gcs_interface.cpp:212: warning: passing NULL to non-pointer argument 3 of 'ssize_t ACE_SOCK_Stream::send_n(const void*, size_t, int, const ACE_Time_Value*, size_t*) const'

检查代码。报告告警的地方在。

    petappframe->FrameHeadEncode();

    ret = client_stream.send_n((void *)petappframe,len,NULL);

跳过去检查了一下send_n函数的重载函数,发现了问题。ACEsend_n有两个重载函数,

  /// Try to send exactly @a len bytes from @a buf to the connection socket.

  ssize_t send_n (const void *buf,

                  size_t len,

                  int flags,

                  const ACE_Time_Value *timeout = 0,

                  size_t *bytes_transferred = 0) const;

 

  /// Try to send exactly @a len bytes from @a buf to the connected socket.

  ssize_t send_n (const void *buf,

                  size_t len,

                  const ACE_Time_Value *timeout = 0,

                  size_t *bytes_transferred = 0) const;

对于ret = client_stream.send_n((void *)petappframe,len,NULL); 这个调用,上面两个函数都可以匹配,所以对编译器要么无法正确编译,要么要选择一个。(其实无法编译应该更好)。原来的开放者希望匹配的函数应该是ssize_t send_n (const void *buf,size_t len, const ACE_Time_Value *timeout = 0,size_t *bytes_transferred = 0) const。但是由于使用了NULL,这个可爱的参数,(#define NULL    0),第3个参数被当作了int参数。实际匹配的函数成了ssize_t send_n (const void *buf,size_t len,int flags, const ACE_Time_Value *timeout = 0,size_t *bytes_transferred = 0) const;

这个代码应该改为。明确这个指针的类型,避免编译错误。

ACE_Time_Value *time_wait = NULL;

ret = client_stream.send_n((void *)petappframe,len,time_wait);

Effect C++里面应该有一节讲过这个问题。

 

问题教训和总结:

注意参数的型别和函数参数的匹配,

对于重载函数的参数要精心设计,避免出现这样参数,ACE这两个函数的设计就会导致这个问题,所以它的参数设计其实是失败。

对于NULL是什么要有清楚人数,NULLC++中往往就是被定义为0,而不是((void *)0),所以格外要当心。

9.2          雪崩效应造成处理阻塞

昨天发现ogre4a(一个比较适应通用的通讯协议的服务器,依靠PIPE和业务进程通讯)发送数据感觉速度慢,(就像有阻塞),服务器重启动一段时间后,就会出现后端服务器进城将发送管道写满的情况。前端的用户请求无法正常处理。

业务服务器的日志如下,每次处理都是管道阻塞。

Apr 23 19:00:42.771 2008@LM_ERROR@SEND_PIPE Pipe is full or data small?,Some data can't put to pipe. Please increase and check.

Apr 23 19:00:42.771 2008@LM_INFO@tmplen:1234 deque_begin=2707885,deque_end=2813817

Apr 23 19:00:42.771 2008@LM_DEBUG@put frame to pipe.len:1437 i:0

Apr 23 19:00:42.771 2008@LM_INFO@Before push_end node->sizeofnode:1437 deque_begin=2521517,deque_end=2521457

Apr 23 19:00:42.771 2008@LM_ERROR@SEND_PIPE Pipe is full or data small?,Some data can't put to pipe. Please increase and check.

Apr 23 19:00:42.771 2008@LM_INFO@tmplen:1429 deque_begin=2709119,deque_end=2813817

OGRE4A的日志来看。

Apr 23 19:32:44.406 2008@LM_INFO@tmplen:1437 deque_begin=3063266,deque_end=3058935

Apr 23 19:32:44.406 2008@LM_INFO@Can't find svchanle info. svrinfo IP|Port:[58.44.83.95|24812] .

Apr 23 19:32:44.406 2008@LM_INFO@Has 767 peer want to close. Please wait. ACE that is accursed.

从日志和配置文件,代码定位看到情况如下:

1.发送PIPE的管道不长,只有5M。但是这个问题应该不足以引发问题,只是错误的一个诱因。

2.INFOSVRD发送的数据比较长,一个用户就有1.6K左右。结合前面的问题,目前的管道只能处理大约3000个请求。但这还是一个诱因。

3.根据日志观察,每次OGRE都只从管道取出一个数据发送,但是却找不到发送目标对应句柄,由于是WEB业务,这个服务器的客户端可能经常断开。

5.检查OGRE的代码,在发送数据时如果出现错误(包括找不到对端)就退出从PIPE取数据的循环。如果正常,这个循环会一次取多达1024个发送数据。而错误情况,只处理一个数据。

6.每次处理的时间片间隔有点长,达到0.05s

 

由于上面这些原因和错误,一个雪崩效果就造成了。

由于客户端可能经常吊线,结果造成每次从PIPE只能取一个数据出来,所以找不到原来对应的端口。然后处理退出循环,继续等,然后由于等待时间很长,更多的端口肯定断掉了,结果只有恶性循环了。所以数据一直发送不出去。

 

修正问题

核心问题应该还是处理循环的问题,将代码修改为出现错误时,继续处理,测试,客户端得到正常请求。其他几个问题调整后修正。

9.3          dlsvrd日志时间出错

现象:

由于anna MM反映无法更新pet_def.db3文件,于是到dlsvrd上跟了一把日志。发现输出的日志时间不太对,也没有太在意,以为可能是谁把dlsvrd停了,ps一把,发现dlsvrd进程还在,而且是几天以前了,感觉有点奇怪,于是重启了dlsvrd,发现时间又正常了,于是让anna再测一次,猛然发现正确的时间突然又不对了,而且正好和现在的时间相差8小时,日志时间从0948倒回到0148Date发现,系统时钟仍然是0948,有意思。初步认为是程序中的什么地方将时区进行了修改。

定位:

根据日志打出的上下文去查代码,发现在此期间只有format_gmt_time一个函数进行了实际的操作,函数代码如下:

size_t DlsvrTask::format_gmt_time(time_t timestamp, char *buff, size_t buflen)

{

       struct tm tm;

        gmtime_r(&timestamp, &tm);

        return strftime(buff, buflen, "%a, %d %b %Y %X GMT", &tm);

}

Gmtime_r:将timestamp解析成GMT然后将结果保存到tm中,线程安全(dlsvrd采用了多线程)。

Strftime:将tm已指定的格式和长度输入到buff中。

理论上来说,这两个函数都没有可能去修改时区,由于该函数位于子线程中,所以sail认为可能和多线程有关系,于是将这段代码单独移到主循环中(线程初始化之后),发现仍然会出现这个问题。同时分别将这两个函数进行屏蔽,发现当屏蔽strftime时不会出现此现象,断定是由于strftime造成的时区变化。

 

但是sail单独写了一段测试代码发现,无论怎么调strftimegmtime_r,都不会产生时区变化,于是又怀疑是多线程的原因,将gmtime_rstrftime移到线程初始化之前,发现问题依旧,无语。但无意间将这两个函数移到InitInstance的最开始,发现没有出现时区变化的问题,神了。那么从InitInstance的最开始到线程初始化之间的代码和strftime共同作用的结果,且存在顺序问题。

 

无奈,只好老老实实、一个一个比较测试程序和InitInstance的最开始到线程初始化之间代码的差异。

 

发现将strftimegmtime_r移到下面一段代码之前和之后,输出结果明显不同,同时,执行和不执行strftime,也会影响时区(也难怪sailwindows在调了半天dlsvrd也没有重现这个问题)

#ifndef WIN32

       //chroot

        ret = chroot(InstOfDlsvrSvrConfig::instance()->root_path_.c_str());

        if (ret != 0)

        {

           ACE_DEBUG((LM_ERROR,"chroot failed: %u | %s/n", ACE_OS::last_error(), strerror(ACE_OS::last_error())));

            return ret;

        }

#endif

于是自己写了一段测试代码(见附件),发现是由chrootstrftime一起调用引起的问题。

 

原因:

strftime在《The Single UNIX ® Specification, Version 2 Copyright © 1997 The Open Group》有如下一段解释:

Local timezone information is used as though strftime() called tzset().

strftime会通过tzset来获取时区的信息。

 

man tzset中有这样一段解释:

If the TZ variable does not appear in the environment, the tzname variable is initialized with the best approximation  of local  wall  clock  time, as specified by the tzfile(5)-format file localtime found in the system timezone directory (see below).  (One also often sees /etc/localtime used here, a symlink to the right file in the system timezone directory.)

简单来说,就是tzset是通过TZ环境变量或是/etc/localtime来设置timezone,而我们目前没有设置TZ变量,所以实际是通过/etc/localtime来设置的。由于我们没有在InstOfDlsvrSvrConfig::instance()->root_path_下设置/etc目录,所以在chroot后在调用strftimetzset会找不到localtime,因此将timezone设置成0.这样,日志的时间就从原来的CST变成了GMT

 

修改

方案一:简单的方法是在InstOfDlsvrSvrConfig::instance()->root_path_cp一个/etc/localtime,但是由于这个变量是从配置文件里读的,那么改一次配置需要cp一次,麻烦。

方案二:由于dlsvrd已经对字符..进行了屏蔽,那么其实chroot也就不那么必要了,初步考虑是将chroot去掉。

 

思考

Q:为什么strftime会调用tzset

A:因为strftime有一个参数%Z,用于格式化时区,而其无法通过tm参数来获得时区的信息,所以必须通过tzset来获得timezone,这个是可以理解。但是我们在程序中其实并没有用到%Z这个参数,     那么理论上来说,不应该去调tzset,这个有点想不通。

 

Q:linux下的3个时间文件的作用

A/etc/sysconfig/clock - this is a short text file that defines the timezone, whether or not the hardware clock is using UTC, and an ARC option that is only relevant to DEC systems.

       /etc/localtime - this is a symbolic link to the appropriate time zone file in /usr/share/zoneinfo

       /usr/share/zoneinfo - this directory contains the time zone files that were compiled by zic. These are binary files and cannot be viewed with a text viewer. The files contain information such      as rules about DST. They allow the kernel to convert UTC UNIX time into appropriate local dates and times.

       如果只关注timezone的话,简单而言,3个文件作用如下:

/etc/sysconfig/clock 是定义timezone的文本文件

/etc/localtime       zoneinfo下一个文件的硬连接,这个文件由/etc/sysconfig/clock中的ZONE选项指定

/usr/share/zoneinfo  这个目录下存储了各个时区与UTC转换的相关规则和信息。

 

那么,为什么需要三个这样的文件呢?两个应该是完全够用的。如果没有clock,那么程序每次到localtime去查询转换规则即可,如果没有localtime,那么每次到clock去查询ZONE选项,再通过ZONE去定位/usr/share/zoneinfo目录下的相关文件,查询转换规则,这样也是可以的。由此可以感觉到/etc/sysconfig/clock/etc/localtime至少有一个是多余的,不知道为什么会设计如此。Google了很久,未果,待续……

 

10      设计规范

这里的设计规范不从宏观上怎么讲解设计,用什么设计模式解决系统的可扩展性,降低系统的耦合性,提高系统的性能等等。只从一般的开发中可具体操作的细节方面指导一些编码或者设计需要主要的事项,使得代码更清晰可读,运营维护更方便。

10.1       函数

类内的流程控制函数不能直接调用外部提供的接口,最好只调用类内部的函数,在函数内部调用外部的函数,避免流程受外部接口的影响。

 

10.2       日志

1           频繁被调用的函数不要记录日志,除非是业务要求的流水。

2           模块直接的消息接收和发送必须要有日志,以便运营维护方便定位问题。

3           如果日志量太大,以上规则要适当调整,至少是有日志开关

4           以上规则要兼顾性能上的问题,用曾总的话说“平衡是道中之道”

11      后记

11.1       作者介绍

 

11.2       参考文档

Thinking in C++

Advanced Programing in Unix Envirement

Unix Network Programing

Design Patterns

C++ Network Programing

POSA V1 V2 V3

 

11.3       致谢

诚挚的谢意献给为本文档提供过相关贡献的腾讯同事或者朋友,他们或者为文档里面提到的内容提供来源,或者为相关问题提供解决方案。具体名单如下(排名不分先后以及贡献大小,按字母顺序排序): anselyang campingliu jeffli bwarliao clarkli kylemao sandypan sailzeng

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值