老王的技术手册
百度空间 | 百度首页 
 
文章列表
 
2009年11月18日 星期三 下午 03:03
作者:老王

MySQL从5.0.37起,提供了profile的功能,对性能调试极其有用,不过今天在一台服务器上使用时却出了问题:

# /usr/local/mysql/bin/mysqld -V
/usr/local/mysql/bin/mysqld  Ver 5.0.83-log for pc-linux-gnu on i686 (MySQL Community Server (GPL))

版本号是5.0.83,大于5.0.37,感觉应该可以使用profile功能,可是:

mysql> set profiling = 1;
ERROR 1193 (HY000): Unknown system variable 'profiling'

出人意料,竟然无法使用profile功能。接着试试别的命令:

mysql> show profiles;
ERROR 1289 (HY000): The 'SHOW PROFILES' feature is disabled; you need MySQL built with 'enable-profiling' to have it working

这次的错误信息相对而言更明确了,说编译时没有激活enable-profiling选项。确认一下目前的编译参数:

# cat /usr/local/mysql/bin/mysqlbug | grep CONFIGURE_LINE
CONFIGURE_LINE="./configure  '--prefix=/usr/local/mysql' '--localstatedir=/usr/local/mysql/data' '--libexecdir=/usr/local/mysql/bin' '--with-comment=MySQL Community Server (GPL)' '--with-server-suffix=' '--enable-thread-safe-client' '--enable-local-infile' '--enable-assembler' '--with-pic' '--with-fast-mutexes' '--with-client-ldflags=-static' '--with-mysqld-ldflags=-static' '--with-zlib-dir=bundled' '--with-big-tables' '--with-yassl' '--with-readline' '--with-innodb' '--with-ndbcluster' '--with-archive-storage-engine' '--with-blackhole-storage-engine' '--with-csv-storage-engine' '--without-example-storage-engine' '--with-federated-storage-engine' '--with-extra-charsets=all' 'CC=ccache /usr/local/gcc-4.3.2/bin/gcc -static-libgcc' 'CFLAGS=-g -O3 -march=i686' 'CXX=ccache /usr/local/gcc-4.3.2/bin/gcc -static-libgcc' 'CXXFLAGS=-g -O3 -march=i686'"

确实没enable-profiling选项,不过别以为简单加上这个选项之后重新编译一次就行了,实际上如果想编译成功,还得同时确保使用enable-community-features选项,可参阅官方文档。如果觉得记这些编译命令太烦,那就使用新版的MySQL吧,从5.1.28版本开始,这些选项缺省就是激活的。

补充点知识,查看nginx,apache,mysql,php的编译参数(假设都安装在/usr/local目录):

查看nginx编译参数:/usr/local/nginx/sbin/nginx -V
查看apache编译参数:cat /usr/local/apache/build/config.nice
查看mysql编译参数:cat /usr/local/mysql/bin/mysqlbug | grep CONFIGURE_LINE
查看php编译参数:/usr/local/php/bin/php -i | grep configure
 
2009年11月15日 星期日 下午 01:51
作者:老王

关于shell命令,网上流传着很多奇技淫巧,比如说快速统计日志文件里点击量前十位的URL:

awk '{print $7}' /path/to/log | sort | uniq -c | sort -nr | head -n 10

附:这里假设日志文件是common格式的,如此一来,按空格分隔后,第七个字段就是URL

稍加思考,你会发现这里又是sort,又是uniq,存在重复操作,下面看看如何优化这条shell命令:

01 #!/bin/awk -f
02
03 {
04     url[$7]++
05 }
06
07 END {
08     for (key in url) {
09         print url[key], key | "sort -nr | head -n 10"
10     }
11 }


把上面代码(去掉行号)保存到demo.awk文件里,然后:chmod +x ./demo.awk,做这些就够了。

我找了一个7G的common格式日志文件来测试性能,视服务器性能结果会有差异,但相对值应该差不多:

time awk '{print $7}' /path/to/log | sort | uniq -c | sort -nr | head -n 10

real    1m57.150s
user    1m6.794s
sys     0m13.923s


time ./demo.awk /path/to/log

real    1m21.414s
user    0m32.799s
sys     0m22.556s


总体消耗的时间降低了四分之一,还是很明显的。
 
2009年11月12日 星期四 下午 08:51
作者:老王

下午在QQ群里大家讨论了一下awk的用法,发现自己还是不太熟,其实这东西挺简单的,只是平时用的少,所以温习一下:

原本的数据文件是demo.txt

1 x a=1&b=2&c=3
2 y b=1&c=3
3 z c=2&a=1


需要整理成下面的形式(也就是说值要按照abc的顺序排列,没有的缺省显示“-”):

1 x 1 2 3
2 y - 1 3
3 z 1 - 2


对应的awk脚本demo.awk(拷贝代码的话注意去掉行号):

01 #!/bin/awk -f
02
03 BEGIN {
04     ORS = "";
05     OFS = "\t";
06 }
07
08 NF == 3 {
09     param["a"] = "-";
10     param["b"] = "-";
11     param["c"] = "-";
12
13     print $1, $2, "";
14
15     split($3, query, "&");
16     for (key in query) {
17         split(query[key], item, "=");
18         if (item[1] in param) {
19             param[item[1]] = item[2];
20         }
21     }
22
23     print param["a"], param["b"], param["c"], "\n";
24 }


执行下面语句就能看到效果了:

chmod +x ./demo.awk
./demo.awk demo.txt

有一个有趣的/bin/pgawk,调试时可以用来替换/bin/awk,会生成一个awkprof.out文件,对分析性能有帮助。
 
2009年11月08日 星期日 下午 09:23
作者:老王

我这里所说的主表是指在连接查询里MySQL以哪个表为主进行查询。比如说在LEFT JOIN查询里,一般来说左表就是主表,但这只是经验之谈,很多时候经验主义是靠不住的,为了说明问题,先来个例子,建两个演示用的表categories和posts:

CREATE TABLE IF NOT EXISTS `categories` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(15) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
);

CREATE TABLE IF NOT EXISTS `posts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`category_id` int(10) unsigned NOT NULL,
`title` varchar(100) NOT NULL,
`content` varchar(200) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
KEY `created` (`created`),
KEY `category_id_created` (`category_id`, `created`)
);

先注意一下每个表的索引情况,以后会用到,记得随便插入一点测试数据,不用太多,但怎么也得两行以上,然后执行以下SQL:

EXPLAIN SELECT *
FROM posts
LEFT JOIN categories ON posts.category_id = categories.id
WHERE categories.name LIKE 'foobar%'
ORDER BY posts.created DESC


结果如下所示:

table      key         Extra
categories name       
Using where; Using temporary; Using filesort
posts      category_id


在join查询的explain的结果中,第一行表示的表就是主表。所以说在此查询里categories是主表,而在我们的经验里,LEFT JOIN查询里,左表(posts表)才应该是主表,这产生一个根本的矛盾,MySQL之所以这样处理,是因为在我们的WHERE部分,查询条件是按照categories表的字段来进行筛选的,且categories表刚好存在合适的索引,所以在查询时把categories表作为主表更有利于缩小结果集。

那explain结果中的Using temporary; Using filesort又是为什么呢,为什么created或category_id_created索引无效呢?这是因为主表是categories表,从表是posts表,而我们使用从表的字段去ORDER BY,这通常不是一个好选择,最好改成主表字段。不过很多时候改不了,那就没招了。

再看一个比较怪异的例子:

EXPLAIN SELECT *
FROM posts
LEFT JOIN categories ON posts.category_id = categories.id
WHERE categories.id = ‘一个已经存在的ID’
ORDER BY posts.created DESC


这个例子里posts表仍然是从表,但是按照从表排序的结果却没有出现文件排序和临时表,这是因为已经确定了categories.id,所以主表相当于一个只有一行数据的常量表了,从表根据category_id_created索引在连接的同时自然就得到排序后的结果。但换个角度看,既然categories.id都是确定的了,那类似这样的需求,我们一般就不会再使用LEFT JOIN查询了,而会分成两个独立的查询去检索categories和posts才对。

主观上一旦搞错了主表,可能怎么调整索引都得不到高效的SQL,所以在写SQL时,比如说在写LEFT JOIN查询时,如果希望左表是主表,那么就要保证在WHERE语句里的查询条件尽可能多的使用左表字段,进而,一旦确定了主表,也最好只通过主表字段去ORDER BY。

注意:大多数情况下,使用从表字段去排序都是低效的,我最初的例子误导了大家,已更正。
 
2009年11月07日 星期六 下午 03:45
作者:老王

Web应用中为什么会需要消息队列?主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达mysql,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。在Web2.0的时代,高并发的情况越来越常见,从而使消息队列有成为居家必备的趋势,相应的也涌现出了很多实现方案,像Twitter以前就使用RabbitMQ实现消息队列服务,现在又转而使用Kestrel来实现消息队列服务,此外还有很多其他的选择,比如说:ActiveMQZeroMQ等。

上述消息队列的软件中,大多为了实现AMQP,STOMP,XMPP之类的协议,变得极其重量级,但在很多Web应用中的实际情况是:我们只是想找到一个缓解高并发请求的解决方案,不需要杂七杂八的功能,一个轻量级的消息队列实现方式才是我们真正需要的。

第一感觉是能不能使用memcached来实现消息队列?稍加考虑后就会发现它不合适,因为memcached仅仅支持键值方式的操作,没有排序之类的功能,所以如果要用它来实现消息队列,则必须自己通过某个键来保存数组形式的队列,不过这样的话,在操作队列的时候很容易丢失数据,比如说我们要添加一个消息,则需先取出现有队列,然后把消息保存到队列尾部,最后保存队列,单纯使用memcached的话,由于我们无法保证整个过程的原子性,所以当处理若干个并发请求时,各个请求间可能会互相覆盖,丢失数据就在所难免。另外,memcached只是内存键值缓存而已,一旦宕机,数据就消失了。

memcacheq的出现解决了上面的问题,它在memcached的基础上实现了消息队列,以php客户端为例:

消息从尾部入栈:memcache_set
消息从头部出栈:memcache_get

memcacheq依附于memcached之上,所以你可以通过现有的memcached工具来操作它,这无疑是它的一大优势,但它也有一个很大的缺点,那就是memcacheq本身的开发维护似乎并不活跃,如果遇到问题的话,你很可能需要自己动手解决。

目前看来,我更推荐下面这种解决方案,那就是redis,如果不了解,可以参考我以前的文章,表面上看,redis和memcached差不多,也是键值操作,但是redis本身实现了list,相关操作也可以保证是原子的,所以可以很自然的通过list来实现消息队列:

消息从尾部入栈:RPUSH
消息从头部出栈:LPOP

redis本身虽然是一个新项目,但很有朝气,开发维护也很活跃,如果你的下一个Web应用里需要使用轻量级的消息队列,不妨使用它。

此外,还有不少其他的选择可供尝试,比如说MySQL第三方的Q4M引擎,通过扩展SQL语法来操作消息队列,也是一个不错的选择。

套用网络流行语:那些重量级软件实现的不是你要的功能,而只是独在高处不胜寒的寂寞,所以不必迷恋其中,它们只是传说而已。
 
     
 
 
文章分类
 
 
Os(35)
 
 
 
Php(61)
 
 
 
 
 
 
     
 
关注工具
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
     
 
在线资料
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
     
 
订阅我的空间
 
已有人次访问本空间
 
订阅RSS  什么是RSS?

您也想拥有这样的空间?请点此申请。
     


©2009 Baidu