HandlerSocket安装测试

目前使用MySQL的网站,多半同时使用Memcache作为键值缓存。虽然这样的架构极其流行,有众多的案例,但过于依赖Memcache,无形中让Memcache成为故障的根源:

 

  • Memcache数据一致性的问题:当MySQL数据变化后,如果不能及时有效的清理掉过期的数据,就会造成数据不一致。这在强调即时性的Web2.0时代,不可取。
  • Memcache崩溃后的雪崩效应:作为缓存的Memcache一旦崩溃,MySQL很可能在短时间内承受高负载而宕机。据说前段时间新浪微博就遭遇了这样的问题。

注:关于清理过期数据的问题,可以在程序架构上想办法,如果数据操作有统一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.gz
shell> tar zxf ahiguti-HandlerSocket-Plugin-for-MySQL-1.0.6-76-gf5f7443.tar.gz
shell> cd ahiguti-HandlerSocket-Plugin-for-MySQL-f5f7443
shell> ./autogen.sh
shell> ./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=5
MYSQL_VERSION_MINOR=1
MYSQL_VERSION_PATCH=37
MYSQL_VERSION_EXTRA=

再次运行configure脚本,应该就OK了,把剩下的步骤进行完:

shell> make
shell> 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 :9998
shell> 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分割,一行就是一个请求。本文用到了:

  • 打开索引:P <索引标识> <数据库> <表> <索引> <字段>
  • 插入数据:<索引标识> ‘+’ <参数个数> <参数1> … <参数N>
  • 读取数据:<索引标识> <操作> <参数个数> <参数1> … <参数N> <条数> <偏移>

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); 
 

删除(注意:此处的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->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();
    }
}
 

 

猜你喜欢

转载自ieric.iteye.com/blog/1397726