关于tokyocabinet的memory db的参数说明

官方文档针对不同类型的存储一股脑地介绍了一大堆选项,至于哪个选项适用于哪种类型的存储并不清楚(可能是我没看清楚),下面只说一下影响memory db的参数选项:

参考源码:
tcadb.c :

 
  1. /* Open an abstract database. */
  2. bool tcadbopen(TCADB *adb, const char *name){
  3.     …
  4.     } else if(!tcstricmp(path, "*")){
  5.         adb->mdb = bnum > 0 ? tcmdbnew2(bnum) : tcmdbnew();
  6.         adb->capnum = capnum;
  7.         adb->capsiz = capsiz;
  8.         adb->capcnt = 0;
  9.         adb->omode = ADBOMDB;
  10.     } else if(!tcstricmp(path, "+")){
  11.         …
  12.     }
  13. }

说明:
有三个选项影响全内存型的hash存储:
bnum: 总的bucket的数量; 注意map有8个(固定的);8个map中共有bnum个bucket
capnum: 最大允许的记录数;
capsize: 最大允许的key-value使用的存储的大小;

下面解释一下capnum与capsize的使用逻辑:
先看一下源代码吧:
tcadb.c :

 
  1. /* Store a record into an abstract database object. */
  2. bool tcadbput(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  3.     assert(adb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  4.     bool err = false;
  5.     char numbuf[TCNUMBUFSIZ];
  6.     ADBSKEL *skel;
  7.     switch(adb->omode){
  8.         case ADBOMDB:
  9.         if(adb->capnum > 0 || adb->capsiz > 0){
  10.             tcmdbput3(adb->mdb, kbuf, ksiz, vbuf, vsiz);
  11.             adb->capcnt++;
  12.             if((adb->capcnt & 0xff) == 0){ // 256的倍数时才检查,否则会比较影响性能
  13.                 if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
  14.                     tcmdbcutfront(adb->mdb, 0x100);
  15.                     if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
  16.                         tcmdbcutfront(adb->mdb, 0x200);
  17.             }      
  18.         } else {
  19.             tcmdbput(adb->mdb, kbuf, ksiz, vbuf, vsiz);
  20.         }      
  21.         break
  22.         case ADBONDB:
  23.         …    
  24.     }
  25. }

说明:
1. 如果设置了capnum或者设置了capsiz, 则使用tcmdbput3()来写入,然后判断capnum或capsiz是否超过了设置;
2. 并不是记录数超过设置立即删除,也不是超过多少删除多少; 而是记录数为0x100(即:256)的倍数时才检查,如果超过则删除,而且是删除256条记录;
3. 如果内存使用(这里说内存使用不确切,而应是key-value字节数)超过了capsiz限制,则直接删除256条记录;
4. 这里面的tcmdbput3() 和 tcmdbput() 的差别在于:
如果插入的记录是存在的,对于tcmput3()来讲,将把该条记录移动到链表的尾部,视为热数据,删除总是从链表头开始的。但是作者没有在get的时候也做这样的调整,不知道是疏忽还是另有考虑。

或许你测试发现删除的并非恰好256条记录,原因是这样的,先看源码:
tcutil.c

 
  1. void tcmdbcutfront(TCMDB *mdb, int num){
  2.     assert(mdb && num >= 0);
  3.     num = num / TCMDBMNUM + 1;
  4.     for(int i = 0; i < TCMDBMNUM; i++){
  5.         if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + i) == 0){
  6.             tcmapcutfront(mdb->maps, num);
  7.             pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
  8.         }  
  9.     }
  10. }

说明:
因为存储是分了TCMDBMNUM(8)个map来存储的,不能只删除一个map中的num个,这样太不公平了。所以平均了一下,但是可能平均的不能分完,所以每个就多删除1个; 即使这样,其实还是有问题的,因为有的map重可能不够num条记录,或者根本就没有记录;

关于从map中删除记录tcmapcutfront的说明:
tcutil.c

 
  1. /* Remove front records of a map object. */
  2. void tcmapcutfront(TCMAP *map, int num){
  3.     assert(map && num >= 0);
  4.     tcmapiterinit(map);
  5.     while(num– > 0){
  6.         int ksiz;
  7.         const char *kbuf = tcmapiternext(map, &ksiz);
  8.         if(!kbuf) break;
  9.         tcmapout(map, kbuf, ksiz);
  10.     }
  11. }

说明:
因为每个map下面的记录通过pre、next指针形成一个链表结构; 删除记录的时候也就从链表的头部开始删除了; 而且这种删除也是不会考虑到数据的冷热的,删除完全是一种先进先出的策略;
对于memcache来讲,内存不够用时,也是从一头儿开始删除,但是因为memcache在读取的时候会将数据调整到链表的另一头,所以memcache删除的总是最冷的数据。

在tc的memory db的实现中,获取数据时,完全可以把获取的数据调整到链表的尾部(在非全内存的btree的map中是这么实现的,见tcutil.c:tcmapget3()),这样剔除的时候就不至于把热数据给删除掉了

————————-
说句废话:
xmsiz选项对mdb是没有关系的。

tokyocabinet存储结构分析

mdb – memory db

mdb是cabinet的一种数据组织方式,其他还有hdb(hash)、bdb(btree)等,详见"tokyo cabinet源码分析"。

由名字可知,mdb使用纯内存(不一定,见下面),速度最快。它是后面其他较高级的数据组织方式的基础,hdb、bdb的read cache都是直接使用mdb实现。而且mdb的实现最简单,所以先从mdb分析。

先贴出mdb结构图片:

也有画做下图的:

个人觉得下图不如上图容易理解; 第一次看下图的时候,还以为hash冲突是用链表来解决的,仔细观察下图的第三对key、value,后面连接了两对key、value; 其实不是画错了,而是说明这里的结构是二叉搜索树,而非链表

Tokyocabinet的MAP存储的记录的存储结构体为:
typedef struct _TCMAPREC {               /* type of structure for an element of a map */
  int32_t ksiz;                          /* size of the region of the key */
  int32_t vsiz;                          /* size of the region of the value */
  struct _TCMAPREC *left;                /* pointer to the left child */
  struct _TCMAPREC *right;               /* pointer to the right child */
  struct _TCMAPREC *prev;                /* pointer to the previous element */
  struct _TCMAPREC *next;                /* pointer to the next element */
} TCMAPREC;

该结构体有些特殊,一般我们看到的存储的结构体是有数据指针的,但是这里只有上下左右节点的指针和key、value的大小,却没有数据指针,作何解释呢?

原来是这样的:
大概是为了少malloc一次内存,于是在添加一条记录的时候,直接把存储结构体的内存和key、value的内存一并申请了: TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
这样data的起始地址其实就是 rec + sizeof(*rec) 了。

参考资料:
http://topic.xhttp.cn/view/100/
http://hi.baidu.com/sing520/blog/item/60a29bcbaac3b0f352664ffa.html
http://hi.baidu.com/sing520/blog/item/60453c9787a5296355fb96b6.html
http://gavin.iteye.com/blog/553948

xargs 用法点滴

如果输出的参数是写在执行命令的结尾的,则:
echo f1 f2 f3| xargs -n 1 ls

如果要将文件重命名,则:
echo f1 f2 f3| xargs -i  -n 1 mv {} {}.bak
或者:
echo f1 f2 f3| xargs -ixx  -n 1 mv xx xx.bak

-i 的默认值为 {} ; 所以如果不用默认值,必须挨着写,和i之间不能有空格, 即 -ixx 不能 -i xx

======================
在所有的php文件中找某字符串theString :
find . -name "*.php" | xargs -n 1 -i grep theString {}

vim变量

vim中的变量类型和大多数高级语言的基本变量类似,大概可以分为:
1. 数值
2. 浮点数
3. 字符串
4. 函数引用变量
5. 列表
6. 字典
简单说一下函数引用变量、列表变量、字典变量。
一. 函数引用变量
函数引用变量通过function()函数得到,用在表达式里来代替函数名。例如:
:let Fn = function("MyFunc")
:echo Fn()
函数引用变量必须以大写字母、"s:"、"w:"、"t:"、或"b:"开始,引用变量不能和其他任何函数重名。这些前缀的意思和申请变量时前缀一样,分别表明其作用域(:help internal-variables):
b:   局部于当前缓冲区。  :help buffer-variable
w:   局部于当前窗口。  :help window-variable
t:   局部于当前标签页。  :help tabpage-variable
g:   全局。      :help global-variable
l:   局部于函数。    :help local-variable
s:   局部于 |:source| 的 Vim 脚本。  :help script-variable
a:   函数参数 (只限于函数内使用)。  :help function-argument
v:   Vim 预定义的全局变量。    :help vim-variable

二. 列表变量
列表变量和perl的变量很类似,比如:
:let mylist = [1, two, 3, "four"]
:let nestlist = [[11, 12], [21, 22], [31, 32]]
1. 访问列表项目时同样用索引的办法:
:let item = mylist[0];    " 得到第一个项目: 1
:let item = nestlist[0][1]  " 得到第三个项目: 3
也可以试用负数索引,它的意思就是列表倒数,比如:
:let last = mylist[-1]          " 得到最后一个项目: "four"
要避免非法索引值产生的错误,用 |get()| 函数。如果项目不存在,它返回零或者你指定的缺省值:
:echo get(mylist, idx)
:echo get(mylist, idx, "NONE")

2. 列表连接  两个列表可以用 "+" 操作符连接,比如 :let longlist = mylist + [5, 6]
3. 子列表 列表的一部分可以通过指定首末两个索引获得,方括号内以冒号分隔两者:
:let shortlist = mylist[2:-1]   " 得到列表 [3, "four"]
:let endlist = mylist[2:]       " 从项目 2 到结束: [3, "four"]
4. 列表同一 如果变量 "aa" 是列表,把它赋给另一个变量 "bb" 后,两个变量指向同一列表。因此,对列表 "aa" 的修改也同时修改了 "bb":
:let aa = [1, 2, 3]
:let bb = aa
:call add(aa, 4)
:echo bb
[1, 2, 3, 4]

|copy()| 函数可以复制列表。如上所述,用 [:] 也可。这种方式建立列表的浅备份: 改变列表中的列表项目仍然会修改复制列表的相应项目:
:let aa = [[1, ‘a’], 2, 3]
:let bb = copy(aa)
:call add(aa, 4)
:let aa[0][1] = ‘aaa’
:echo aa
[[1, aaa], 2, 3, 4]  
:echo bb
[[1, aaa], 2, 3]

可用操作符 "is" 检查两个变量是否指向同一个列表。"isnot" 刚好相反。与此对照,"==" 比较两个列表的值是否相同。
:let alist = [1, 2, 3]
:let blist = [1, 2, 3]
:echo alist is blist
0
:echo alist == blist
1
比较列表时 注意: 如果长度相同,所有项目用 "==" 的比较的结果也相同,两个列表就认为相同。有一个例外: 数值和字符串总被认为不相同。这里不进行自动类型转换,而在变量间直接用 "==" 却不是如此。例如:
echo 4 == "4"
1
echo [4] == ["4"]
0

5. 列表解包 要给列表项目解包,即把它们分别存入单独的变量,用方括号把变量括起来,如同把它们当作列表项目:
:let [var1, var2] = mylist
6. 列表修改 要修改列表的指定项目,用:let命令:
:let list[4] = "four"
:let listlist[0][3] = item
VIM内建了大量操作列表的函数,可以查阅:help function-list,找到针对列表操作的函数群,查看其用途。利用这些函数就可以修改列表。

7. For 循环 就跟perl的for循环遍历列表数据类似:
:for item in mylist
:   call Doit(item)
:endfor
就像 |:let| 命令,|:for| 也可以接受变量的列表。这需要参数是列表的列表。
:for [lnum, col] in [[1, 3], [2, 8], [3, 0]]
:   call Doit(lnum, col)
:endfor
三. 字典变量
字典是关联数组: 每个项目有一个键和一个值。这和perl的关联数组(%)类似,用键可以定位项目,而项目的存储不能确定任何特定顺序。
1. 字典建立
字典通过花括号里逗号分隔的项目列表建立。每个项目包含以冒号分隔的键和值(perl中用"=>"分隔)。一个键只能出现一次。例如:
:let mydict = {1: ‘one’, 2: ‘two’, 3: ‘three’}
键必须是字符串。用数值也可以,但它总被自动转换为字符串。所以字符串 ‘4’ 和数值4 总会找到相同的项目。注意 字符串 ’04’ 和数值 04 是不一样的,因为后者被转换成字符串 ‘4’。
2. 访问项目
常见的访问项目的方式是把键放入方括号:
:let val = mydict["one"]
:let mydict["four"] = 4
用这种方式可以给已存在的字典增加新项目,这和列表不同。

如果键只包含字母、数字和下划线,可以使用如下形式 |expr-entry|:  
:let val = mydict.one
:let mydict.four = 4

因为项目可以是包括列表和字典的任何类型,你可以反复使用索引和键进行访问:
:echo dict.key[idx].key
3. 字典到列表的转换
你可以循环遍历字典的所有项目。为此,你需要把字典转为列表,然后把它传递给:for
通常,你期望遍历所有的键,用 |keys()| 函数就可以了:
:for key in keys(mydict)
:   echo key . ‘: ‘ . mydict[key]
:endfor
要遍历所有的值,用 |values()| 函数:  >
:for v in values(mydict)
:   echo "value: " . v
:endfor

如果你想同时得到键和值,用 |items()| 函数。它返回一个列表,其中每个项目是两个项目的列表: 键和值:  
:for [key, value] in items(mydict)
:   echo key . ‘: ‘ . value
:endfor
4. 字典同一
就像列表那样,你需要用 |copy()| 和 |deepcopy()| 来构造字典的备份。否则,赋值产生的结果会引用同一个字典:  
:let onedict = {‘a’: 1, ‘b’: 2}
:let adict = onedict
:let adict[‘a’] = 11
:echo onedict[‘a’]
11
5. 字典修改
要修改字典已经存在的项目或者增加新的项目,用:let:  
:let dict[4] = "four"
:let dict[‘one’] = item
从字典里删除项目可以通过 |remove()| 或 |:unlet| 完成。从 dict 里删除键 "aaa" 的项目有三种方法:
:let i = remove(dict, ‘aaa’)
:unlet dict.aaa
:unlet dict[‘aaa’]
6. 字典函数

关于Apache调优点滴

Apache 通过管道记日志对性能的影响:

  1. 如果访问量比较小,则,使用管道可能比只直接写文件要快那么一点点(不会太明显),因为写管道毕竟是些内存(但是操作系统层面对写文件也是有优化的)
  2. 如果访问量很大,多个进程并发写一个管道,而只有一个进程在读管道,写日志文件,则,管道很容易写慢导致处理进程阻塞,这时,管道产生的瓶颈是很明显的,可能会导致Apache的处理性能减少一个数量级;而且你看到的将是cpu idle很高,iowait很低,但是loadaverage却很高

进程的一生

随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。

然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。

人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个"}",从容地离我们而去;也可以是自杀,自杀有2种方 式,一种是调用exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下来;它还甚至能可被谋杀, 被其它进程通过另外一些方式结束他的生命。

进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。

这就是进程完整的一生。

给Apache做压力测试时遇到的问题

最近,在Linux上对Apache-2.1.16做了一下压力测试;只访问一个简单的hello world静态文件,做了多次,每秒处理请求次数才700左右;
一定是哪里出现了问题,因为:
   1. 虽然load很高,但是cpu和io都很低,这种现象就比较奇怪
   2. client端的负载很低
   3. 曾经有过 2000/s的测试,而且当时的机器配置比现在的要差的多

当我使用同样的方法对nginx做测试的时候,nginx可以达到 2.2w/s 。

无奈之下,只好又把strace请出来了,发现写日志的地方很慢。
于是怀疑是磁盘问题; 使用hdparm测试了一下磁盘,没有问题; 又用 time dd … 测试了一下,写的速度也 700+MB/s ; 回头想想,确认了一下nginx也是记日志的,而且都是同一块磁盘,大概不是磁盘问题了。

修改Apache 配置文件,把记日志的配置注释掉, 重新测试,发现可以达到 7k+/s 了;仔细观察Apache的配置,是这样写的:
TransferLog "|/data1/apache2/bin/rotatelogs /path/to/logfile"

于是,我想怀疑rotatelogs 这个程序了,但是似乎也没有道理; 那么就只剩下那个管道了,恩, 大概问题就出现在管道上了。

因为管道的大小为4k, 多进程写管道,单进程读管道,也难免会出现阻塞的情况;另外,load高的时候,io是很低的,所以说磁盘不是瓶颈。 管道是瓶颈

———-
如何利用管道来模拟一个cpu、io都很低,但是load很高的实例呢?