首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

HandlerSocket装配测试

2012-06-27 
HandlerSocket安装测试目前使用MySQL的网站,多半同时使用Memcache作为键值缓存。虽然这样的架构极其流行,有

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源代码:

    • mysql-5.1.49.tar.gz
    • ahiguti-HandlerSocket-Plugin-for-MySQL-1.0.6-76-gf5f7443.tar.gz
      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分割,一行就是一个请求。本文用到了:

      • 打开索引: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); 
        ?

        /* * 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();    }}
        ?

        ?

        ?

        ?

        ?

        ?

        ?

热点排行