2016年7月12日星期二

redis内存容量估算

最近刚接手一个redis服务,由于是内部使用,所以在redis前端封装了一层代理,用于内部协议和redis协议的转化,并不复杂。但是使用方刚使用两天,就发现内存爆量了。具体现象是redis返回的错误:OOM command not allowed when used memory > 'maxmemory',使用方给出的请求量是:5000万key,每个key40字节(string类型),每个value8字节,这样算下来只要2.4G内存啊,怎么一下内存就爆了。
我也是刚接触redis,只好研究了一番,还好网上有一篇文章,详细说明了redis的容量估算问题,简单的说就是redis占用的内存除了业务数据之外,还有redis自己的数据结构也会占用一部分内存,所以实际占用内存会大于业务数据的。链接在此:http://www.searchdatabase.com.cn/showcontent_55189.htm
根据文章提供的公式,结合使用方提供的数据,估算的容量如下:
1.首先是计算公式:
string类型的内存大小 = 键值个数 * (dictEntry大小 + redisObject大小 + 包含key的sds大小 + 包含value的sds大小) + bucket个数 * 4

2.根据使用方提供的数据,占用的redis内存容量如下:
dictEntry大小: 16 字节
redisObject大小:16字节
key:40字节, sds对象:9 字节。总共是49字节,按jemalloc分配机制会占用到64字节。
value:8字节,sds对象:9字节,总共是17字节,按jemalloc分配机制会占用到32字节。
以上的内存占用量为:
 (16 + 16 + 64 + 32)* 5000*1000*10 = 6.4G

bucket个数大概为6000万(5000万向上取整到2的n次方,即2的26次方,6000多万)
bucket大小:6000万*4 = 240M左右。

所以总容量为6.4G左右,而redis的配置里面maxmemeroy只配置了4G,难怪要报错了。修改配置后,运行正常了。
从redis里面也可以看到峰值使用内存为6.44G, 和计算出来的容量是吻合的.











2016年6月29日星期三

PHP SSH2 的各种问题

最近两天,不断碰到php ssh2的问题,总结下来有几个:
1.PHP Warning: ssh2_scp_send(): Failed copying file
 此问题出现在传大文件的时候
2.PHP Warning: ssh2_exec(): Unable to request a channel from remote host
 连接某台机器的时候,执行shell命令的时候。其他机器没问题。

对于第一个问题,测试机上没问题(php ssh2 扩展版本为0.11.3),生产环境有问题(php ssh2 版本为 0.11.0),所以更新生产环境为0.11.3。但是测试还是有问题,百思不得其解。
后面再更新为0.13 stabel版本,ssh2_scp_send 不再报错了,但是传文件后发现文件md5结果不一样!又再google一番,在ssh2_scp_send之后增加一句:ssh2_exec($con, 'exit');这样才ok了。
但是在测试的时候,发现另外一个新的问题:
php: symbol lookup error: /usr/local/php/lib/php/extensions/no-debug-zts-20100525/ssh2.so: undefined symbol: libssh2_session_set_timeout
然后 nm ssh2.so |grep libssh2_session_set_timeout,发现这个函数是引用的外部实现。那这个函数在哪个so里面呢?找了一下,在libssh2.so.1里面,但是为啥没找到呢。
和运维同学想了半天,最后发现/etc/ld.so.conf.d/libssh2.conf 这个文件里面没有把 libssh2.so.1的路径包含进去,最后修改libssh2.conf文件,增加 /usr/local/libssh2/lib,这下ok了。

总结:碰到过两次php扩展版本的问题,之前碰到过memcache的问题,也是用的beta版本导致很多问题。这次也是一样。所以以后任何的扩展,都要仔细检查是否是stable版本的。

2016年3月31日星期四

php foreach reference 导致的问题

今天碰到一个奇怪的问题,在一个php脚本中写了一个foreache循环,遍历一个数组,同时修改数组里面的元素。大致就是这样:
foreach ($a as $key => &$value) {
   $value['ttt'] = '123';
}
forearch ($a as $key => $value) {

}
结果第二次循环的时候,数组最后一行数据居然没有了。
第一次循环完dump出来的数组又是对的,但是其中一行有点奇怪,大概是这样:
 &array(1) {
    [0]=>
    &array(20) {
      ["sampleID"]=>'aaa'
    }
 }
和这个有没有关系呢?搜了一圈,找到原因了。
这篇解释得很清楚:
php foreach reference
解决方法就是在使用引用完后要unset一下。php manual也有说明
php:foreach

2016年2月23日星期二

“Illegal double … value found during parsing” 错误

刚刚碰到的一个问题,数据提交时报"Illegal double '20160221E0259700' value found during parsing"错误。但是程序运行了一段时间,怎么会报错呢?查了一下,带"E"的串会被PHP认为是double变量,而要更新的这个字段是一个varchar类型,所以导致错误。
之前为啥没报错呢,因为代码太搓:update strid=2016123456,这样就没报错,实际上应该是update strrid='201612345'。坑啊!

2016年1月15日星期五

GCC 4.4.6 const char* to char* compiler error

今天在编译一个同事的代码发现一个小问题,就是报编译错误: error: invalid conversion from ‘const char*’ to ‘char*’,报错出在strstr这个函数,但是他原来编译又是ok的。猜测是GCC版本不一致导致的问题,一看果然是,他用的gcc 4.1.2,我用的gcc 4.4.6。但是为啥gcc 4.4.6编译会报错呢,查了一下gcc 手册:

Strict null-terminated sequence utilities

Some of the standard C++ library include files have been edited to use replacement overloads for some common C library functions (if available), with the goal of improving const-correctness: functions passed a const char* return const char*.
The table below shows the functions and files that have been changed.
HeaderFunctions
<cstring>strchrstrpbrkstrrchrstrstrmemchr
<cwchar>wcschr wcspbrkwcsrchrwcsstrwmemchr
An example.
#include <cstring>

const char* str1;
char* str2 = strchr(str1, 'a');
Gives the following compiler error:
error: invalid conversion from ‘const char*’ to ‘char*’
Fixing this is easy, as demonstrated below.
#include <cstring>

const char* str1;
const char* str2 = strchr(str1, 'a');
More information about the C++ standard requirements can be found in chapter 21, section "Null-terminated sequence utilities."

链接:https://gcc.gnu.org/gcc-4.4/porting_to.html
=====

原来是C++的标准库头文件有变化,目标是“improving const-correctness”,其中就有strstr这个函数。好吧,在原来的代码修改了加了强制转化就ok了。

2015年6月9日星期二

linux OOM

最近碰到的一个问题:进程A先启动,运行正常,然后进程B启动,系统一下卡住了,过了一会,进程B启动完成,进行A却退出了。对这个现象百思不得其解啊。gdb调了一会,发现进程B在分配内存的时候(要分配的内存很大),会导致系统卡住,然后进程A退出。网上搜了一下,发现是linux OOM机制导致的这种问题。

简而言之:
1.当一个进程向linux请求内存分配时(不论是new或者malloc),linux通常会直接返回成功,实际上并未分配内存。
2.实际分配内存的时候,linux若发现系统内存不够,则可能执行OOM(根据系统配置决定)把其他进程kill掉(发送SIGTERM信号)
3.程序中对new或者malloc的分配是否成功的判断有时候是没用的。只有通过监控系统内存来判断程序的内存的使用是否有问题。

内核关于OOM的文章:
https://www.kernel.org/doc/gorman/html/understand/understand016.html

另外以下这篇文章对OOM分析比较到位:
http://blog.csdn.net/guomsh/article/details/6536915

Linux Out-of-Memory(OOM) Killer


    Linux有一个特性:OOM Killer,一个保护机制,用于避免在内存不足的时候不至于出现严重问题,把一些无关的进程优先杀掉,即在内存严重不足时,系统为了继续运转,内核会挑选一个进程,将其杀掉,以释放内存,缓解内存不足情况,不过这种保护是有限的,不能完全的保护进程的运行。
    在很多情况下,经常会看到还有剩余内存时,oom-killer依旧把进程杀死了,现象是在/var/log/messages日志文件中有如下信息:
    Out of Memory: Killed process [PID] [process name].
    该问题是low memory耗尽,因为内核使用low memory来跟踪所有的内存分配。
    当low memory耗尽,不管high memory剩多少,oom-killer都会杀死进程,以保持系统的正常运行。
    在32位CPU下寻址范围是有限的,Linux内核定义了下面三个区域:
   # DMA: 0x00000000 -  0x00999999 (0 - 16 MB) 

   # LowMem: 0x01000000 - 0x037999999 (16 - 896 MB) - size: 880MB

   # HighMem: 0x038000000 - <硬件特定> 
    LowMem区(也叫NORMAL ZONE)共880MB,并且是固定不能变的(除非使用hugemem内核),对于高负荷的系统,可能因为LowMem使用不好而触发了OOM Killer机制。因为内存分配是一个连续的区域,在此时,如果LowMem里存在很多碎片或者LowFree太少,此时无法分配到一块连续的内存区域,就触发了OOM Killer。
    查看当前LowFree值:
    cat /proc/meminfo | grep LowFree
    查看LowMem内存碎片:
    cat /proc/buddyinfo
    上面这命令需要在2.6内核才有效。
    有如下方法可以解决该问题:
    1、升级到64位系统,这是最好的方法,因为此时所有的内存都属low memory,如此时提示out of memory,则真的是low memory耗尽,真的OOM了。
    2、如必须使用32位系统,那么可以使用hugemem内核,此时内核会以不同的方式分割low/high memory,而大多数情况下会提供足够多的low memory至high memory的映射,此时很简单的一个修复方法是可以安装hugemem内核包,然后重启。
    3、如果hugemem内核也用不了,那么我们可以尝试将/proc/sys/vm/lower_zone_protection的值设为250或更大,可使用如下命令查看和设置该值:
       cat /proc/sys/vm/lower_zone_protection
       echo 250 > /proc/sys/vm/lower_zone_protection
       或者可以修改/etc/sysctl.conf文件,以便重启后生效,添加的内容如下:
       vm.lower_zone_protection = 250
    4、实在没办法,那么我们把oom-killer关掉,不过该选项可能导致系统挂起,故要看实际情况使用。
       查看当前的oom-killer的状态:cat /proc/sys/vm/oom-kill
       关闭/打开oom-killer:
       echo "0" > /proc/sys/vm/oom-kill
       echo "1" > /proc/sys/vm/oom-kill
       或者直接加到/etc/sysctl.conf文件,内容如下:
       vm.oom-kill = 0
       此时,当进程被oom-killer尝试杀死而没有成功时,会把相关信息记录到/var/log/messages文件中,信息如下:
       "Would have oom-killed but /proc/sys/vm/oom-kill is disabled"
    5、或者不把oom-killer关掉,只针对单一进程处理,把某个进程保护起来,此时,我们可以使用如下命令:
       echo -17 > /proc/[pid]/oom_adj
       /proc/[pid]/oom_adj中oom_adj的取值范围是-17~15,当该值为-17时,系统将不会杀死指定pid的进程,而-16~15则会使得进程的/proc/[pid]/oom_adj值呈指数(K*2^n)形式递增,即它们被杀掉的可能性呈指数递增。针对init(进程号为1)这个进程,无论该值设为多少都不会被杀。
    6、参考资料
       
以上是从网络上查到,结合自己的问题进行下补充:
一开始由于系统配置是2G,而且没有交换分区,所以每天导致out of memory,后来增加了物理内存,并做了交换分区,情况有所改善,但是运行2-3天后还是会出现out of memory的情况,后来分析日志文件messages发现粗体部分,分析是low memory不足导致,后来自己试验用sync ;echo 3 >> /proc/sys/vm/drop_caches可以将内存释放出来,查看/proc/buddyinfo内存释放出来。
Jun 10 13:33:11 xx user.warn kernel: DMA free:3548kB min:68kB low:84kB high:100kB active:0kB inactive:0kB present:16384kB pages_scanned:0 all_unreclaimable? yes
Jun 10 13:33:11 xx user.warn kernel: lowmem_reserve[]: 0 0 880 4080
Jun 10 13:33:11 xx user.warn kernel: DMA32 free:0kB min:0kB low:0kB high:0kB active:0kB inactive:0kB present:0kB pages_scanned:0 all_unreclaimable? no
Jun 10 13:33:11 xx user.warn kernel: lowmem_reserve[]: 0 0 880 4080
Jun 10 13:33:11 xx user.warn kernel: Normal free:1340kB min:3756kB low:4692kB high:5632kB active:0kB inactive:28kB present:901120kB pages_scanned:2456536 all_unreclaimable? yes
Jun 10 13:33:11 xx user.warn kernel: lowmem_reserve[]: 0 0 0 25600
Jun 10 13:33:11 xx user.warn kernel: HighMem free:1615600kB min:512kB low:3928kB high:7344kB active:274112kB inactive:24928kB present:3276800kB pages_scanned:0 all_unreclaimable? no
Jun 10 13:33:11 xx user.warn kernel: lowmem_reserve[]: 0 0 0 0
Jun 10 13:33:11 xx user.warn kernel: DMA: 1*4kB 1*8kB 1*16kB 0*32kB 1*64kB 1*128kB 1*256kB 0*512kB 1*1024kB 1*2048kB 0*4096kB = 3548kB
Jun 10 13:33:11 xx user.warn kernel: DMA32: empty
Jun 10 13:33:11 xx user.warn kernel: Normal: 1*4kB 1*8kB 1*16kB 1*32kB 0*64kB 0*128kB 1*256kB 0*512kB 1*1024kB 0*2048kB 0*4096kB = 1340kB
Jun 10 13:33:11 CHINASOFT user.warn kernel: HighMem: 8192*4kB 7506*8kB 5166*16kB 2932*32kB 1528*64kB 688*128kB 253*256kB 86*512kB 67*1024kB 12*2048kB 234*4096kB = 1615600kB

以上是自己遇到的问题,处理肯定有不对的地方,如果您发现请指正!补充:内核是2.26.16

2014年12月24日星期三

解决Samba访问慢的问题

之前的一台开发机samba访问一直很正常,但是突然有天就变得很慢,现象就是保存一个文件时可能要得10多秒,这实在是受不了。折腾了两天,网上的方法都试了一下,完全没用。今天偶尔发现 /etc/resolv.conf 里面的一台nameserver有点慢,ping都要40ms,然后换了一台快的,ping基本上在1ms以内,然后samba的速度一下就快起来了,秒存文件,爽啊。