结合逍遥问问讲解如何利用LoadRunner进行性能测试

作者 : admin 于 2011年01月27日, 10:33:44
2011
01-27

最近群里聊到测试,我也拿公司测试部门的一个文档分享。我们测试组4个MM,负责平时的功能测试,性能测试和安全测试。既有很萌的测试主管,又有彪悍的千杯不醉,还有海吃不胖的瘦弱女生,包括由行政转为技术的全才。

PPT主要得靠演讲人的口述才能理解,这里PPT写的也比较详尽,大家可以看出一些功能和流程上的东西。

PPT部分内容删除,包括产品业务流程图,设计敏感账号和我们一些IP信息,不过不影响阅读。

附加一句:得用WPS打开。没装WPS的可以来这里下载 http://www.wps.cn/

顺便宣传我们的产品 逍遥问问

PPT下载

利用udf扩展mysql增加json_object函数

作者 : admin 于 2011年01月23日, 21:47:57
2011
01-23

其实已经有一个成形的mysql json编码的模块 http://www.mysqludf.org/lib_mysqludf_json/index.php

不过我们在使用的过程中,发现这个函数有版本兼容的问题,后看代码,发现它的json加密规则比较麻烦,没拆出来分析,就自己使用一个json类 http://sourceforge.net/projects/cjson/ 重写了一遍json_object函数。

目前在mysql-5.0.22版本上使用基本没问题,但在mysql5.5以上版本会有问题,udf接口格式好像有了变化。

mysql-5.0.22版本给出的例子是c++的代码。使用了extern语法,为了兼容gcc的语法。而我实际使用纯C环境,就抛弃了extern的语法。

udf_json源码下载

  1. file:udf_json.c
  2. #ifdef STANDARD
  3. #include <stdlib .h>
  4. #include <stdio .h>
  5. #include <string .h>
  6. #ifdef __WIN__
  7. typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */
  8. typedef __int64 longlong;
  9. #else
  10. typedef unsigned long long ulonglong;
  11. typedef long long longlong;
  12. #endif /*__WIN__*/
  13. #else
  14. #include <my_global .h>
  15. #include <my_sys .h>
  16. #include <m_string .h>
  17. #endif
  18. #include <mysql .h>
  19. #include <ctype .h>
  20. #include <float .h>
  21. #include "cJSON.h"
  22. #ifdef HAVE_DLOPEN
  23. my_bool json_object_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
  24. {
  25.   if (args->arg_count < 1)
  26.   {
  27.     strcpy(message,"Wrong arguments to metaphon;  Use the source");
  28.     return 1;
  29.   }
  30.   return 0;
  31. }
  32. void json_object_deinit(UDF_INIT *initid)
  33. {
  34. }
  35. char *json_object(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error)
  36. {
  37. cJSON *root;
  38. char *out;
  39. int i = 0;
  40.  
  41. root=cJSON_CreateObject();
  42. for(i = 0;i<args->arg_count;i++)
  43. {
  44. if(args->arg_type[i] == STRING_RESULT)
  45. {
  46.     cJSON_AddStringToObject(root, args->attributes[i], args->args[i]);
  47. }
  48. else if(args->arg_type[i] == INT_RESULT)
  49. {
  50. cJSON_AddNumberToObject(root, args->attributes[i], *((int*)args->args[i])); //函数参数为double型,这里强转int是好使的,纳闷
  51. }
  52. }
  53. out=cJSON_Print(root);
  54.     cJSON_Delete(root);
  55. *length = strlen(out);
  56. return out;
  57. }
  58. #endif /* HAVE_DLOPEN */
  59.  
  60. file:make.sh
  61. #!/bin/sh
  62. gcc -Wall -I/opt/mysql-5.0.22/include/mysql -I. -shared udf_json.c cJSON.c -o lib_mysqludf_json.so
  63. cp -f ./lib_mysqludf_json.so /usr/lib/lib_mysqludf_json.so
  64. service mysqld restart
  65. </float></ctype></mysql></m_string></my_sys></my_global></string></stdio></stdlib>

编译完成,复制so文件至公共库,然后执行以下语句:

  1. drop function json_object;
  2. create function json_object returns string soname 'lib_mysqludf_json.so';

使用 :

  1. SELECT json_object(field1,field2……) AS jsdate FROM table;

PHP调用:

  1. $db = mysql_connect("127.0.0.1:3306","root","123456");
  2. mysql_select_db("test");
  3. $query = mysql_query("SELECT json_object(id,intval,value) FROM test LIMIT 6");
  4. while($row = mysql_fetch_array($query)){
  5.     var_dump(json_decode($row[0],true));
  6. }

结果显示:

array(3) {
["id"]=>
int(2)
["intval"]=>
int(43324)
["value"]=>
string(10) “fdsfdsfdsa”
}
array(3) {
["id"]=>
int(5)
["intval"]=>
int(432432432)
["value"]=>
string(10) “fdsafdsasa”
}

mysql5.5.8安装笔记

作者 : admin 于 2011年01月19日, 11:46:42
2011
01-19

mysql5.5.8对于守旧的人绝对是一种惨无人道的蹂躏。

我开始用mysql5.5.8了,被忽悠得天花乱坠,说性能提升,说主从同步迅速……

不过我是专用udf功能的,因为我常用的mysql稳定版本5.0.22官方说有点小bug,使用udf有点问题,被迫升级了5.5.8版本。

mysql5.5.8抛弃了我们钟爱的configure,而次用了cmake。

当然你要先安装cmake http://www.cmake.org/files/v2.8/cmake-2.8.3.tar.gz

cmake安装简单,tar后make && make install 搞定。

mysql5.5.8到官网下载 http://dev.mysql.com/downloads/mirror.php?id=399302#mirrors

tar出来,就找不到configure了,不过官方提供了高仿configure的转换脚本,不过神马都是浮云,自己折腾脚本吧

  1. cmake -DCMAKE_INSTALL_PREFIX=/opt/mysql-5.5.8 \
  2.       -DMYSQL_DATADIR=/opt/mysql-5.5.8/data \
  3.       -DSYSCONFDIR=/opt/mysql-5.5.8/etc \
  4.       -DINSTALL_PLUGINDIR=dir_name=/opt/mysql-5.5.8/plugin \
  5.       -DWITH_INNOBASE_STORAGE_ENGINE=1 \
  6.       -DWITH_ARCHIVE_STORAGE_ENGINE=1 \
  7.       -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
  8.       -DWITH_FEDERATED_STORAGE_ENGINE=1 \
  9.       -DWITH_PARTITION_STORAGE_ENGINE=1 \
  10.       -DMYSQL_UNIX_ADDR=/tmp/mysqld.sock \
  11.       -DMYSQL_TCP_PORT=3306 \
  12.       -DENABLED_LOCAL_INFILE=1 \
  13.       -DEXTRA_CHARSETS=all \
  14.       -DDEFAULT_CHARSET=utf8 \
  15.       -DDEFAULT_COLLATION=utf8-general_ci \
  16.       -DMYSQL_USER=mysql

然后make && make install就行。

剩下的事情就跟老版本的一样了,不过我编译的时候没有成功安装 mysql_install_db 脚本,自己写了一个,也算成功完成任务了。

discuz论坛大表优化

作者 : admin 于 2011年01月11日, 23:15:17
2011
01-11

通过观察,大部分的dz论坛在数据量发展到一定程度,在线人数逐渐提高后,首先锁表的是sessions,这个表我已经写过多种优化的方式,不外乎寻找一些比mysql负载性能好的程序来代替这部分工作。但时间长了,随着在线人数继续增加,那个附加的程序也会面临瓶颈。提高硬件性能和软件性能固然能提高负载,但一旦到瓶颈,必须想其他的方案。

硬件和简约的程序能提高性能,在大数据量下,算法的优势就能体现出来了。

顺便提一下主从:很多人认为主从可以解决问题,其实未必。设想一主多从结构,假如主库写压力很大,那同样压力会同步到从库,会造成N个从库压力同时很大。事实上从库压力会小于主库,因为主库是多进程写,而从库是单进程写, 但总的来说,执行的语句不会少。所以,主从这种结构在一定情况下也就失去了优势。(在硬件资源充裕,压力不是很大的环境下,这种问题发生较少,在硬件条件比较差的一些环境下,这种瓶颈很容易表现出来)

这里拿discuz7的posts表举例,摆脱主从结构,硬件比较差,表很大,10G以上。

一个负载大的dz论坛,在线人数多,又比较活跃,那posts表的压力肯定不会小。在一个回复比较频繁,存储引擎使用myisam的posts表,锁表是经常发生的,我所遇见的问题发生的环境为:数据库单点,无主从,io压力中等。posts表频繁锁表,而造成查询排队,查询速度骤然下降。

在不提升硬件的情况下,要想提速,显然是比较困难的,大量的文本数据装入memcached显然也不合适。所以,这个问题我用优化数据存储的角度对数据表进行了改造。

TIPS:同样100M带宽的集线器和交换机,交换机的吞吐性能远远高于集线器,原因在于:交换机建立专有通道,避免了冲突。

而在mysql中,锁表可以形象描述为冲突了,读写冲突了。但如果我们分表,把读写分散,也许会好点。

分表规则:按照tid进行hash,分散到16个表中。

假设,一读一写,两个操作,同时进行,那么他们撞在一起的几率就是1。如果分为两个表,那么他们撞在一起的几率就是1/2 = 0.5,用一个函数来表示,就是F(x)=1/x ,显然,分的表越多,冲突的机会越小,锁表的几率就越少。即时锁表,影响的也只是1/x的数据,不会对所有的用户造成影响。

数据模型解释:如果是绝对同步发生,几率应该是 F(x)=1/((x-1)*x),但在计算机里,无论两个操作时间间隔多小,在cpu时间片上都是顺序执行,因为,函数我认定为:F(x)=1/x。

以上用数学的方式解释了算法优化对性能的提升。实际上,通过对逍遥论坛的用户行为统计:posts表95%以上的操作都是在读写,搜索和管理占小部分。

补充一种分表算法:在discuzX里,后台启用了分表,我没有细看,大概是把表按照时间段或者其他条件分开。我猜测,作者本意是拆分老数据,主表只留最新数据和一些命中高的数据。这种方式可以起到一定效果,但根据统计,大部分用户习惯浏览回复最新帖子,因此,大部分的读写还是定位到了一张表,也就是没有彻底解决读写冲突的问题。
还有一个朋友使用的是顺序分表,500w个pid一张表,但这个方法同上个方法,没解决冲突问题。
所以,在他的基础上,我考虑出按照tid进行hash分表的方案。

说到这里,分表又给我们带来了麻烦,有些查询并不能用tid主键进行定位,这里我用了mysql合并表,这个合并表可以合并16个分表,成为一个大表进行查询,而表名依然用原始的表明,这样,dz中原来的功能就不受影响了。

此方案已经实现,我用的新老数据+分别hash的方式,即32张表存储posts数据的方式。但未做压力测试。最近努力学习loadrunner使用,这个压测马上就可以进行了。

小货倒库绝版秘籍

作者 : admin 于 2011年01月10日, 00:07:31
2011
01-10

AB库,车子默认在B库内。

1、B库到考试初试地点:前进->右杆进入右边第一个盲区(两块玻璃中间的盲区),右打轮一圈半,待车头变正,停止。
2、开始考试,帖库:看车后窗户,缓行,中杆(AB库中间的杆)进入右后盲区(驾驶室右后角的盲区),右打轮两圈打满,倒行,后窗户右侧立柱跟中杆重合后,左打轮一圈,倒行,中杆到右后盲区跟右侧立柱中间的位置,右打轮一圈打满,看左倒车镜,出现A库左后杆后,逐渐调整车子对准A库后边左右杆中间,右打轮两圈,方向盘打正,倒行,待车子后侧将近后侧的时候,停车,帖库完成。
3、移库:右打轮两圈打满,前进,待中杆移动到将近左倒车镜左数1/3的时候,停,左打轮四圈打满,前进,待车头打正的时候停止,如果车头不到前边线,可适当前进,右打轮四圈打满,倒行,视线看准后窗户立柱左边的立柱并且瞄准车后边左侧第二颗铆钉与第一颗铆钉中间,B库右后杆移动到第二颗铆钉上,停车,左打轮四圈打满,看车头,倒车,待车头摆正,停止,右打轮两圈,方向盘打正,前进,并且适当调整车子在车库中央,然后倒车,车子后边接近后边线,停止。
4、出库:左打轮一圈,前进,从A库出去,看车子右后角,将近出线的时候,左打轮一圈半,带车头摆正后,停止。
5、倒库:A库左杆刚出左边第一个盲区(左边两块玻璃中间的盲区),停止,左打轮两圈打满,倒行,待AB库中间的杆出线在后盲区后,后盲区最下边的边与杆相切,停止,右打轮一圈,继续倒行,待中杆第二次与后盲区最下边的杆相切,左打轮一圈打满,看右倒车镜,待B库右后杆出线在右倒车镜中,看左右倒车镜后边的杆线,调整车子摆正,倒车,待车头离前边线两米左右,停车。

考试完毕。必须6分钟内完成,不得压线,不得撞杆,不得违法步骤。

http://www.zyue.com/n/BU83/na70809.html 皮卡科目二蝴蝶桩过关技巧配图详解

http://teli.blog.hexun.com/36153356_d.html 科目二考试内容及技巧皮卡怎样定点停车,上坡起步,侧方位,单边桥,过圆饼

利用loadrunner做mysql压力测试

作者 : admin 于 2011年01月06日, 23:05:07
2011
01-6

loadrunner下的mysql c api

一个很简单的模拟用户访问论坛帖子的loadrunner测试脚本

最近跟测试组学习loadrunner的使用,测试组的姑娘们习惯用界面进行操作,而习惯linux平台使用的我很多功能都使用代码来实现了。

最近用loadrunner写了一个模拟社区用户压测论坛数据库的一个脚本,进行数据库的压力测试和优化工作。

用户行为分析:

在社区中,看帖的人是发帖人的10倍以上,而看帖人大概80%以上都在看新帖,20以下的用户有挖坟行为。

因此,设定如此的比率:每11个用户,1个发帖,8个看最近30%的帖,两个看老的70%的帖。

我的论坛帖子回复表大概是:973505个帖子的回复,两千多万的回帖。

根据这些数据,配合mysql的c api,写如下脚本:

注:脚本的my_mysql_insert()函数是有问题的,多线程下有一个资源符没处理好,因为还不太了解loadrunner的线程机制,所以留下了一个bug。

在做完这个脚本后,我发现我们测试机性能都不错,很难在一个5G大小的单表上主键查询造成很大的压力,所以,计划把dz论坛架设,用php+mysql真实环境下进行压测,这样可以顺便练习http函数下的loadrunner编程。

  1. globals.h
  2. #ifndef _GLOBALS_H
  3. #define _GLOBALS_H
  4. #include "lrun.h"
  5. #include "web_api.h"
  6. #include "lrw_custom_body.h"
  7. #include "modal_dialog_callbacks.h"
  8. #define random(x) (rand()%x)
  9. #include <mysql .h>
  10. #include <mysql_com .h>
  11. #include <mysql_time .h>
  12. #include <mysql_version .h>
  13. #include <typelib .h>
  14. #include <my_list .h>
  15. #include <my_alloc .h>
  16. #endif // _GLOBALS_H
  17. </my_alloc></my_list></typelib></mysql_version></mysql_time></mysql_com></mysql>
  1. vuser_init.c
  2. MYSQL *db;
  3. MYSQL_ROW record;
  4. vuser_init()
  5. {
  6. char sql[128];
  7. MYSQL_RES *results1;
  8.     lr_load_dll("libmysql.dll");
  9.     db = mysql_init(NULL);
  10. mysql_real_connect(db, "192.168.8.32", "root", "123456", "sunboyu_test", 3311, "/tmp/mysql3311.sock", 1);
  11.     
  12. sprintf(sql, "show tables");
  13. mysql_query(db, sql);
  14.     results1 = mysql_store_result(db);
  15.     while((record = mysql_fetch_row(results1)))
  16. {
  17.     lr_log_message("table = %s", record[0]);
  18.      }
  19. mysql_free_result(results1);
  20. return 0;
  21. }
  1. vuser_end.c
  2. vuser_end()
  3. {
  4.    mysql_close(db);
  5.    mysql_server_end();
  6. return 0;
  7. }
  1. Action.c
  2. Action()
  3. {
  4. int r1 = 0;
  5. char sql[128];
  6. r1 = random(11);            //10种用户,8种正常访问,两种在挖坟
  7. lr_log_message(" %d", r1);
  8.         if(r1==11)
  9.         {
  10.             my_mysql_insert();
  11.         }
  12.         else
  13.         {
  14.             my_mysql_query( r1 );
  15.         }
  16. return 0;
  17. }
  18.  
  19. int my_mysql_query( int randid )
  20. {
  21. int r2 = 0;
  22. int j = 0;
  23. int count = 0;
  24. int page = 0;
  25. char sql[128];
  26. MYSQL_RES *results2;
  27. if(randid&lt;2)  //老数据,小部分 20%
  28. {
  29. r2 = random(700000);
  30. sprintf(sql, "SELECT COUNT(*) AS count FROM posts_jx3 WHERE tid = %d;",r2);
  31. lr_log_message("%s",sql);
  32.         mysql_query(db, sql);
  33.         results2 = mysql_store_result(db);
  34.         while((record = mysql_fetch_row(results2)))
  35. {
  36.         lr_log_message("count = %d", record[0]);
  37.         }
  38. mysql_free_result(results2);
  39. page = count/10;
  40. lr_log_message("page = %d", page);
  41. for(j=0;j< =page;j++)
  42. {
  43. sprintf(sql, "SELECT *  FROM posts_jx3 WHERE tid = %d LIMIT %d,10;",r2,j);
  44. mysql_query(db, sql);
  45. lr_log_message("%s", sql);
  46. if (j>0) {
  47.   results2 = mysql_store_result(db);
  48.                   while((record = mysql_fetch_row(results2)))
  49.           {
  50.                      lr_log_message("pid = %d", record[0]);
  51.                    }
  52.           mysql_free_result(results2);
  53. }
  54. }
  55. }
  56. else //新数据,大部分 80%
  57. {
  58.         r2 = random(273505);
  59. r2 = r2+700000;
  60. sprintf(sql, "SELECT COUNT(*) AS count FROM posts_jx3 WHERE tid = %d;",r2);
  61. lr_log_message("%s",sql);
  62.         mysql_query(db, sql);
  63.         results2 = mysql_store_result(db);
  64.         while((record = mysql_fetch_row(results2)))
  65. {
  66. lr_log_message("count = %d", record[0]);
  67.         }
  68. mysql_free_result(results2);
  69. page = count/10;
  70. lr_log_message("page = %d", page);
  71.  
  72. for(j=0;j< =page;j++)
  73. {
  74. sprintf(sql, "SELECT *  FROM posts_jx3 WHERE tid = %d LIMIT %d,10;",r2,j);
  75. lr_log_message("%s", sql);
  76. mysql_query(db, sql);
  77. if (j>0) {
  78.   results2 = mysql_store_result(db);
  79.                   while((record = mysql_fetch_row(results2)))
  80.           {
  81.                       lr_log_message("pid = %d", record[0]);
  82.                    }
  83.           mysql_free_result(results2);
  84. }
  85. }
  86. }
  87. mysql_free_result(results2);
  88. return 0;
  89. }
  90.  
  91.  
  92.  
  93. int my_mysql_insert()
  94. {
  95. int t;
  96.     char sql[500];
  97. MYSQL_RES *results3;
  98. sprintf(sql,"%s","INSERT INTO posts_jx3 ( fid, tid, first, author, authorid, subject, dateline, message, useip, invisible, anonymous, usesig, htmlon, bbcodeoff, smileyoff, parseurloff, attachment, rate, ratetimes, status) VALUES ( 12345, 215053, 0, 'fdsafsdfsd', 2265065, '', 1240535917, 'fdsfsdfdsfd', '118.239.49.229', 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0);");
  99. lr_log_message("%s", sql);
  100.     t = mysql_query(db, sql);
  101. if(t)
  102. {
  103. lr_log_message("%s", mysql_error(db));
  104. }
  105. else
  106. {
  107. lr_log_message("%s", "suc");
  108. }
  109. }