FROM: 推荐《探索推荐引擎的秘密》系列
最近推荐引擎成为显学,主要原因应该是电子商务的蓬勃发展。头些日子和图灵的两位老师吃饭,我甚至了解到因为推荐引擎以及机器学习领域的日渐火爆,图灵出的线性代数最近销量都很好,更别提大家现在到处都可以看到这个领域相关的招聘。我最近的创业项目iApp4Me其实也是一个推荐引擎的应用,我关注这个领域有很长一段时间了。这个领域还很新,还有很多未知的可能性,非常有意思。
不过在我关注的过程中,我发现很多人其实对什么是推荐引擎一知半解,这有点像5-6年前的技术界对搜索引擎的理解一样,那时候有人曾在CSDN言之凿凿的说Google其实用的就是Mysql无非是服务器多,而且管理员水平高而已。虽然大多数的高校的计算机专业都有信息检索课程,但是很多甚至是名校的毕业生也说不清楚搜索引擎是怎么回事儿。
事实上技术界开始对搜索引擎技术大规模的扫盲是从lucene这个开源软件的出现以后开始的,在这个问题上某Cutting同学居功至伟。后来也是在他组织下Lucene项目组开发孵化出来了Google的MapRuduce架构的开源实现Hadoop。在Yahoo、在阿里巴巴以及全世界很多公司和组织中Hadoop都起到了很大的作用。后来,lucene项目组还孵化了Mahout,一个基于Hadoop和Lucene的机器学习、推荐引擎项目。现在推荐引擎的实践中,这个Mahout这个项目也起到了很大的作用。值得推荐的相关开源项目还有weka,Javaml,numpy等。
当然光有这些开源项目也是不够的,如果你完全不理解推荐引擎的理论,你也很难玩转它。今天我发现了IBM开发者社区近期出现了一组文章《探索推荐引擎的秘密》,写的很好,可以算作非常好的这个领域的综述的文章,非常适合给不了解或者一知半解的人建立概念,所以在这里推荐给大家。
PS:
前几日,titan 推荐我看这篇日志,当然,重点还是文中提到的 IBM 开发者社区推出的关于推荐引擎的系列文章。推荐系统,去年有一阵子看过一些文章,但到此刻,基本上都已归还给作者,so, 现在将其转载过来,以备自己“捡”起来时资料的查找。
还有这篇文章 智能推荐系统 ,很值得一看。
2011年5月31日星期二
Web 开发人员速查卡
FROM: Web开发人员速查卡
无论你是多牛的程序员,你都无法记住所有的东西。而很多时候,查找某些知识又比较费事。所以,网上有很多Cheat Sheets,翻译成小抄也好 ,速查卡也好,总之就是帮你节省 时间的。之前给大家介绍过Web设计的速查卡、25个jQuery的编程小抄,还有程序员小抄大全,今天转一篇开发人员的速查卡,源文在这里。下面的文章我就不翻译了。
无论你是多牛的程序员,你都无法记住所有的东西。而很多时候,查找某些知识又比较费事。所以,网上有很多Cheat Sheets,翻译成小抄也好 ,速查卡也好,总之就是帮你节省 时间的。之前给大家介绍过Web设计的速查卡、25个jQuery的编程小抄,还有程序员小抄大全,今天转一篇开发人员的速查卡,源文在这里。下面的文章我就不翻译了。
HTML Cheat Sheet
- HTML/XTML in one page
- HTML5: The Evolution of Web Standards by James Sugrue
- (X)HTML Elements and Attributes
- Doctype Declarations (DTDs)
- XHTML Character Entity Reference
- GoSquared HTML Help Sheet
CSS Cheat Sheets
- CSS in one page
- CSS Properties and Values
- All CSS Properties Listed Alphabetically
- CSS Shorthand Guide
- GoSquared CSS Help Sheet
Adobe Flash Cheat Sheets
ASP Cheat Sheets
PHP Cheat Sheets
- PHP Basics Quick Reference Sheet
- PHP Cheat Sheet
- PHP Security Cheat Sheet
- PHP Variable and Array Tests
- GoSquared PHP Help Sheet
MySQL Cheat Sheets
JavaScript Cheat Sheets
jQuery Cheat Sheets
- jQuery Cheatsheet
- jQuery 1.3 Visual Cheat Sheet by Antonio Lupetti
- jQuery Selectors by Bear Bibeault & Yehuda Katz
Unicode Cheat Sheets
XML Cheat Sheets
mod_rewrite and .htaccess Cheat Sheets
2011年5月29日星期日
Super Smack
Super Smack 是一个强大的压力测试工具,支持 MySQL, PostgreSQL, Oracle。最开始的版本是由Sasha Pachev写成,由Jeremy Zawodny在维护,而现在,是
Tony Bourke在维护,根据 Tony Bourke 的 开发 log 来看,2005-08-30 后,super smack 就已经停止发布新的版本,但这并不妨碍我们现在继续使用它(这个工具的开发者和维护者很伟大)。
安装有点点麻烦,主要是编译时会出现一些问题。
[bash]
[root@localhost tmp]# wget http://vegan.net/tony/supersmack/super-smack-1.3.tar.gz
[root@localhost tmp]# tar xvzf super-smack-1.3.tar.gz
[root@localhost super-smack-1.3]# ./configure --prefix=/usr/local/super-smack --with-mysql --with-mysql-lib=/usr/local/mysql/lib/mysql --with-mysql-include=/usr/local/mysql/include/mysql
[root@localhost super-smack-1.3]# make
[root@localhost super-smack-1.3]# make install
[/bash]
1 编译时,必须指定它所支持的数据库管理系统,否则会报如下错误。
2 选择支持 MySQL 后, 在 MySQL 编译安装的情况下时, 也需要在编译参数中指定 MySQL 的 lib。
3 编译时,dictionary.h 和 super-smack.cc 报错:
在 super smack 源代码的
4 编译时,query.cc 报错
在 super smack 源代码的
1 看 Makefile 才知道, smack 文件是放在
[bash]
...
SMACKS_DIR = /usr/share/smacks
DATADIR = /var/smack-data
...
[/bash]
2 根据 MySQL 的参数对应修改 /usr/share/smacks 目录下的 select-key.smack 和 update-select.smack 文件。包括 user, host, db, pass, 还有,最重要的 socket 路径。
3 未将 super smack 的 bin 目录加入 PATH 时,则还需:
[bash]
gen_data_file "gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
#改为
gen_data_file "./gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
#或者直接
gen_data_file "/usr/local/super-smack/bin/gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
[/bash]
4 因为在 select-key.smack 和 update-select.smack 文件中指定的数据文件是 words.dat, 而安装默认情况下 words.dat 为空文件:
[bash]
[root@localhost smack-data]# ls -al
total 5320
drwxr-xr-x 2 root root 4096 May 26 18:12 .
drwxr-xr-x 24 root root 4096 May 26 17:25 ..
-rw-r--r-- 1 root root 5421337 May 26 17:25 .. http_auth.dat
-rw-r--r-- 1 root root 0 May 26 17:25 .. words.dat
[/bash]
所以,需将 select-key.smack 和 update-select.smack 文件中指定的数据 words.dat 改为 http_auth.dat(其实为了统一,最好也是 http_auth.dat,因为文件中指定测试的表名是 http_atuh)。
当然,为了不修改,也可以在开始先执行如下命令:
[bash]
[root@localhost bin]# /usr/local/super-smack/bin/gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d > /var/smack-data/words.dat
[/bash]
5 将 /usr/share/smacks 下文件 copy 到 /usr/local/super-smack/bin/ 目录下:
[bash]
[root@localhost super-smack]# cp /usr/share/smacks/* bin/
[/bash]
[bash]
[root@localhost bin]# ./super-smack -d mysql select-key.smack 20 1000
Query Barrel Report for client smacker1
connect: max=2204ms min=1ms avg= 221ms from 20 clients
Query_type num_queries max_time min_time q_per_s
select_index 40000 0 0 5017.26
[/bash]
参数:
实际上,还有
返回结果:
Tony Bourke在维护,根据 Tony Bourke 的 开发 log 来看,2005-08-30 后,super smack 就已经停止发布新的版本,但这并不妨碍我们现在继续使用它(这个工具的开发者和维护者很伟大)。
安装
安装有点点麻烦,主要是编译时会出现一些问题。
[bash]
[root@localhost tmp]# wget http://vegan.net/tony/supersmack/super-smack-1.3.tar.gz
[root@localhost tmp]# tar xvzf super-smack-1.3.tar.gz
[root@localhost super-smack-1.3]# ./configure --prefix=/usr/local/super-smack --with-mysql --with-mysql-lib=/usr/local/mysql/lib/mysql --with-mysql-include=/usr/local/mysql/include/mysql
[root@localhost super-smack-1.3]# make
[root@localhost super-smack-1.3]# make install
[/bash]
说明
1 编译时,必须指定它所支持的数据库管理系统,否则会报如下错误。
......
configure: error:
You should include support for at least one database!
Reconfigure with one or more of:
--with-mysql
--with-pgsql
--with-oracle
2 选择支持 MySQL 后, 在 MySQL 编译安装的情况下时, 也需要在编译参数中指定 MySQL 的 lib。
3 编译时,dictionary.h 和 super-smack.cc 报错:
......
dictionary.h:93: error: ‘strlen’ was not declared in this scope
super-smack.cc:126: error: ‘strlen’ was not declared in this scope
在 super smack 源代码的
src 目录,找到 dictionary.h, super-smack.cc,分别加上#include <string.h>。4 编译时,query.cc 报错
query.cc:200: error: cast from ‘char*’ to ‘unsigned int’ loses precision
query.cc:200: error: cast from ‘char*’ to ‘unsigned int’ loses precision
query.cc:219: error: cast from ‘char*’ to ‘unsigned int’ loses precision
query.cc:219: error: cast from ‘char*’ to ‘unsigned int’ loses precision
在 super smack 源代码的
src 目录, 找到 query.cc文件,将上面指定的 200, 219 行中的unsigned int改为unsigned long。用其测试 MySQL 之前奏
1 看 Makefile 才知道, smack 文件是放在
/usr/share/smacks 这个目录下(开始并不知道源代码中有一份), 产生的数据文件是在/var/smack-data目录下[bash]
...
SMACKS_DIR = /usr/share/smacks
DATADIR = /var/smack-data
...
[/bash]
2 根据 MySQL 的参数对应修改 /usr/share/smacks 目录下的 select-key.smack 和 update-select.smack 文件。包括 user, host, db, pass, 还有,最重要的 socket 路径。
3 未将 super smack 的 bin 目录加入 PATH 时,则还需:
[bash]
gen_data_file "gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
#改为
gen_data_file "./gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
#或者直接
gen_data_file "/usr/local/super-smack/bin/gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
[/bash]
4 因为在 select-key.smack 和 update-select.smack 文件中指定的数据文件是 words.dat, 而安装默认情况下 words.dat 为空文件:
[bash]
[root@localhost smack-data]# ls -al
total 5320
drwxr-xr-x 2 root root 4096 May 26 18:12 .
drwxr-xr-x 24 root root 4096 May 26 17:25 ..
-rw-r--r-- 1 root root 5421337 May 26 17:25 .. http_auth.dat
-rw-r--r-- 1 root root 0 May 26 17:25 .. words.dat
[/bash]
所以,需将 select-key.smack 和 update-select.smack 文件中指定的数据 words.dat 改为 http_auth.dat(其实为了统一,最好也是 http_auth.dat,因为文件中指定测试的表名是 http_atuh)。
当然,为了不修改,也可以在开始先执行如下命令:
[bash]
[root@localhost bin]# /usr/local/super-smack/bin/gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d > /var/smack-data/words.dat
[/bash]
5 将 /usr/share/smacks 下文件 copy 到 /usr/local/super-smack/bin/ 目录下:
[bash]
[root@localhost super-smack]# cp /usr/share/smacks/* bin/
[/bash]
运行
[bash]
[root@localhost bin]# ./super-smack -d mysql select-key.smack 20 1000
Query Barrel Report for client smacker1
connect: max=2204ms min=1ms avg= 221ms from 20 clients
Query_type num_queries max_time min_time q_per_s
select_index 40000 0 0 5017.26
[/bash]
参数:
-d指定测试的数据库管理系统的类型。2020 个线程1000每个线程 1000 个查询
实际上,还有
-D参数来指定数据文件,默认路径如前面提到是 /var/smack-data, 这个路径需跟 select-key.smack 和 update-select.smack 指定的一致。返回结果:
- max=2204ms min=1ms avg= 221ms from 20 clients 连接的最大、最小及平均花费时间。
- q_per_s|5017.26 QPS,每秒请求处理数
- 40000, 脚本中,对查询次数做了翻倍处理, 所以, 20 × 1000 x 2 = 40,000.
标签:
压力测试
,
MySQL
,
super smack
2011年5月25日星期三
Install PHP 5.3 using source code
compile configuration
[bash]
./configure \
--prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc \
--with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir \
--enable-xml \
--disable-debug \
--disable-rpath \
--enable-safe-mode \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--without-sqlite \
--with-curlwrappers \
--enable-mbregex \
--enable-mbstring \
--with-mcrypt \
--enable-cgi \
--enable-fpm --with-fpm-user=www --with-fpm-group=www
[/bash]
[bash]
./configure \
--prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc \
--with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir \
--enable-xml \
--disable-debug \
--disable-rpath \
--enable-safe-mode \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--without-sqlite \
--with-curlwrappers \
--enable-mbregex \
--enable-mbstring \
--with-mcrypt \
--enable-cgi \
--enable-fpm --with-fpm-user=www --with-fpm-group=www
[/bash]
2011年5月23日星期一
Libtool library used but `LIBTOOL' is undefined
Install HandlerSocket 时,报如下错误:
(Solution) Libtool library used but `LIBTOOL' is undefined这里有提到类似的问题,解决办法很简单,合并文件后,删除多余的,然后创建一符号链接。
当然,也可以直接:
[bash]
cp -R /usr/local/share/aclocal /usr/share/
[/bash]
handlersocket/Makefile.am:3: Libtool library used but `LIBTOOL' is undefined
(Solution) Libtool library used but `LIBTOOL' is undefined这里有提到类似的问题,解决办法很简单,合并文件后,删除多余的,然后创建一符号链接。
This is caused because FreeBSD renames the default tools, like aclocal to aclocal19. When aclocal is run, it looks for files in its FreeBSD set directory,
/usr/local/share/aclocal19 but most normal apps, like libtool, install to /usr/local/share/aclocal. The solution? Simple! Just merge the contents into one
directory, delete the other, and create a symlink!
当然,也可以直接:
[bash]
cp -R /usr/local/share/aclocal /usr/share/
[/bash]
jQuery Tab
FROM: jQuery idTabs
推荐一 jQuery 实现 Tab 的插件,代码很简短,而且调用也非常简单。head 加上:
[html]
<script type="text/javascript" src="jquery.idTabs.min.js"></script>
[/html]
[html]
<ul class="idTabs">
<li><a href="#jquery">jQuery</a></li>
<li><a href="#official">Tabs 3</a></li>
</ul>
<div id="jquery">If you haven't checked out ...</div>
<div id="official">idTabs is only a simple ...</div>
[/html]
demo
推荐一 jQuery 实现 Tab 的插件,代码很简短,而且调用也非常简单。head 加上:
[html]
<script type="text/javascript" src="jquery.idTabs.min.js"></script>
[/html]
class="idTabs" element 中,任何<a href="#tabs">以这种形式调用,被点击时都会显示对应 id="tab" element 中的内容。如下:[html]
<ul class="idTabs">
<li><a href="#jquery">jQuery</a></li>
<li><a href="#official">Tabs 3</a></li>
</ul>
<div id="jquery">If you haven't checked out ...</div>
<div id="official">idTabs is only a simple ...</div>
[/html]
demo
标签:
Javascript
,
jQuery
,
Plugin
PHP proxy
[php]
$requestUrl = 'http://request-url.com';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $requestUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, '300');
curl_setopt($curl, CURLOPT_PROXY, 'proxy-ip:proxy:port');
curl_exec($curl);
curl_close($curl);
[/php]
$requestUrl = 'http://request-url.com';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $requestUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, '300');
curl_setopt($curl, CURLOPT_PROXY, 'proxy-ip:proxy:port');
curl_exec($curl);
curl_close($curl);
[/php]
跳出.each()
[js]
$.each(objs, function(i, n) {
if(n.attr('checked') == true) {
return false;
}
});
[/js]
换到新的公司后,有专门的前端工程师,以至于之前知道的 JS 也都忘记。而最近做内部沟通系统时,一切需要自己实现,才又捡起来。
$.each(objs, function(i, n) {
if(n.attr('checked') == true) {
return false;
}
});
[/js]
return false 跳出 jQuery .each().换到新的公司后,有专门的前端工程师,以至于之前知道的 JS 也都忘记。而最近做内部沟通系统时,一切需要自己实现,才又捡起来。
Using MySQL as a NoSQL
FROM: Using MySQL as a NoSQL - A story for exceeding 750,000 qps on a commodity server
由于 MySQL 的局限性,很多站点都采用了 MySQL+Memcached 的架构。另外一些站点放弃 MySQL 而采用 NoSQL,比如 TokyoCabinet/Tyrant 等。不可否认,在做一些简单查询 (尤其 PK 查询) 的时候,NoSQL 比 MySQL 要快很多很多。而且网站上的绝大多数查询都是这样的简单查询。
像其他大规模的公司一样,DeNA 也面临过类似的问题。但最后我们采用的是一完全不同的方法, 仅使用了 MySQL。我们仍然使用 Memcached 做前端缓存(例如,预处理 HTML, 数量/摘要 信息),但在后端,既没有使用 Memcached 缓存任何记录,也没有使用 NoSQL,这是为什么呢?因为与其他的 NoSQL 产品相比,我们的 MySQL 能得到更好的性能。 在我们的基准测试中,一普通的 MySQL/InnoDB 5.1 服务器达到了 750,000+ QPS,生产环境中的性能当然更不列外。或许,你们很难相信这个数字,但这是事实。我将在以下分享我们的经验。
(作者经历)2010-08,我离开了 Oracle, 现在任职于日本最大社交游戏平台供应商之一的 DeNA。
在每秒中,需要做多少次的 PK 查询了?在 DeNA 公司的应用中,经常要进行 PK 查询。比如根据 user id 取出 userinfo,根据 diary id 取出日志内容, 对于这样的需求,不用说,Memcached 和 NoSQL 都相当适合。在简单的多线程 "Memcached GET"基准测试中,很可能每秒进行 400,000 次 get 操作,即使 Memcached client 在不同的服务器。在一台 Nehalem box 2.5GHz x 8 核 CPU, Broadcom 四端口千兆网卡的服务器上,最新的 libMemcached 和 Memcached 每秒可达到 420,000 次 get 操作。
在 MySQL 下, 每秒可作多少次的 PK 查询呢, 我们可用 sysbench, super-smack or mysqlslap 等来进行基准测试
[bash]
[matsunobu@host ~]$ mysqlslap --query="select user_name,.. from test.user where user_id=1" \
--number-of-queries=10000000 --concurrency=30 --host=xxx -uroot
[/bash]
通过如下命令,很快就得知 InnoDB 的 QPS 大概为 100,000, 几乎只有 Memcached 的 1/4.
[bash]
[matsunobu@host ~]$ mysqladmin extended-status -i 1 -r -uroot \
| grep -e "Com_select"
[/bash]
看上去, 100, 000+ QPS 也不是太差,但为什么 MySQL 比 Memcached 差这么多呢,MySQL 到底在做什么呢。从 vmstat 的统计信息得知, %user 和 %system 的数据都非常高.
[bash]
[matsunobu@host ~]$ vmstat 1
[/bash]
再看 Oprofile 输出,可知 CPU 消耗的出去:
MySQL 的 SQL 解析阶段,有调用 MYSQLparse() 和 MYSQLlex(); 查询优化阶段,调用 make_join_statistics() 和 JOIN::optimize()。很明显,主要耗资源的是SQL 层,而不是 InnoDB 存储层。与 Memcached/NoSQL 比起来,MySQL 还要额外做一些工作:
另外,MySQL 还必须要做大量的并发控制,比如在发送/接收网络数据包的时候,fcntl() 就要被调用很多次; Global mutexes 比如 LOCK_open,LOCK_thread_count 也被频繁地取得/释放。所以, 在 Oprofile 的输出中,排在第二位的是 my_pthread_fastmutex_lock()。并且 %system 占用的 CPU 相当高(28%)。
其实 MySQL 开发团队和外围的开发团体已意识到大量并发控制对性能的影响,MySQL5.5 中已经解决了一些问题。未来的 MySQL 版本中,应该会越来越好。
还有一个大的问题是,%user 达到了60%。互斥量的争夺导致 %system 上增,而不是 %user,即使 MySQL 内部关于互斥量的的问题都得到修复,还是很难达到我们所期望的 300,000 QPS.也许,会有人提到使用
如果只有一小部分数据进入内存,那么 SQL 带来的消耗可以忽略不计。很简单,因为磁盘的 I/0 操作所带来的消耗会要大,这种情况下时,就不需要太过的去考虑 SQL 所带来的消耗。
但是,在大多数的 hot MySQL 服务器中, 大部分的数据都是因为全部载入至内存中而变的只受 CPU 的限制。Profiling 的结果就类似上所述的那样: SQL 层消耗了大量的资源。假设,需要做大量的 PK 查询(i.e. SELECT x FROM t WHERE id=?)或者是做 LIMIT 的范围查询, 即使有 70-80% 都是在同一张表中做 PK 查询(仅仅只是查询条件中给定的值不同,即 value 不同而已), MySQL 还是每次需要去做 parse/open/lock/unlock/close, 这对我们来说,是非常影响效率的。
到底有没有好的方法来减少 MySQL SQL 层的 CPU 资源/争夺呢? 如果使用 MySQL Cluster, NDBAPI 不失为一个很好的解决办法。 在我还是 MySQL/Sun/Oracle 的顾问时,就见到过很多客户对SQL Node + NDB performance 感到非常不爽,但当他们用了 NDBAPI 客户端后,发现性能调提高了 N 倍。当然,在 MySQL Cluster 中是可以同时使用 NDBAPI 和 SQL 的,但在做频繁的访问模式时还是推荐使用 NDBAPI,而在 ad-hoc 或者 查询不频繁的情况下使用 SQL + MySQL + NDB。
以快捷的速度访问 API, 这正是我们需要的,但同时我们也想在 ad_hoc 或者复杂的查询的情况时还是使用 SQL. 像其他的 web service, DeNA 使用的是 InnoDB, 转为 NDB,这并不是一件容易的事情,因为内置InnoDB 即不支持 SQL 也不支持网络层的服务。
最好的办法可以是在 MySQL 的内部,实现一以 MySQL plugin 的形式存在的 NoSQL 的网络服务。它侦听在某端口来接收采用 NoSQL 协议/API 的通讯, 然后通过 MySQL 内部的存储引擎 API 来直接访问 InnoDB。这种方法的理念类似于 NDBAPI, 但是它可以做到与 InnoDB 通讯。
这个理念最初是去年由 Kazuho Oku 在 Cybozu Labs 上提出的,他曾写过采用 Memcached protocols 通讯的MyCached UDF。而我的大学同学实现了另外一个插件 -- HandlerSocket, 下面图描述了 HandlerSocket 具体做了哪些工作:

因为 HandlerSocket 是以 MySQL daemaon plugin 形式存在,所以在应用中可把 MySQL 当 NoSQL 使用. 它最大的功能是实现了与存储引擎交互,比如 InnoDB,而这不需要任何的 SQL 方面的开销. 访问 MySQL 的 table 时,当然她也是需要 open/close table 的,但是 它并不是每次都去 open/close table, 因为它会将以前访问过的 table 保存下来以供来是使用,而 opening/closing tables 是最耗资源的,而且很容易引起互斥量的争夺,这样一来,对于提高性能,非常有效。在流量变小时, HandlerSocket 会 close tables, 所以,它不会阻塞 administrative commands (DDL).
它与MySQL + Memcached 的区别在哪呢? 对比图1 和图2 ,可从中看出其不同点。图2 展示了典型的 MySQL + Memecached 的使用. 因为 Memcached 的 get 操作比 MySQL 的内存中/磁盘上的主键查询要快很多,所以 Memcached 用于缓存数据库记录。如果 HandlerSocket 的查询速度能和 Memcached 媲美,我们就不需要使用 Memcached 来缓存记录。

举一个例子,假设有一 user 表,通过 user_id 来获取用户信息:
[sql]
CREATE TABLE user
(
user_id INT UNSIGNED PRIMARY KEY,
user_name VARCHAR(50),
user_email VARCHAR(255),
created DATETIME
)
ENGINE=InnoDB;
[/sql]
用 SELECT 语句获取用户信息
[sql]
mysql> SELECT user_name, user_email, created FROM user WHERE user_id=101;
+---------------+-----------------------+---------------------+
| user_name | user_email | created
| +---------------+-----------------------+---------------------+
| Yukari Takeba | yukari.takeba@dena.jp | 2010-02-03 11:22:33
| +---------------+-----------------------+---------------------+
1 row in set (0.00 sec)
[/sql]
下面我们来看看如何使用 HandlerSocket 完成同样的事情.
HandlerSocket具体安装步骤请参考这里,基本步骤如下:
因为 HandlerSocket是 MySQL 插件,所以可以象使用其它插件,如 InnoDB, Q4M 和 Spider 那样使用它,即不需要修改 MySQL 源代码,MySQL 最好是 5.1 或更高版本,编译 HandlerSocket 时需要 MySQL 源码和 MySQL bin 库。
目前已提供 C++ 和 perl 调用的客户端库,下面是使用 perl 调用的实例代码:
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use Net::HandlerSocket;
#1. establishing a connection
my $args = { host => 'ip_to_remote_host', port => 9998 };
my $hs = new Net::HandlerSocket($args);
#2. initializing an index so that we can use in main logics.
# MySQL tables will be opened here (if not opened)
my $res = $hs->open_index(0, 'test', 'user', 'PRIMARY',
'user_name,user_email,created');
die $hs->get_error() if $res != 0;
#3. main logic
#fetching rows by id
#execute_single (index id, cond, cond value, max rows, offset)
$res = $hs->execute_single(0, '=', [ '101' ], 1, 0);
die $hs->get_error() if $res->[0] != 0;
shift(@$res);
for (my $row = 0; $row < 1; ++$row) {
my $user_name= $res->[$row + 0];
my $user_email= $res->[$row + 1];
my $created= $res->[$row + 2];
print "$user_name\t$user_email\t$created\n";
}
#4. closing the connection
$hs->close()
[/perl]
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use Net::HandlerSocket;
#1. establishing a connection
my $args = { host => 'ip_to_remote_host', port => 9998 };
my $hs = new Net::HandlerSocket($args);
#2. initializing an index so that we can use in main logics.
# MySQL tables will be opened here (if not opened)
my $res = $hs->open_index(0, 'test', 'user', 'PRIMARY',
'user_name,user_email,created');
die $hs->get_error() if $res != 0;
#3. main logic
#fetching rows by id
#execute_single (index id, cond, cond value, max rows, offset)
$res = $hs->execute_single(0, '=', [ '101' ], 1, 0);
die $hs->get_error() if $res->[0] != 0;
shift(@$res);
for (my $row = 0; $row < 1; ++$row) {
my $user_name= $res->[$row + 0];
my $user_email= $res->[$row + 1];
my $created= $res->[$row + 2];
print "$user_name\t$user_email\t$created\n";
}
#4. closing the connection
$hs->close();
[/perl]
上面代码是通过 user_id=101 条件在 user 表获取用户 user_name, user_email和 created 信息,得到的结果应该和之前在 MySQL client 查询出来的结果一样。
[bash]
[matsunobu@host ~]$ perl sample.pl
Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33
[/bash]
对于大多数Web应用程序而言,保持轻量级的 HandlerSocket 连接是一个很好的做法(持续连接),让大量的请求可以集中于主要逻辑(上面代码中的#3部分)。
HandlerSocket 协议是一个小尺寸的基于文本的协议,和 Memcached 文本协议类似,可以使用 telnet 通过 HandlerSocket 获取数据。
[bash]
[matsunobu@host ~]$ telnet 192.168.1.2 9998
Trying 192.168.1.2...
Connected to xxx.dena.jp (192.168.1.2).
Escape character is '^]'.
P 0 test user PRIMARY user_name,user_email,created
0 1
0 = 1 101
0 3 Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33
[/bash]
现在是时候展示基准测试结果,使用上面的 user 表,从多线程远程客户端测试了执行主键查询操作的次数,所有用户数据都装入到内存中(我测试了 100 万行),也用类似的数据测试了 Memcached(我使用 libMemcached 和 Memcached_get() 获取用户数据),在 MySQL SQL 测试中,我使用了的是传统的 SELECT 语句: "SELECT user_name, user_email, created FROM user WHERE user_id=?", Memcached 和 HandlerSocket 客户端代码均使用 C/C++ 编写,所有客户端程序都位于远程主机上,通过 TCP/IP 连接到 MySQL/Memcached。最高的吞吐量情况如下:
HandlerSocket的吞吐量比使用传统 SQL 时高出 7.5, 而且 %us 也只有使用传统 SQL 时的3/4, 这说明 MySQL 的 SQL 层是非常耗资源的,如果能跳过这一层,性能肯定会大大提升。有趣的是,MySQL 使用 HandlerSocket 时的速度比使用 Memcached 也要快 178%,并且 Memcached 消耗的 %sy 资源也更多。所以虽然 Memcached 是一个很好的产品,但仍然有优化的空间。
下面是oprofile输出内容,是在 MySQL HandlerSocket 测试期间收集到的,在核心操作,如网络数据包处理,获取数据等的 CPU 资源消耗(bnx2是一个网络设备驱动程序)。
因为 HandlerSocket 是运行于 MySQL 内部,直接与 InnoDB 交互,所以,可以使用常见的 SQL 命令,如
[bash]
$ mysqladmin extended-status -uroot -i 1 -r | grep "InnoDB_rows_read"
...
| Innodb_rows_read | 750192 |
| Innodb_rows_read | 751510 |
| Innodb_rows_read | 757558 |
| Innodb_rows_read | 747060 |
| Innodb_rows_read | 748474 |
| Innodb_rows_read | 759344 |
| Innodb_rows_read | 753081 |
| Innodb_rows_read | 754375 |
...
[/bash]
测试用机的详细信息如下:
Memcached 和 HandlerSocket 都做了网络 I/O 限制,当我测试单个端口时,HandlerSocket 的 QPS 为 260000,而 Memcached 为 220000。
如下所述,HandlerSocket 有其自己的特点和优势,而其中一些对我们来说, 是真的很给力.
HandlerSocket 目前支持 主键/唯一性查询,非唯一性索引查询,范围扫描,LIMIT 和 INSERT/UPDATE/DELETE,但还不支持未使用任何索引的操作。另外,
HandlerSocket 连接是轻量级的,因为 HandlerSocket 采用
HandlerSocket,如上所描述, 相对于其它 NoSQL 阵容,性能表现一点也不逊色。事实上,我还未曾见过哪个 NoSQL 产品在一台普通服务器上可达到 750000+ 次查询。它不仅没有调用与 SQL 相关的函数,还优化了网络/并发相关的问题。
当使用 Memcached 缓存 MySQL/InnoDB 记录时,在 Memcached 和 InnoD B缓冲池中均缓存了这些记录,因此效率非常低(内存仍然很贵). 而采用 HandlerSocket插件, 由于它访问 InnoDB 存储引擎,记录缓存在 InnoDB 缓冲池中,这样,其它 SQL 语句就可以重复使用它。
由于数据只存储在一个地方(InnoDB 内),不像使用 Memcached 时,需要在 Memcached 和 MySQL 之间检查数据一致性。
后端存储是 InnoDB,它是事务性和崩溃安全的,即使有设置innodb-flush-log-at-trx-commit!=1,在服务器崩溃时也只会丢掉 < 1s 内的数据。
在许多情况下,人们仍然希望使用 SQL(如生产摘要报告),这就是为什么我们不能使用嵌入式 InnoDB 的原因,大多数 NoSQL 产品都不支持 SQL 接口,HandlerSocket 仅仅是一个 MySQL 插件,可以从 MySQL 客户端发送 SQL 语句,但当需要高吞吐量时,最好使用 HandlerSocket。
因为 HandlerSocket 运行于 MySQL 内部,因此所有 MySQL 操作,如 SQL,在线备份,复制,通过 Nagios/EnterpriseMonitor 监控等都是支持的,HandlerSocket 获得可以通过普通的 MySQL 命令监控,如
因为 HandlerSocket 是一个插件,所以它支持 MySQL 社区版和企业服务器版,而无需对 MySQL 做出任何修改就可以使用。
虽然我们只测试了5.1和5.5 InnoDB 插件,但 HandlerSocket 可以和任何存储引擎交互。
尽管它很容易使用,但仍然需要学习如何与 HandlerSocket 交互,我们提供了C++ API、Perl绑定。
和其它NoSQL数据库类似,HandlerSocket不支持安全功能,HandlerSocket的工作线程以系统用户权限运行,因此应用程序可以访问通过 HandlerSocket 协议的所有表,当然,你可以象其它 NoSQL 产品一样使用防火墙过滤数据包。
对于 HDD I/O 绑定工作负载,数据库每秒无法执行数千次查询,通常只有 1-10% 的 CPU 利用率,在这种情况下,SQL 执行层不会成为瓶颈,因此使用HandlerSocket没有什么优势,我们只在数据完全装载到内存的服务器上使用 HandlerSocket。
我们已经在生产环境中使用了 HandlerSocket 插件,效果很明显,因为我们减少了许多 Memcached 和 MySQL 从属服务器,而且整个网络流量也在减少。目前还没有发现任何性能问题(如响应时间慢,延迟等)。
我认为, NoSQL/Database 社区完全低估了 MySQL, 相对于其他产品来说,它历史悠久,而且到目前为止,我优秀的前同事们也做了许多独特的、伟大的改进。从 NDBAPI 可以看出 MySQL 有成为 NoSQL 的潜力,因为存储引擎 API 和守护进程接口的完全独立,使得 Akira 和 DeNA 开发 HandlerSocket 成为可能。作为 MySQL 一名前员工和对 MySQL 长期的了解,我想看到 MySQL 变得更好,更受欢迎,而不仅仅只作为一个 RDBMS,也应该成为 NoSQL 阵营中的一员。
由于 MySQL 的局限性,很多站点都采用了 MySQL+Memcached 的架构。另外一些站点放弃 MySQL 而采用 NoSQL,比如 TokyoCabinet/Tyrant 等。不可否认,在做一些简单查询 (尤其 PK 查询) 的时候,NoSQL 比 MySQL 要快很多很多。而且网站上的绝大多数查询都是这样的简单查询。
像其他大规模的公司一样,DeNA 也面临过类似的问题。但最后我们采用的是一完全不同的方法, 仅使用了 MySQL。我们仍然使用 Memcached 做前端缓存(例如,预处理 HTML, 数量/摘要 信息),但在后端,既没有使用 Memcached 缓存任何记录,也没有使用 NoSQL,这是为什么呢?因为与其他的 NoSQL 产品相比,我们的 MySQL 能得到更好的性能。 在我们的基准测试中,一普通的 MySQL/InnoDB 5.1 服务器达到了 750,000+ QPS,生产环境中的性能当然更不列外。或许,你们很难相信这个数字,但这是事实。我将在以下分享我们的经验。
(作者经历)2010-08,我离开了 Oracle, 现在任职于日本最大社交游戏平台供应商之一的 DeNA。
SQL 真的适合做 PK 查询?
在每秒中,需要做多少次的 PK 查询了?在 DeNA 公司的应用中,经常要进行 PK 查询。比如根据 user id 取出 userinfo,根据 diary id 取出日志内容, 对于这样的需求,不用说,Memcached 和 NoSQL 都相当适合。在简单的多线程 "Memcached GET"基准测试中,很可能每秒进行 400,000 次 get 操作,即使 Memcached client 在不同的服务器。在一台 Nehalem box 2.5GHz x 8 核 CPU, Broadcom 四端口千兆网卡的服务器上,最新的 libMemcached 和 Memcached 每秒可达到 420,000 次 get 操作。
在 MySQL 下, 每秒可作多少次的 PK 查询呢, 我们可用 sysbench, super-smack or mysqlslap 等来进行基准测试
[bash]
[matsunobu@host ~]$ mysqlslap --query="select user_name,.. from test.user where user_id=1" \
--number-of-queries=10000000 --concurrency=30 --host=xxx -uroot
[/bash]
通过如下命令,很快就得知 InnoDB 的 QPS 大概为 100,000, 几乎只有 Memcached 的 1/4.
[bash]
[matsunobu@host ~]$ mysqladmin extended-status -i 1 -r -uroot \
| grep -e "Com_select"
[/bash]
...
| Com_select | 107069 |
| Com_select | 108873 |
| Com_select | 108921 |
| Com_select | 109511 |
| Com_select | 108084 |
| Com_select | 108483 |
| Com_select | 108115 |
...
看上去, 100, 000+ QPS 也不是太差,但为什么 MySQL 比 Memcached 差这么多呢,MySQL 到底在做什么呢。从 vmstat 的统计信息得知, %user 和 %system 的数据都非常高.
[bash]
[matsunobu@host ~]$ vmstat 1
[/bash]
r b swpd free buff cache in cs us sy id wa st
23 0 0 963004 224216 29937708 58242 163470 59 28 12 0 0
24 0 0 963312 224216 29937708 57725 164855 59 28 13 0 0
19 0 0 963232 224216 29937708 58127 164196 60 28 12 0 0
16 0 0 963260 224216 29937708 58021 165275 60 28 12 0 0
20 0 0 963308 224216 29937708 57865 165041 60 28 12 0 0
再看 Oprofile 输出,可知 CPU 消耗的出去:
samples % app name symbol name
259130 4.5199 mysqld MYSQLparse(void*)
196841 3.4334 mysqld my_pthread_fastmutex_lock
106439 1.8566 libc-2.5.so _int_malloc
94583 1.6498 bnx2 /bnx
284550 1.4748 ha_innodb_plugin.so.0.0.0 ut_delay
67945 1.1851 mysqld _ZL20make_join_statisticsP4JOINP10TABLE_LISTP4ItemP16st_dynamic_array
63435 1.1065 mysqld JOIN::optimize()
55825 0.9737 vmlinux wakeup_stack_begin
55054 0.9603 mysqld MYSQLlex(void*, void*)
50833 0.8867 libpthread-2.5.so pthread_mutex_trylock
49602 0.8652 ha_innodb_plugin.so.0.0.0 row_search_for_mysql
47518 0.8288 libc-2.5.so memcpy
46957 0.8190 vmlinux .text.elf_core_dump
46499 0.8111 libc-2.5.so malloc
MySQL 的 SQL 解析阶段,有调用 MYSQLparse() 和 MYSQLlex(); 查询优化阶段,调用 make_join_statistics() 和 JOIN::optimize()。很明显,主要耗资源的是SQL 层,而不是 InnoDB 存储层。与 Memcached/NoSQL 比起来,MySQL 还要额外做一些工作:
- Parsing SQL statements 解析 SQL.
- Opening, locking tables 打开并锁定表.
- Making SQL execution plans SQL 执行计划.
- Unlocking, closing tables 解锁并关闭表.
另外,MySQL 还必须要做大量的并发控制,比如在发送/接收网络数据包的时候,fcntl() 就要被调用很多次; Global mutexes 比如 LOCK_open,LOCK_thread_count 也被频繁地取得/释放。所以, 在 Oprofile 的输出中,排在第二位的是 my_pthread_fastmutex_lock()。并且 %system 占用的 CPU 相当高(28%)。
其实 MySQL 开发团队和外围的开发团体已意识到大量并发控制对性能的影响,MySQL5.5 中已经解决了一些问题。未来的 MySQL 版本中,应该会越来越好。
还有一个大的问题是,%user 达到了60%。互斥量的争夺导致 %system 上增,而不是 %user,即使 MySQL 内部关于互斥量的的问题都得到修复,还是很难达到我们所期望的 300,000 QPS.也许,会有人提到使用
HANDLER ,但是因为在解析 SQL时,opening/closing table 还是必须的,所以对于提高吞吐量,它还是只能爱莫能助。在完全内存操作的情况时, CPU的效率非常重要
如果只有一小部分数据进入内存,那么 SQL 带来的消耗可以忽略不计。很简单,因为磁盘的 I/0 操作所带来的消耗会要大,这种情况下时,就不需要太过的去考虑 SQL 所带来的消耗。
但是,在大多数的 hot MySQL 服务器中, 大部分的数据都是因为全部载入至内存中而变的只受 CPU 的限制。Profiling 的结果就类似上所述的那样: SQL 层消耗了大量的资源。假设,需要做大量的 PK 查询(i.e. SELECT x FROM t WHERE id=?)或者是做 LIMIT 的范围查询, 即使有 70-80% 都是在同一张表中做 PK 查询(仅仅只是查询条件中给定的值不同,即 value 不同而已), MySQL 还是每次需要去做 parse/open/lock/unlock/close, 这对我们来说,是非常影响效率的。
NDBAPI
到底有没有好的方法来减少 MySQL SQL 层的 CPU 资源/争夺呢? 如果使用 MySQL Cluster, NDBAPI 不失为一个很好的解决办法。 在我还是 MySQL/Sun/Oracle 的顾问时,就见到过很多客户对SQL Node + NDB performance 感到非常不爽,但当他们用了 NDBAPI 客户端后,发现性能调提高了 N 倍。当然,在 MySQL Cluster 中是可以同时使用 NDBAPI 和 SQL 的,但在做频繁的访问模式时还是推荐使用 NDBAPI,而在 ad-hoc 或者 查询不频繁的情况下使用 SQL + MySQL + NDB。
以快捷的速度访问 API, 这正是我们需要的,但同时我们也想在 ad_hoc 或者复杂的查询的情况时还是使用 SQL. 像其他的 web service, DeNA 使用的是 InnoDB, 转为 NDB,这并不是一件容易的事情,因为内置InnoDB 即不支持 SQL 也不支持网络层的服务。
HandlerSocket —— 一个采用 NoSQL 网络协议的MySQL插件
最好的办法可以是在 MySQL 的内部,实现一以 MySQL plugin 的形式存在的 NoSQL 的网络服务。它侦听在某端口来接收采用 NoSQL 协议/API 的通讯, 然后通过 MySQL 内部的存储引擎 API 来直接访问 InnoDB。这种方法的理念类似于 NDBAPI, 但是它可以做到与 InnoDB 通讯。
这个理念最初是去年由 Kazuho Oku 在 Cybozu Labs 上提出的,他曾写过采用 Memcached protocols 通讯的MyCached UDF。而我的大学同学实现了另外一个插件 -- HandlerSocket, 下面图描述了 HandlerSocket 具体做了哪些工作:

图1 What is Hanldersocket?
因为 HandlerSocket 是以 MySQL daemaon plugin 形式存在,所以在应用中可把 MySQL 当 NoSQL 使用. 它最大的功能是实现了与存储引擎交互,比如 InnoDB,而这不需要任何的 SQL 方面的开销. 访问 MySQL 的 table 时,当然她也是需要 open/close table 的,但是 它并不是每次都去 open/close table, 因为它会将以前访问过的 table 保存下来以供来是使用,而 opening/closing tables 是最耗资源的,而且很容易引起互斥量的争夺,这样一来,对于提高性能,非常有效。在流量变小时, HandlerSocket 会 close tables, 所以,它不会阻塞 administrative commands (DDL).
它与MySQL + Memcached 的区别在哪呢? 对比图1 和图2 ,可从中看出其不同点。图2 展示了典型的 MySQL + Memecached 的使用. 因为 Memcached 的 get 操作比 MySQL 的内存中/磁盘上的主键查询要快很多,所以 Memcached 用于缓存数据库记录。如果 HandlerSocket 的查询速度能和 Memcached 媲美,我们就不需要使用 Memcached 来缓存记录。

图2 Common architecture pattern for MySQL + memcached
使用 HandlerSocket
举一个例子,假设有一 user 表,通过 user_id 来获取用户信息:
[sql]
CREATE TABLE user
(
user_id INT UNSIGNED PRIMARY KEY,
user_name VARCHAR(50),
user_email VARCHAR(255),
created DATETIME
)
ENGINE=InnoDB;
[/sql]
用 SELECT 语句获取用户信息
[sql]
mysql> SELECT user_name, user_email, created FROM user WHERE user_id=101;
+---------------+-----------------------+---------------------+
| user_name | user_email | created
| +---------------+-----------------------+---------------------+
| Yukari Takeba | yukari.takeba@dena.jp | 2010-02-03 11:22:33
| +---------------+-----------------------+---------------------+
1 row in set (0.00 sec)
[/sql]
下面我们来看看如何使用 HandlerSocket 完成同样的事情.
安装 HandlerSocket
HandlerSocket具体安装步骤请参考这里,基本步骤如下:
- 1 下载HandlerSocket
- 2 编译 HandlerSocket(客户端和服务端)
- 3 安装 HandlerSocket
将如下内容添加至 MySQL 配置文件 my.cnf
[bash]
[mysqld]
loose_handlersocket_port = 9998
# the port number to bind to (for read requests)
loose_handlersocket_port_wr = 9999
# the port number to bind to (for write requests)
loose_handlersocket_threads = 16
# the number of worker threads (for read requests)
loose_handlersocket_threads_wr = 1
# the number of worker threads (for write requests)
open_files_limit = 65535
# to allow handlersocket accept many concurrent
# connections, make open_files_limit as large as
# possible.
[/bash]
以 root 身份登录 MySQL
[sql]
mysql> INSTALL PLUGIN 'handlersocket' soname 'handlersocket.so';
[/sql]
重启 MySQL 服务。
[bash]
[root@localhost handlersocket]# ./autogen.sh
[root@localhost handlersocket]# ./configure --with-mysql-source=mysql-source-dir --with-mysql-bindir=mysql-server-bin-dir
[root@localhost handlersocket]# make
[root@localhost handlersocket]# make install
[/bash]
因为 HandlerSocket是 MySQL 插件,所以可以象使用其它插件,如 InnoDB, Q4M 和 Spider 那样使用它,即不需要修改 MySQL 源代码,MySQL 最好是 5.1 或更高版本,编译 HandlerSocket 时需要 MySQL 源码和 MySQL bin 库。
书写 HandlerSocket 客户端代码
目前已提供 C++ 和 perl 调用的客户端库,下面是使用 perl 调用的实例代码:
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use Net::HandlerSocket;
#1. establishing a connection
my $args = { host => 'ip_to_remote_host', port => 9998 };
my $hs = new Net::HandlerSocket($args);
#2. initializing an index so that we can use in main logics.
# MySQL tables will be opened here (if not opened)
my $res = $hs->open_index(0, 'test', 'user', 'PRIMARY',
'user_name,user_email,created');
die $hs->get_error() if $res != 0;
#3. main logic
#fetching rows by id
#execute_single (index id, cond, cond value, max rows, offset)
$res = $hs->execute_single(0, '=', [ '101' ], 1, 0);
die $hs->get_error() if $res->[0] != 0;
shift(@$res);
for (my $row = 0; $row < 1; ++$row) {
my $user_name= $res->[$row + 0];
my $user_email= $res->[$row + 1];
my $created= $res->[$row + 2];
print "$user_name\t$user_email\t$created\n";
}
#4. closing the connection
$hs->close()
[/perl]
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use Net::HandlerSocket;
#1. establishing a connection
my $args = { host => 'ip_to_remote_host', port => 9998 };
my $hs = new Net::HandlerSocket($args);
#2. initializing an index so that we can use in main logics.
# MySQL tables will be opened here (if not opened)
my $res = $hs->open_index(0, 'test', 'user', 'PRIMARY',
'user_name,user_email,created');
die $hs->get_error() if $res != 0;
#3. main logic
#fetching rows by id
#execute_single (index id, cond, cond value, max rows, offset)
$res = $hs->execute_single(0, '=', [ '101' ], 1, 0);
die $hs->get_error() if $res->[0] != 0;
shift(@$res);
for (my $row = 0; $row < 1; ++$row) {
my $user_name= $res->[$row + 0];
my $user_email= $res->[$row + 1];
my $created= $res->[$row + 2];
print "$user_name\t$user_email\t$created\n";
}
#4. closing the connection
$hs->close();
[/perl]
上面代码是通过 user_id=101 条件在 user 表获取用户 user_name, user_email和 created 信息,得到的结果应该和之前在 MySQL client 查询出来的结果一样。
[bash]
[matsunobu@host ~]$ perl sample.pl
Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33
[/bash]
对于大多数Web应用程序而言,保持轻量级的 HandlerSocket 连接是一个很好的做法(持续连接),让大量的请求可以集中于主要逻辑(上面代码中的#3部分)。
HandlerSocket 协议是一个小尺寸的基于文本的协议,和 Memcached 文本协议类似,可以使用 telnet 通过 HandlerSocket 获取数据。
[bash]
[matsunobu@host ~]$ telnet 192.168.1.2 9998
Trying 192.168.1.2...
Connected to xxx.dena.jp (192.168.1.2).
Escape character is '^]'.
P 0 test user PRIMARY user_name,user_email,created
0 1
0 = 1 101
0 3 Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33
[/bash]
基准测试
现在是时候展示基准测试结果,使用上面的 user 表,从多线程远程客户端测试了执行主键查询操作的次数,所有用户数据都装入到内存中(我测试了 100 万行),也用类似的数据测试了 Memcached(我使用 libMemcached 和 Memcached_get() 获取用户数据),在 MySQL SQL 测试中,我使用了的是传统的 SELECT 语句: "SELECT user_name, user_email, created FROM user WHERE user_id=?", Memcached 和 HandlerSocket 客户端代码均使用 C/C++ 编写,所有客户端程序都位于远程主机上,通过 TCP/IP 连接到 MySQL/Memcached。最高的吞吐量情况如下:
approx qps server CPU util
MySQL via SQL 105,000 %us 60% %sy 28%
Memcached 420,000 %us 8% %sy 88%
MySQL via HandlerSocket 750,000 %us 45% %sy 53%
HandlerSocket的吞吐量比使用传统 SQL 时高出 7.5, 而且 %us 也只有使用传统 SQL 时的3/4, 这说明 MySQL 的 SQL 层是非常耗资源的,如果能跳过这一层,性能肯定会大大提升。有趣的是,MySQL 使用 HandlerSocket 时的速度比使用 Memcached 也要快 178%,并且 Memcached 消耗的 %sy 资源也更多。所以虽然 Memcached 是一个很好的产品,但仍然有优化的空间。
下面是oprofile输出内容,是在 MySQL HandlerSocket 测试期间收集到的,在核心操作,如网络数据包处理,获取数据等的 CPU 资源消耗(bnx2是一个网络设备驱动程序)。
samples % app name symbol name
984785 5.9118 bnx2 /bnx2
847486 5.0876 ha_innodb_plugin.so.0.0.0 ut_delay
545303 3.2735 ha_innodb_plugin.so.0.0.0 btr_search_guess_on_hash
317570 1.9064 ha_innodb_plugin.so.0.0.0 row_search_for_mysql
298271 1.7906 vmlinux tcp_ack
291739 1.7513 libc-2.5.so vfprintf
264704 1.5891 vmlinux .text.super_90_sync
248546 1.4921 vmlinux blk_recount_segments
244474 1.4676 libc-2.5.so _int_malloc
226738 1.3611 ha_innodb_plugin.so.0.0.0 _ZL14build_template P19row_prebuilt_structP3THDP8st_tablej
206057 1.2370 HandlerSocket.so dena::hstcpsvr_worker::run_one_ep()
183330 1.1006 ha_innodb_plugin.so.0.0.0 mutex_spin_wait
175738 1.0550 HandlerSocket.so dena::dbcontext:: cmd_find_internal(dena::dbcallback_i&, dena::prep_stmt const&, ha_rkey_function, dena::cmd_exec_args const&)
169967 1.0203 ha_innodb_plugin.so.0.0.0 buf_page_get_known_nowait
165337 0.9925 libc-2.5.so memcpy
149611 0.8981 ha_innodb_plugin.so.0.0.0 row_sel_store_mysql_rec
148967 0.8943 vmlinux generic_make_request
因为 HandlerSocket 是运行于 MySQL 内部,直接与 InnoDB 交互,所以,可以使用常见的 SQL 命令,如
SHOW GLOBAL STATUS 获得统计信息,Innodb_rows_read 达到了 750000+ 是值得一看的。[bash]
$ mysqladmin extended-status -uroot -i 1 -r | grep "InnoDB_rows_read"
...
| Innodb_rows_read | 750192 |
| Innodb_rows_read | 751510 |
| Innodb_rows_read | 757558 |
| Innodb_rows_read | 747060 |
| Innodb_rows_read | 748474 |
| Innodb_rows_read | 759344 |
| Innodb_rows_read | 753081 |
| Innodb_rows_read | 754375 |
...
[/bash]
测试用机的详细信息如下:
| 型号 | 戴尔PowerEdge R710 |
|---|---|
| CPU | Nehalem 8核,E5540@2.53GHz |
| 内存 | 32GB(所有数据都装入缓冲池) |
| MySQL | 5.1.50 InnoDB |
| Memcached/libMemcached | 1.4.5(Memcached),0.44(libMemcached) |
| Network | Boradcom NetXtreme II BCM5709 1000Base-T(内建四端口,使用了其中三个) |
Memcached 和 HandlerSocket 都做了网络 I/O 限制,当我测试单个端口时,HandlerSocket 的 QPS 为 260000,而 Memcached 为 220000。
HandlerSocket 的特点和优势
如下所述,HandlerSocket 有其自己的特点和优势,而其中一些对我们来说, 是真的很给力.
支持多种查询模式
HandlerSocket 目前支持 主键/唯一性查询,非唯一性索引查询,范围扫描,LIMIT 和 INSERT/UPDATE/DELETE,但还不支持未使用任何索引的操作。另外,
multi_get()(类似于in(1,2,3), 只需一次网络往返)还可获取多行数据。到这里可查询详细信息。处理大量并发连接
HandlerSocket 连接是轻量级的,因为 HandlerSocket 采用
epoll()和 worker-thread/thread-pooling 架构,而 MySQL 内部线程的数量是有限的(可以由 my.cnf中的 handlersocket_threads参数控制),所以即使建立上千万的网络连接到 HandlerSocket,它的稳定性也不会受到任何影响(消耗太多的内存,会造成巨大的互斥竞争等其他问题,如bug#26590,bug#33948,bug#49169)。及其优秀的性能
HandlerSocket,如上所描述, 相对于其它 NoSQL 阵容,性能表现一点也不逊色。事实上,我还未曾见过哪个 NoSQL 产品在一台普通服务器上可达到 750000+ 次查询。它不仅没有调用与 SQL 相关的函数,还优化了网络/并发相关的问题。
- 更小的网络数据包
和传统 MySQL 协议相比,HandlerSocket 协议更简短,因此整个网络的流量更小。 - 运行有限的 MySQL 内部线程数
参考上面的内容。 - 将客户端请求分组
当大量的并发请求抵达 HandlerSocket 时,每个工作线程尽可能多地聚集请求,然后同时执行聚集起来的请求和返回结果。这样,通过牺牲一点响应时间,而大大地提高性能。例如,你可以得到以下好处,如果有人感兴趣,我会在今后的文章中对它们加以深入的解释。
减少fsync()调用的次数.
减少复制延迟.
无重复缓存
当使用 Memcached 缓存 MySQL/InnoDB 记录时,在 Memcached 和 InnoD B缓冲池中均缓存了这些记录,因此效率非常低(内存仍然很贵). 而采用 HandlerSocket插件, 由于它访问 InnoDB 存储引擎,记录缓存在 InnoDB 缓冲池中,这样,其它 SQL 语句就可以重复使用它。
无数据不一致的现象
由于数据只存储在一个地方(InnoDB 内),不像使用 Memcached 时,需要在 Memcached 和 MySQL 之间检查数据一致性。
崩溃安全
后端存储是 InnoDB,它是事务性和崩溃安全的,即使有设置innodb-flush-log-at-trx-commit!=1,在服务器崩溃时也只会丢掉 < 1s 内的数据。
可从 MySQL 客户端使用 SQL
在许多情况下,人们仍然希望使用 SQL(如生产摘要报告),这就是为什么我们不能使用嵌入式 InnoDB 的原因,大多数 NoSQL 产品都不支持 SQL 接口,HandlerSocket 仅仅是一个 MySQL 插件,可以从 MySQL 客户端发送 SQL 语句,但当需要高吞吐量时,最好使用 HandlerSocket。
从 MySQL获益
因为 HandlerSocket 运行于 MySQL 内部,因此所有 MySQL 操作,如 SQL,在线备份,复制,通过 Nagios/EnterpriseMonitor 监控等都是支持的,HandlerSocket 获得可以通过普通的 MySQL 命令监控,如
SHOW GLOBAL STAUTS,SHOW ENGINE INNODB STATUS和SHOW PROCESSLIST等.不需要修改/重建 MySQL
因为 HandlerSocket 是一个插件,所以它支持 MySQL 社区版和企业服务器版,而无需对 MySQL 做出任何修改就可以使用。
独立于存储引擎
虽然我们只测试了5.1和5.5 InnoDB 插件,但 HandlerSocket 可以和任何存储引擎交互。
注意事项和限制
需要学习HandlerSocket API
尽管它很容易使用,但仍然需要学习如何与 HandlerSocket 交互,我们提供了C++ API、Perl绑定。
没有安全功能
和其它NoSQL数据库类似,HandlerSocket不支持安全功能,HandlerSocket的工作线程以系统用户权限运行,因此应用程序可以访问通过 HandlerSocket 协议的所有表,当然,你可以象其它 NoSQL 产品一样使用防火墙过滤数据包。
对于 HDD 绑定工作负载没有优势
对于 HDD I/O 绑定工作负载,数据库每秒无法执行数千次查询,通常只有 1-10% 的 CPU 利用率,在这种情况下,SQL 执行层不会成为瓶颈,因此使用HandlerSocket没有什么优势,我们只在数据完全装载到内存的服务器上使用 HandlerSocket。
DeNA 在生产环境中使用 HandlerSocket
我们已经在生产环境中使用了 HandlerSocket 插件,效果很明显,因为我们减少了许多 Memcached 和 MySQL 从属服务器,而且整个网络流量也在减少。目前还没有发现任何性能问题(如响应时间慢,延迟等)。
我认为, NoSQL/Database 社区完全低估了 MySQL, 相对于其他产品来说,它历史悠久,而且到目前为止,我优秀的前同事们也做了许多独特的、伟大的改进。从 NDBAPI 可以看出 MySQL 有成为 NoSQL 的潜力,因为存储引擎 API 和守护进程接口的完全独立,使得 Akira 和 DeNA 开发 HandlerSocket 成为可能。作为 MySQL 一名前员工和对 MySQL 长期的了解,我想看到 MySQL 变得更好,更受欢迎,而不仅仅只作为一个 RDBMS,也应该成为 NoSQL 阵营中的一员。
标签:
HandlerSocket
,
MySQL
,
NoSQL
2011年5月2日星期一
我希望...
我希望我们没有长大,永远只谈那些不着四六的事;
我希望哪怕分开了也会记得对方,只因为我们曾经拥抱过;
我希望时间不要走的那么快,不要那么匆匆;
我希望年轻的更久一点不要那么快变的苍老嘿我说的是心灵;
我希望情感驻留的瞬间可以覆盖这个世界的冷漠改变他坚硬的衬衣;
我希望我们的心里永远闪烁着真挚与炽热,有那份不在乎得失的情谊;
我希望我们有着最朴素的生活,最遥远的梦想;
我希望情感可以超越我们的身份,我们的收入,我们的阶层,甚至我们的立场;
我希望我们保持着相遇的初衷,直到生老病死,始终如一。
从 cutlife上辗转得知这段话,就如同该帖子所说,能有几人不被TA所打动?五一假归来杂乱的内心在这段话的安抚下,终于也找到了些许的平静。
我希望哪怕分开了也会记得对方,只因为我们曾经拥抱过;
我希望时间不要走的那么快,不要那么匆匆;
我希望年轻的更久一点不要那么快变的苍老嘿我说的是心灵;
我希望情感驻留的瞬间可以覆盖这个世界的冷漠改变他坚硬的衬衣;
我希望我们的心里永远闪烁着真挚与炽热,有那份不在乎得失的情谊;
我希望我们有着最朴素的生活,最遥远的梦想;
我希望情感可以超越我们的身份,我们的收入,我们的阶层,甚至我们的立场;
我希望我们保持着相遇的初衷,直到生老病死,始终如一。
从 cutlife上辗转得知这段话,就如同该帖子所说,能有几人不被TA所打动?五一假归来杂乱的内心在这段话的安抚下,终于也找到了些许的平静。
订阅:
博文
(
Atom
)