HandlerSocket安装测试
目前使用MySQL的网站,多半同时使用Memcache作为键值缓存。虽然这样的架构极其流行,有众多的案例,但过于依赖Memcache,无形中让Memcache成为故障的根源:
?
注:关于清理过期数据的问题,可以在程序架构上想办法,如果数据操作有统一DAO封装的话,可以利用Observer模式来清理过期数据,非主题内容,资料自查。
面对这些问题,HandlerSocket项目是个不错的解决方案,它通过插件的方式赋予MySQL完整的NoSQL功能,从原理上讲,它跳过MySQL中最耗时的语法解析,查询计划等步骤,直接读取数据,如果内存够大,能装下索引,MySQL的查询效率能提高若干倍!
性能测试:Using MySQL as a NoSQL – A story for exceeding 750,000 qps
因为HandlerSocket的性能足够好,所以就没有必要使用Memcache了,能节省大量的硬件资源,相当低碳!而且HandlerSocket操作的是MySQL放在内存中的索引,没有额外的缓存,所以自然就不存在数据一致性的问题。
?
?
?
首先要确保已经安装了MySQL5.1以上的版本,我用的是Ubuntu操作系统,事先已经用apt安装了MySQL5.1.49,同时还需要相应的mysql_config,如果是Ubuntu的话,可以:
shell> aptitude install libmysqld-dev
注:如果你用的MySQL是从源代码编译的或官方提供的二进制版本,可以略过此步。
?
接着下载一份和系统MySQL版本一致的MySQL源代码和HandlerSocket源代码:
shell> tar zxf mysql-5.1.49.tar.gzshell> tar zxf ahiguti-HandlerSocket-Plugin-for-MySQL-1.0.6-76-gf5f7443.tar.gzshell> cd ahiguti-HandlerSocket-Plugin-for-MySQL-f5f7443shell> ./autogen.shshell> ./configure --with-mysql-source=../mysql-5.1.49 \ --with-mysql-bindir=/usr/bin \ --with-mysql-plugindir=/usr/lib/mysql/plugin
其中的参数含义如下:with-mysql-source表示MySQL源代码目录,with-mysql-bindir表示MySQL二进制可执行文件目录(也就是mysql_config所在目录, 用whereis mysql_config可查得),with-mysql-plugindir表示MySQL插件目录,如果不清楚这个目录在哪,可以按如下方法查询:
mysql> SHOW VARIABLES LIKE 'plugin%';+---------------+-----------------------+| Variable_name | Value |+---------------+-----------------------+| plugin_dir | /usr/lib/mysql/plugin |+---------------+-----------------------+
运行命令后,如果你使用的是MySQL5.1.37版本的话,会遇到如下错误信息:
MySQL source version does not match MySQL binary version
明明我们的MySQL源代码版本和二进制版本都是5.1.37,为什么还会出现这个错误呢?通过查询HandlerSocket的编译脚本,发现原来它会检索MySQL源代码目录中的VERSION文件,可MySQL5.1.37的源代码目录里不知何故竟然没有这个文件,所以就报错了,既然知道了原因,那我们就照猫画虎做一个VERSION文件放到MySQL源代码目录,内容如下:
MYSQL_VERSION_MAJOR=5MYSQL_VERSION_MINOR=1MYSQL_VERSION_PATCH=37MYSQL_VERSION_EXTRA=
再次运行configure脚本,应该就OK了,把剩下的步骤进行完:
shell> makeshell> make install
接着需要配置一下HandlerSocket,编辑MySQL配置文件,加入如下内容:
[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.
此外,InnoDB的innodb_buffer_pool_size,或MyISAM的key_buffy_size等关系到缓存索引的选项尽可能设置大一些,这样才能发挥HandlerSocket的潜力。
注:apt包管理下的配置文件一般是/etc/mysql/my.cnf,否则一般是/etc/my.cnf
最后登陆MySQL并激活HandlerSocket插件:
mysql> INSTALL PLUGIN handlersocket soname 'handlersocket.so';
重启一下MySQL服务,如果没有问题,就能在MySQL里看到HandlerSocket的线程了:
mysql> SHOW PROCESSLIST;
也可以通过查询刚配置的端口是否已经被MySQL占用来确认是否安装成功:
shell> lsof -i :9998shell> lsof -i :9999
完活儿!现在你的MySQL已经具备NoSQL的能力了!
?
首先创建一个测试用的表:
CREATE TABLE IF NOT EXISTS `test`.`t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `a` varchar(10) NOT NULL, `b` varchar(10) NOT NULL, PRIMARY KEY (`id`), KEY `a_b` (`a`,`b`)) ENGINE=InnoDB;
注:理论上HandlerSocket支持MyISAM,InnoDB等各种引擎,不过推荐使用InnoDB。
HandlerSocket的协议非常简单,指令通过TAB分割,一行就是一个请求。本文用到了:
?
API:
谷歌code(http://code.google.com/p/php-handlersocket/)中提供了PHP扩展作者的API,这里我将每个方法的参数具体说明一下(也可以去https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/perl-client.en.txt参考一下perl扩展的API说明,其实实现都是一样的,只不过是不同语言):
实例化:
/* * String $host:MySQL ip; * String $port:handlersocket插件的监听端口,它有两个端口可选:一个用于读、一个用于写 */ $hs = new HandlerSocket($host, $port);?
打开一个数据表:
/* * Int $index:这个数字相当于文件操作里的句柄,HandlerSocket的所有其他方法都会依据这个数字来操作由这个 openIndex打开的表, * String $dbname:库名 * String $table:表名 * String $key:表的“主键”(HandlerSocket::PRIMARY)或“索引名”作为搜索关键字段,这就是说表必须有主键或索引 * 个人理解:要被当做where条件的key字段,这样可以认为handlersocket只有一个where条件 * String $column:'column1,column2' 所打开表的字段(以逗号隔开),就是说$table表的其他字段不会被操作 */ $hs->openIndex($index, $dbname, $table, $key, $column);
??????
?????? 查询:
/* * Int $index: openIndex()所用的$index * String $operation:openIndex方法中指定的$key字段所用的操作符,目前支持'=', '>=', '<=', '>',and '<';可以理解为where条件 * Array $value * Int $number(默认是1):获取结果的最大条数;相当于SQL中limit的第二个参数 * Int $skip(默认是0):跳过去几条;相当于SQL中limit的第一个参数 */ $retval = $hs->executeSingle($index, $operation, $value, $number, $skip);?
???? 插入(注意:此处的openIndex要用$port_wr,即读写端口) :
?
/* * Int $index: openIndex()所用的$index * Array $arr:数字元素数与openIndex的$column相同 */ $retval = $hs->executeInsert($index, $arr);?
/* * Int $index: openIndex()所用的$index * String $operation:openIndex方法中指定的$key字段所用的操作符,目前支持'=', '>=', '<=', '>',and '<';可以理解为where条件 * Array $value * Int $number(默认是1):获取结果的最大条数;相当于SQL中limit的第二个参数 * Int $skip(默认是0):跳过去几条;相当于SQL中limit的第一个参数 */ $retval = $hs->executeDelete($index, $operation, $value, $number, $skip); ?
更新(注意:此处的openIndex要用$port_wr,即读写端口):
/* * Int $index: openIndex()所用的$index * String $operation:openIndex方法中指定的$key字段所用的操作符,目前支持'=', '>=', '<=', '>',and '<';可以理解为where条件 * Array $value * Int $number(默认是1):获取结果的最大条数;相当于SQL中limit的第二个参数 * Int $skip(默认是0):跳过去几条;相当于SQL中limit的第一个参数 */ $retval = $hs->executeUpdate($index, $operation, $value, $number, $skip);?
测试:
新建一个1000w条数据的用户表,id为主键,包括uname、email、add_time字段,使用两台不同的机器做ab压力测试:
读测试:
并发50,5000次压力测试:
MySQL:??????? min: 0.504740953445??? max:13.1727859974??? average: 1.05??? CPU:0.7%us,? 0.3%sy??? use:111s
HandlerSocket:min: 0.302443981171??? max:9.37712621689??? average:0.736???? CPU:0.4%us,? 0.3%sy??? use:77s
并发70,5000次压力测试:
MySQL:?????????? min: 0.504750013351??? max:10.4482009411??? average: 1.094?? CPU:0.9%us,? 0.4%sy??? use:85s
HandlerSocket:min: 0.302488803864??? max:10.3345310688??? average: 0.788?? CPU:0.5%us,? 0.4%sy??? use:62s
并发110,5000次压力测试:
MySQL:?????????? min:0.505280017853??? max:21.3242678642??? average:1.095?? CPU:1.5%us,? 0.7%sy??? use:55s
HandlerSocket:min: 0.30281996727??? max:10.6022770405??? average:0.786?? CPU:1.1%us,? 0.7%sy??? use:39s
并发150,5000次压力测试:
MySQL:?????????? min: 0.505041122437??? max:28.8087069988??? average:1.073?? CPU:1.8%us,? 0.9%sy??? use:61s
HandlerSocket:min: 0.302739143372??? max:12.878344059??? average:0.774?? CPU:1.0%us,? 0.9%sy??? use:30s
总结:
共同点:并发越高,性能越好
hs系统占用和执行时间都少于MySQL 性能约好30%~40%
写测试:
并发50,5000次压力测试:
MySQL:?????????? min: 0.507106781006 max: 4.95259904861 average: 0.594?? CPU:0.76%us, 0.49%sy??? use:62s
HandlerSocket:min: 0.303457021713 max: 7.0854101181? average: 0.383?? CPU:0.4%us,? 0.2%sy???? use:43s
并发70,5000次压力测试:
MySQL:??????? min: 0.508066892624 max: 12.8451189995 average: 0.659?? CPU:1.0%us,? 0.6%sy???? use:51s
HandlerSocket:min: 0.30427312851? max: 12.4244120121 average: 0.417?? CPU:0.53%us, 0.29%sy??? use:32s
并发90,5000次压力测试:
MySQL:??????? min: 0.507676839828 max: 12.8466610909 average: 0.689?? CPU:1.3%us,? 0.72%sy??? use:45s
HandlerSocket:min: 0.304312229156 max: 12.4680581093 average: 0.465?? CPU:0.66%us, 0.38%sy??? use:29s
并发110,5000次压力测试:
MySQL:??????? min: 0.507092952728 max: 11.7785778046 average: 0.775?? CPU:1.34%us, 0.82%sy??? use:45s (13条未写入)
HandlerSocket:min: 0.219769954681 max: 12.6269509792 average: 0.556?? CPU:0.63%us, 0.37%sy??? use:32s (15条未写入)
并发150,5000次压力测试:
MySQL:??????? min: 0.507570981979 max: 13.4538660049 average: 0.75??? CPU:1.9%us,? 1.1%sy???? use:29s (写多1条)
HandlerSocket:min: 0.304651975632 max: 16.3402500153 average: 0.555?? CPU:0.7%us,? 0.43%sy??? use:26s (8条未写入)
总结:
共同点:并发越高,性能越好
hs系统占用和执行时间都少于MySQL 性能约好50%~60%
测试结果确实比较明显,HandlerSocket可以在高并发、简单表操作的环境下替代MySQL。
?
原文链接:http://www.cnblogs.com/yangligogogo/articles/1969823.html
http://database.51cto.com/art/201105/261741.htm
?
最后给大家推荐两篇文章:
1、google的php说明文档:http://code.google.com/p/php-handlersocket/wiki/Classes
2、一篇很不错的学习笔记:http://www.livingelsewhere.net/2011/06/02/php-extension-for-interfacing-with-mysql-handler-socket
最后发一段粗糙的类,眼前用而已:
<?php/** * 本类是对HandlerSocket的一个简单封装。 * 有三个常量:DB_HOST: 服务器ip; DB_HS_PORT:只读端口; DB_HS_WRPORT:读写端口 * * $db = new HSMysql('topic'); //设置表明 * * #插入操作 * $row = array('f_uid' => 12, 'f_uname' => 'too'); * $db->insert($row); * * #读取操作 * #取出的列、索引名、条件关系符、条件对应的数据(与索引顺序一致 )、limit $skip, $number * $db->query($columns, $index, '=', $fields, $number=1, $skip=0); * * #更新操作 * $data = array('f_uid' => 13, 'f_uname' => 'tooo'); * #更新数据、索引、条件关系符、条件对应的数据(与索引顺序一致 )、limit $skip, $number * $db->update($data, $index, $operation, $fields, $number=1, $skip=0); * * #删除操作 * #索引、条件关系符、条件对应的数据(与索引顺序一致 )、limit $skip, $number * $db->delete($index, $operation, $fields, $number=1, $skip=0) * * @author tuyl * */ class HSMysql{ protected static $_db, $_rdb; protected $_table; public function __construct($t_name){ $this->setTable($t_name); } public function setTable($t_name){ $this->_table = DB_PREFIX.$t_name; } public function getInstance($readonly = 1){ if($readonly){ if(!self::$_rdb){ self::$_rdb = new HandlerSocket(DB_HOST, DB_HS_PORT); } return self::$_rdb; }else{ if(!self::$_db){ self::$_db = new HandlerSocket(DB_HOST, DB_HS_WRPORT); } return self::$_db; } } public function openIndex($id, $index = '', $columns = array(), $readonly = 0){ $cls = implode(',', $columns); $db = $this->getInstance($readonly); if (!$db->openIndex($id, DB_NAME, $this->_table, $index, $cls)){ $this->_error('open index :'.$hs->getError()); } return $db; } public function query($columns, $index, $operation, $fields, $number=1, $skip=0){ $hs = $this->openIndex(1, $index, $columns, 1); $res = $hs->executeSingle(1, $operation, $fields, $number, $skip); if($res === false){ $this->_error('query error:'.$hs->getError()); } return $res; } public function insert($rows){ $hs = $this->openIndex(3, HandlerSocket::PRIMARY, array_keys($rows)); $f = $hs->executeInsert(3, array_values($rows)); if($f === false){ $this->_error('executeInsert :'.$hs->getError()); } return $f; } public function update($data, $index, $operation, $fields, $number=1, $skip=0){ $hs = $this->openIndex(2, $index, array_keys($data)); return $hs->executeUpdate(2, $operation, $fields, array_values($data), $number, $skip); } public function delete($index, $operation, $fields, $number=1, $skip=0){ $hs = $this->openIndex(4, $index, ''); return $hs->executeDelete(4, $operation, $fields, $number, $skip); } public function asc($data, $index){ return $this->_sort($data, $index, SORT_ASC); } public function desc($data, $index){ return $this->_sort($data, $index, SORT_DESC); } private function _sort($data, $index, $sort){ if(!$data || !is_array($data)) return $data; $args = array(); foreach ($data as $k => $v) { $args[] = $v[$index]; } array_multisort($args, $sort, $data); return $data; } private function _error($msg){ echo $msg; exit(); }}??
?
?
?
?
?