mysql handlersocket

11
MySQL HandlerSocket 顾春江 2011 4 年, 月

Upload: pwesh

Post on 21-Jan-2015

2.076 views

Category:

Technology


7 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Mysql handlersocket

MySQL HandlerSocket

顾春江

2011 4年, 月

Page 2: Mysql handlersocket

目 录

概要..........................................................................................................................3性能瓶颈................................................................................................................4HandlerSocket结构...................................................................................................5使用了HandlerSocket之后的性能................................................................................7特点.......................................................................................................................8

HS支持Handler语句风格......................................................................................8HS内置线程池.....................................................................................................8性能非常高效......................................................................................................8不再有重复的 Cache,数据保持一致........................................................................9HS不影响正常数据库功能的使用............................................................................9HS以插件的形式存在...........................................................................................9

限制.......................................................................................................................9没有安全性.........................................................................................................9对于磁盘 IO向的场景没有效果...............................................................................9性能瓶颈从 CPU向转到网卡向...............................................................................9加重 slave的复制压力...........................................................................................9

安装和测试过程.......................................................................................................9

Page 3: Mysql handlersocket

概要

关于 SQL和NoSQL的比较已经有很多了,在开源界一直以来MySQL都是通用存储的角色,搭配若干其他高性能 key/value或者是 graph类型的数据存储方案。下面是一个web公司存储技术发展的典型例子:

1: PHP + MySQL

2: PHP + MySQL (Master + Slaves)

3: PHP + MySQL (Master + Slaves) + Memcached (Middleware)

4: PHP + MySQL (Sharding + Master + Slaves) + Memcached (Middleware)

5: PHP + MySQL (Sharding + Master + Slaves) + Memcached (Middleware) + NoSQL

会走用NoSQL这条路的,都是不满足于MySQL对于简单数据的操作的效率。但是在很多情况下,MySQL会在这些操作上慢的原因是由于低效的 SQL /语句引起的,或者说臃肿 分散的表组织引起的(比如

表文件已经很稀疏,或者索引不合理,太庞大)。最近MySQL的HandlerSocket的出现,打破了MySQL对于简单数据操作慢的传统。这要从MySQL :的内部结构说起,先看下图

这个图是MySQL的整个结构,客户端连接到达MySQL后,需要经过很多模块才能真正返回数据。

首先是第一层, mysqld 层,也就是主服务器层,它包含四个模块: SQL接口模块(判断 SQL类型),Parser模块(解析器,解析 SQL语法树),Optimizer模块(优化器,语法树收敛),Cache&Buffer模块

(缓冲层,包含主服务器缓冲,也可接受底层引擎的内存申请请求)。

Illustration 1: MySQL 内部结构

Page 4: Mysql handlersocket

然后是第二层,存储插件层,这一层包含了所有的可用存储引擎,像著名的MyISAM和 InnoDB,都是在这一层,它们会接到来自主服务器层的 io请求,然后负责调度对底层文件系统的 io请求。

最后是第三层,那就是文件系统了,这一层也有可能会包含操作系统的 cache,取决于引擎读写磁盘的方式。

我们要解决的问题是找到这几层之间会影响性能的热点。

性能瓶颈

传统数据库的追求通用性和缺乏高速的简单操作接口,导致了NoSQL的出现。NoSQL的应用场景主要是:适量的热点数据,快速响应。对于MySQL来说,即便是使用memory table,还是无法去除主服务器层的 SQL解析操作,SQL解析在设备 IO占主导时,的确不是什么问题,但是在纯内存数据场景中,SQL解析所占的CPU时间不再无足轻重。我们可以做一个例子,纯内存场景,客户端使用 SQL接口,等值查询PK(借鉴作者的做法,使用一个简单的带 INT主键的 InnoDB 50表,随机产生 million行的数据)。

原始状态下的 select :能力

$mysqlslap --query="select user_name from test.user where user_id=1000" \ --number-of-queries=10000000 --concurrency=30 --host=mysql15 -uxxx -p

$mysqladmin extended-status -i 1 -r -uroot | grep -e "Com_select"

| Com_select | 86234 || Com_select | 82345 || Com_select | 85972 || Com_select | 84270 || Com_select | 84281 || Com_select | 83951 || Com_select | 85317 |

以上数据显示,通过正常的 SQL接口,MySQL 84可以达到 k/s的读取次数。这里就暂不比较Memcache的读取能力了,一般Memcache在MySQL的四五倍以上。

另外同时查看 vmstat :的数据

$ vmstat 1 r b swpd free buff cache in cs us sy id wa st18 0 4 1276402 184176 17348613 68371 212346 55 36 9 0 016 0 4 1276402 184176 17348613 68610 213710 53 34 13 0 021 0 4 1276402 184176 17348613 69137 218767 56 33 11 0 017 0 4 1276402 184176 17348613 67392 209756 54 31 15 0 018 0 4 1276402 184176 17348613 68437 213386 54 35 11 0 0

从 vmstat的结果来看,user时间超过 sys时间不少,这个结果大概能说明,锁并非是占用CPU时间最多的(MySQL在 kernel space执行mutex),相反 user时间占了一半多的比例,那接下来我们用 oprofile来看看MySQL到底在 user space做了些什么。(这边采用原作者的测试案例,大致情景相似,我们的服务器上暂不方便测试)

samples % app name symbol name259130 4.5199 mysqld MYSQLparse(void*)196841 3.4334 mysqld my_pthread_fastmutex_lock106439 1.8566 libc-2.5.so _int_malloc94583 1.6498 bnx2 /bnx284550 1.4748 ha_innodb_plugin.so.0.0.0 ut_delay67945 1.1851 mysqld _ZL20make_join_statisticsP4JOINP10TABLE_LISTP4ItemP16st_dynamic_array63435 1.1065 mysqld JOIN::optimize()55825 0.9737 vmlinux wakeup_stack_begin55054 0.9603 mysqld MYSQLlex(void*, void*)50833 0.8867 libpthread-2.5.so pthread_mutex_trylock49602 0.8652 ha_innodb_plugin.so.0.0.0 row_search_for_mysql47518 0.8288 libc-2.5.so memcpy

Page 5: Mysql handlersocket

46957 0.8190 vmlinux .text.elf_core_dump46499 0.8111 libc-2.5.so malloc

可以看到,MYSQLparse()占用了最多的CPU时间,另外make_join和 JOIN::optimize()和MYSQLlex()也榜上有名。这些都是mysqld的 SQL解析功能,如果能跳过这些步骤直达数据层,那么user space的效率还能提升很多,而且这种读取方式将和NoSQL非常类似。(my_pthread_fastmutex_lock数值很高是mysqld在做 SQL / /解析时,需要不停地打开 关闭 锁定表对象造

成的,如果跳过 SQL解析,此部分也会随之下降)

接下来,就要想办法如果降低或跳过MySQL的 SQL解析操作。一般的客户端要取得数据,需要经过如:下几个步骤

1. SQL Parser解析 SQL语句

2. 打开表对象句柄,申请mutex

3. 根据表信息生成 SQL执行计划

4. IO读写

5. 释放mutex,关闭表对象句柄

5这 个步骤中,只有第四步是 IO向的,其余都是CPU向,如果 InnoDB的一个表的数据都能被缓存到内存(NoSQL 4场景),那么步骤 不再重要,而剩下的四个步骤将成为主角,如何削减这些步骤显得额外重要。

:解决的办法,目前大概有如下

MySQL有一个HANDLER1语法,和一般的 SQL语句一样,它也由 SQL Parser 1来解析(步骤 没法省

3略),但是由于这个语法是直接指定索引来进行遍历的,那么就可以省去步骤 ,因此在NoSQL场景中,1,2,5这个语句能够做到只要 ,不过还有提升的空间。

MySQL Cluster有个底层的API叫NDBAPI2,这个API 1,3可以完全跳过步骤 ,直接根据需要去操作

表。根据HS的作者讲,这种访问方式的效率是标准 SQL接口的数倍。这样的访问方式看起来非常符合NoSQL 2,5的应用场景,只需要步骤 就可。

HandlerSocket结构

HandlerSocket 2正是根据上述 种思想,然后通过MySQL Internal Storage Engine API3绕开mysqld的SQL解析器,直接访问存储层。下面是HS的结构图:

1 http://dev.mysql.com/doc/refman/5.5/en/handler.html 2 http://dev.mysql.com/doc/ndbapi/en/index.html 3 http://forge.mysql.com/wiki/MySQL_Internals_Custom_Engine

Page 6: Mysql handlersocket

HandlerSocket以MySQL 2插件的形式启动,启动后监听 个特殊端口,分别接受读请求和写请求,并且

fork出可配置数量的服务线程。这些服务线程将轮番处理客户端请求,并对多次分散的请求做合并处理。HS /不会不停地打开 关闭表对象句柄,它在打开后会保持一段时间,如果在一段时间内没有请求才会关闭,

以方便句柄重用。HS自己实现了一套基于文本的通信协议(类似Memcache的协议,很简洁,不用构建语法树也不需要优化),支持 telnet的调试。

下图是MySQL搭配NoSQL的场景:

Page 7: Mysql handlersocket

这个场景和HS的场景作比较的话,可以发现HS已经实现了Memcached的基本功能,而且不存在数据变质的问题。

使用了 HandlerSocket之后的性能

在mySQL上创建了一个简单的带主键的 InnoDB 5,000,000表,插入随机数据 条,然后用客户端分别通

过 SQL接口和HS接口分别并行访问相同的数据,然后观察MySQL的实时 select能力。(测试通过 perl客户端做的,c++客户端有点问题还没法测)

$ mysqladmin extended-status -uxxx -p -i 1 -r | grep "InnoDB_rows_read"...| Innodb_rows_read | 328512 || Innodb_rows_read | 340128 || Innodb_rows_read | 312134 || Innodb_rows_read | 338072 || Innodb_rows_read | 342387 || Innodb_rows_read | 399023 || Innodb_rows_read | 312494 || Innodb_rows_read | 353918 |

这次的 qps 330平均在 k/s, 84和原来的 k/s 400%的提升幅度是相当大的,有 左右的提升。等 c++客户端好了可以再和Memcached放在一起做个对比。

同时我们可以再看看这次原来占用很多CPU :时间的线程是如何表现的 (再次借用作者的数据)

samples % app name symbol name984785 5.9118 bnx2 /bnx2847486 5.0876 ha_innodb_plugin.so.0.0.0 ut_delay545303 3.2735 ha_innodb_plugin.so.0.0.0 btr_search_guess_on_hash317570 1.9064 ha_innodb_plugin.so.0.0.0 row_search_for_mysql298271 1.7906 vmlinux tcp_ack291739 1.7513 libc-2.5.so vfprintf264704 1.5891 vmlinux .text.super_90_sync248546 1.4921 vmlinux blk_recount_segments244474 1.4676 libc-2.5.so _int_malloc226738 1.3611 ha_innodb_plugin.so.0.0.0 _ZL14build_templateP19row_prebuilt_structP3THDP8st_tablej206057 1.2370 HandlerSocket.so dena::hstcpsvr_worker::run_one_ep()183330 1.1006 ha_innodb_plugin.so.0.0.0 mutex_spin_wait175738 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_nowait165337 0.9925 libc-2.5.so memcpy149611 0.8981 ha_innodb_plugin.so.0.0.0 row_sel_store_mysql_rec148967 0.8943 vmlinux generic_make_request

这次,原来很多镜像名是mysqld的线程已经不在列表上了,SQL Parser相关的线程已经全部消失,倒是有了很多 InnoDB相关的线程上来了,说明优化已经奏效。那个镜像名是 bnx2的线程是网络设备驱动,也说明性能瓶颈开始从CPU转向网卡了。

:测试环境

• mysql15

• 8 core at 2.93GHZ

• 32G( )所有数据都缓冲在内存里

Page 8: Mysql handlersocket

特点

HS支持 Handler语句风格

除了不支持非索引列的遍历,HS基本支持Handler语句的所有写法。

HS内置线程池

内部采用了基于 epoll()的线程池,数量限制可在my.cnf里配置。

以下是一个新启动的MySQL实例,HS 17已开启,默认根据配置文件,已经有了 个服务线程:

mysql>SHOW PROCESSLIST;+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+| Id | User | Host | db | Command | Time | State | Info |+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+| 1 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 2 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 3 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 4 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 5 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 6 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 7 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 8 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 9 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 10 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 11 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 12 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 13 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 14 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 15 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 16 | system user | connecting host | NULL | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL || 17 | system user | connecting host | handlersocket | Connect | NULL | handlersocket: mode=wr, 0 conns, 0 active | NULL || 18 | root | localhost | NULL | Query | 0 | NULL | SHOW PROCESSLIST |+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+

性能非常高效

• 因为没有像安全验证、性能检测等附属功能,HS的网络协议包非常简洁。

Page 9: Mysql handlersocket

• 服务线程可控,能尽可能避免mutex过量。

• 合并客户端请求,重用表对象句柄,提高吞吐率。

• 能够减少 fsync()的次数。

不再有重复的 Cache,数据保持一致

有这样的效率,Memcached完全可以省了,也没有数据变质的烦恼,另外MySQL还有失败从 binlog恢复的功能,Memcached没有。

HS不影响正常数据库功能的使用

Hs使用的是特殊端口,正常的 SQL端口仍然可用。所有的HS操作都是合法的数据库底层操作,因此mysqld的性能统计,binlog replication, 都不影响。

HS以插件的形式存在

插件的好处是不用重新编译MySQL。

HS使用MySQL Internal Storage Engine API,因此是在mysqld层的服务,和底层引擎类型无关。

限制

没有安全性

Hs的网络协议还非常简单,没有安全性可言。而且HS的服务线程以系统用户的权限运行,所以可以访问任何表的任何数据。

对于磁盘 IO向的场景没有效果

对于这种场景,CPU会消耗大量时间在wait状态,HS的CPU优化意义不大。

性能瓶颈从 CPU向转到网卡向

在CPU方面做好优化了之后,网卡的处理能力成为了性能瓶颈。

加重 slave的复制压力

如果通过HS进行高速的数据修改,而 slave仍然使用传统的基于语句的复制,那么 slave有可能会跟不上,毕竟只有单线程在做 SQL接口的复制,复制协议有必要加上HS接口。

安装和测试过程

# Install HandlerSockettar xvfz ahiguti-HandlerSocket-Plugin-for-MySQL.tar.gzcd ahiguti-HandlerSocket-Plugin-for-MySQL/./autogen.sh./configure --with-mysql-source=${DIR}/mysql --with-mysql-bindir=${DIR}/mysql/binmakesudo make install

Page 10: Mysql handlersocket

# Install the Perl dependencycd perl-Net-HandlerSocketperl Makefile.PLWriting Makefile for Net::HandlerSocketmakesudo make install

${DIR}/mysql/bin/mysql -urootmysql> INSTALL PLUGIN HandlerSocket SONAME 'handlersocket.so';mysql> SHOW PLUGINS;+---------------+----------+----------------+------------------+---------+| Name | Status | Type | Library | License |+---------------+----------+----------------+------------------+---------+| binlog | ACTIVE | STORAGE ENGINE | NULL | GPL || partition | ACTIVE | STORAGE ENGINE | NULL | GPL || ARCHIVE | ACTIVE | STORAGE ENGINE | NULL | GPL || BLACKHOLE | ACTIVE | STORAGE ENGINE | NULL | GPL || CSV | ACTIVE | STORAGE ENGINE | NULL | GPL || FEDERATED | DISABLED | STORAGE ENGINE | NULL | GPL || MEMORY | ACTIVE | STORAGE ENGINE | NULL | GPL || InnoDB | ACTIVE | STORAGE ENGINE | NULL | GPL || MyISAM | ACTIVE | STORAGE ENGINE | NULL | GPL || MRG_MYISAM | ACTIVE | STORAGE ENGINE | NULL | GPL || handlersocket | ACTIVE | DAEMON | handlersocket.so | BSD |+---------------+----------+----------------+------------------+---------+11 rows in set (0.00 sec)

# set my.cnf parameterscd ${DIR}/mysqlecho "[mysqld]plugin-load=handlersocket.soloose_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)" >> my.cnf

# test tableCREATE TABLE user ( user_id INT UNSIGNED NOT NULL, name VARCHAR(50) NOT NULL, email VARCHAR(255) NOT NULL, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(user_id)) ENGINE=InnoDB;

# populate dataINSERT INTO user (user_id,name, email) VALUES(1,'name test','mail test'),...(5000000,'name test','mail test');

# perl test script$ cat retrieve.pl#!/usr/bin/perl

use strict;use warnings;use Net::HandlerSocket;

#1. establishing a connectionmy $args = { host => '172.29.1.115', port => 9998 };

Page 11: Mysql handlersocket

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', 'name,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)for (my $i = 0; $i < 10000000; ++$i) { $res = $hs->execute_single(0, '=', [ '100' ], 1, 0);}

#4. closing the connection$hs->close();

# telnet testing$ telnet 172.29.1.115 9998Trying 172.29.1.115...Connected to mysql15 (172.29.1.115).Escape character is '^]'.P 0 test user PRIMARY user_name,user_email,created0 10 = 1 1000 3 test name test user null