MySQL+HandlerSocket安装
HandlerSocket是啥东东,请参看http://www.ourlinux.net/database/using-mysql-as-a-nosql-a-story-for-exceeding-750000-qps-on-a-commodity-server/,做个简单的摘记:
下载
wget -c http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.51.tar.gz/from/http://ftp.jaist.ac.jp/pub/mysql/
wget -c http://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/tarball/master
安装Mysql
安装HandlerSocket
安装perl模块
perl测试脚本
1 Overview
最近一篇关于MySQL HandlerSocket的blog吸引了不少人的注意,甚至MySQL Performance Blog上也有关于HandlerSocket的性能评测。该blog中声称对于一个CPU bound而非IO bound的MySQL Server(即绝大部分的数据可以从缓存中取得,例如InnoDB Buffer Pool有接近100%的命中率),使用HandlerSocket可以将查询性能提升7.5倍。目前HandlerSocket已经被其作者应用到生产 环境,效果良好。笔者在昨天抽空安装并试用了一下HandlerSocket Plugin。
2 Installation
安装的过程基本顺利,不过可能是由于笔者是在MySQL 5.1.42+InnoDB Plugin 1.0.6上编译安装HandlerSocket的原因,编译HandlerSocket的过程中报如下错误:
configure: error: MySQL source version does not match MySQL binary version
调查一下后发现是configure脚本没有能够找到MySQL source的版本。笔者调整了configure脚本,跳过了该检查。编译安装后会在MySQL的plugin目录中生成一个so文件。然后在MySQL中安装该plugin即可。
- mysql> INSTALL PLUGIN handlersocket soname 'handlersocket.so';
- mysql> show plugins;
- +---------------------+----------+--------------------+---------------------+---------+
- | Name | Status | Type | Library | License |
- +---------------------+----------+--------------------+---------------------+---------+
- | handlersocket | ACTIVE | DAEMON | handlersocket.so | BSD |
- 18 rows in set (0.00 sec)
3 Getting Started
首先创建一个测试用的表。
- mysql> CREATE TABLE `user` (
- `user_id` int(10) unsigned NOT NULL,
- `user_name` varchar(50) DEFAULT NULL,
- `user_email` varchar(255) DEFAULT NULL,
- `created` datetime DEFAULT NULL,
- PRIMARY KEY (`user_id`),
- KEY `INDEX_01` (`user_name`)
- ) ENGINE=InnoDB
- mysql> insert into user values(1, "John", "[email protected]", CURRENT_TIMESTAMP);
- mysql> insert into user values(2, "Kevin", "[email protected]", CURRENT_TIMESTAMP);
- mysql> insert into user values(3, "Dino", "[email protected]", CURRENT_TIMESTAMP);
接下来按照blog中的例子,编写如下perl脚本handlersocket.pl。
- #!/usr/bin/perl
- use strict;
- use warnings;
- use Net::HandlerSocket;
- #1. establishing a connection
- my $args = { host => 'localhost', 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', 'INDEX_01', '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, '=', [ 'kevin' ], 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();
最后执行handlersocket.pl,结果如下:
- perl handlersocket.pl
- Kevin [email protected] 2010-11-14 22:35:22
4 Philosophy of the HandlerSocket
通常,MySQL Server可以被看成两层架构:即SQL Layer和Storage Engine Layer,它们之间通过Handler API进行交互。MySQL Server在接收到客户端的Query请求后,通常需要在SQL layer中进行词法分析,语法分析,优化等过程,最终生成一个树型的查询计划,交由执行引擎执行。执行引擎根据查询计划,跟相应的存储引擎通信,得到查 询结果。
HandlerSocket的作者认为,对于CPU bound的MySQL server来说,SQL layer消耗了过多的资源,以致总体性能不佳。HandlerSocket则绕过了MySQL Server的SQL layer,直接跟存储引擎交互,从而带来了大幅的性能提升。默认情况下HandlerSocket Plugin监听9998和9999两个端口,其中9998只支持读操作,9999支持读写操作,但是性能跟9998端口相比稍慢。
HandlerSocket的作者在其blog中列出了一系列HandlerSocket的优点,以下是笔者认为其中比较重要的:
- Can handle lots of concurrent connections
- Extremely high performance
- No duplicate cache
- No data inconsistency
- Independent from storage engines
- Supporting lots of query patterns
- No need to modify/rebuild MySQL
- All operational benefits from MySQL
其中No duplicate cache和No data inconsistency这两条,笔者感触颇深。关于MySQL的NoSQL扩展,可能很多项目都在使用memcached,或者自己实现的cache 等。这种独立缓存的实现方式有个重要的局限,即如何保证MySQL和cache之间的数据一致性,尽管Memcached Functions for MySQL(基于Trigger和MySQL UDF)从某种程度上提供了一种解决方案。
此外,关于Independent from storage engines和Supporting lots of query patterns。理论上通过Handler API可以和任何存储引擎交互,但是目前HandlerSocket只是在InnoDB Plugin上进行了测试。此外,HandlerSocket的作者在其blog上指出,通过HandlerSocket,不仅可以通过主键查询,也可以 通过普通索引,甚至不使用索引进行查询(包括范围查询),甚至还可以进行insert/update/delete等写操作。
关于All operational benefits from MySQL,正如在其blog中提到的,可以比较方便地通过MySQL的既存功能对HandlerSocket进行监控,例如以下几个监控指标:
- mysql> show global status like 'handler_read%';
- mysql> show global status like 'Com_select%';
- mysql> show global status like 'InnoDB_rows_read';
5 Client API
遗憾的是到目前为止,HandlerSocket只有C和Perl的客户端API,Java和Python等版本的客户端API貌似在开发中。在这里简单分析一下Perl版本的客户端API。
5.1 Connection
与HandlerSocket Plugin创建连接的方式如下:
- use Net::HandlerSocket;
- my $args = { host => 'localhost', port => 9998 };
- my $hs = new Net::HandlerSocket($args);
其中Net::HandlerSocket模块存放于HandlerSocket的分发tar包的perl-Net-HandlerSocket目录中,编译安装方式如下:
- cd perl-Net-HandlerSocket/
- perl Makefile.PL
- make
- make install
5.2 Opening index
在进行操作之前,首先需要打开一个索引,如下:
- my $err = $hs->open_index(3, 'database1', 'table1', 'PRIMARY', 'f1,f2');
- die $hs->get_error() if $res->[0] != 0;
其中'database1'为schema名,'table1'为表名,'PRIMARY'为索引名,'f1,f2'为查询的列名。关于方法的open_index的第一个参数3,用来在每个Net::HandlerSocket对象中唯一标识一个表名。
5.3 Query
通过execute_single方法进行查询,例如:
- my $res = $hs->execute_single(3, '=', [ 'foo' ], 1, 0);
- die $hs->get_error() if $res->[0] != 0;
- shift(@$res);
execute_single方法的第一个参数需要跟之前open_index方法的第一个参数一致。第二个参数'='指定了检索条件,目前支持'=', '>=', '<=', '>'和'<'。第三个参数[ 'foo' ]为一个arrayref,指定了检索的key,其长度必须小于或者等于对应索引的列数。第四个和第五个参数指定了查询的limit和offset。
execute_single方法的返回值类型为arrayref,其第一个元素为error code:
- 0:正常。
- 负数:I/O 错误,对应的Net::HandlerSocket对象需要被丢弃。
- 正数:其它错误,但是与HandlerSocket Plugin的连接仍然正常可用,因此对应的Net::HandlerSocket对象可以继续使用。
第一个元素之后的其它元素即查询结果,如果返回的row数大于1,那么也是存放在这个一维数组中。假设查询结果共5行,每行三列,那么对应的代码如下:
- die $hs->get_error() if $res->[0] != 0;
- shift(@$res);
- for (my $row = 0; $row < 5; ++$row) {
- for (my $col = 0; $col < 3; ++$col) {
- my $value = $res->[$row * 5 + $col];
- # ...
- }
- }
5.4 Insert records
execute_single方法也可以用来插入记录,例如:
- my $args = { host => 'localhost', port => 9999 };
- my $hs = new Net::HandlerSocket($args);
- my $res = $hs->execute_single(3, '+', [ 'foo', 'bar', 'baz' ]);
- die $hs->get_error() if $res->[0] != 0;
- my $num_inserted_rows = $res->[1];
需要注意的是,此时连接的是9999端口,因为9998端口只支持读操作。
5.5 Update or delete records
对于更新和删除操作,同样使用execute_single方法,例如:
- my $args = { host => 'localhost', port => 9999 };
- my $hs = new Net::HandlerSocket($args);
- my $res = $hs->execute_single(3, '=', [ 'bar' ], 1, 0, 'U', [ 'fubar', 'hoge' ]);
- die $hs->get_error() if $res->[0] != 0;
- my $num_updated_rows = $res->[1];
- my $res = $hs->execute_single(3, '=', [ 'baz' ], 1, 0, 'D');
- die $hs->get_error() if $res->[0] != 0;
- my $num_deleted_rows = $res->[1];
execute_single方法的第六个参数指定的操作类型,目前支持'U'和'D'。对于'U'操作,execute_single方法的第七个参数指定了更新后的值;对于'D'操作,第七个参数被忽略。
5.6 Multiple operations
可在一次调用中执行多个操作,这样速度更快,例如:
- my $rarr = $hs->execute_multi([
- [ 0, '>=', [ 'foo' ], 5, 0 ],
- [ 2, '=', [ 'bar' ], 1, 0 ],
- [ 4, '<', [ 'baz' ], 10, 5 ],
- ]);
- for my $res (@$rarr) {
- die $hs->get_error() if $res->[0] != 0;
- shift(@$res);
- # ...
- }
6 Reference
http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html (翻)
http://www.mysqlperformanceblog.com/2010/11/02/handlersocket-on-ssd/