More Related Content Similar to Mysql handlersocket Similar to Mysql handlersocket (20) Mysql handlersocket2. 目 录
概要..........................................................................................................................3
性能瓶颈................................................................................................................4
HandlerSocket 结构...................................................................................................5
使用了 HandlerSocket 之后的性能................................................................................7
特点.......................................................................................................................8
HS 支持 Handler 语句风格......................................................................................8
HS 内置线程池.....................................................................................................8
性能非常高效......................................................................................................8
不再有重复的 Cache,数据保持一致........................................................................9
HS 不影响正常数据库功能的使用............................................................................9
HS 以插件的形式存在...........................................................................................9
限制.......................................................................................................................9
没有安全性.........................................................................................................9
对于磁盘 IO 向的场景没有效果...............................................................................9
性能瓶颈从 CPU 向转到网卡向...............................................................................9
加重 slave 的复制压力...........................................................................................9
安装和测试过程.......................................................................................................9
3. 概要
关于 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 的内部结构说起,先看下图:
Illustration 1: MySQL 内部结构
这个图是 MySQL 的整个结构,客户端连接到达 MySQL 后,需要经过很多模块才能真正返回数据。
首先是第一层, mysqld 层,也就是主服务器层,它包含四个模块: SQL 接口模块(判断 SQL 类型),
Parser 模块(解析器,解析 SQL 语法树),Optimizer 模块(优化器,语法树收敛),Cache&Buffer 模块
(缓冲层,包含主服务器缓冲,也可接受底层引擎的内存申请请求)。
4. 然后是第二层,存储插件层,这一层包含了所有的可用存储引擎,像著名的 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 st
18 0 4 1276402 184176 17348613 68371 212346 55 36 9 0 0
16 0 4 1276402 184176 17348613 68610 213710 53 34 13 0 0
21 0 4 1276402 184176 17348613 69137 218767 56 33 11 0 0
17 0 4 1276402 184176 17348613 67392 209756 54 31 15 0 0
18 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 name
259130 4.5199 mysqld MYSQLparse(void*)
196841 3.4334 mysqld my_pthread_fastmutex_lock
106439 1.8566 libc-2.5.so _int_malloc
94583 1.6498 bnx2 /bnx2
84550 1.4748 ha_innodb_plugin.so.0.0.0 ut_delay
67945 1.1851 mysqld _ZL20make_join_statistics
P4JOINP10TABLE_LISTP4ItemP16st_dynamic_array
63435 1.1065 mysqld JOIN::optimize()
55825 0.9737 vmlinux wakeup_stack_begin
55054 0.9603 mysqld MYSQLlex(void*, void*)
50833 0.8867 libpthread-2.5.so pthread_mutex_trylock
49602 0.8652 ha_innodb_plugin.so.0.0.0 row_search_for_mysql
47518 0.8288 libc-2.5.so memcpy
5. 46957 0.8190 vmlinux .text.elf_core_dump
46499 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
6. HandlerSocket 以 MySQL 插件的形式启动,启动后监听2个特殊端口,分别接受读请求和写请求,并且
fork 出可配置数量的服务线程。这些服务线程将轮番处理客户端请求,并对多次分散的请求做合并处理。
HS 不会不停地打开/关闭表对象句柄,它在打开后会保持一段时间,如果在一段时间内没有请求才会关闭,
以方便句柄重用。HS 自己实现了一套基于文本的通信协议(类似 Memcache 的协议,很简洁,不用构建语
法树也不需要优化),支持 telnet 的调试。
下图是 MySQL 搭配 NoSQL 的场景:
7. 这个场景和 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 name
984785 5.9118 bnx2 /bnx2
847486 5.0876 ha_innodb_plugin.so.0.0.0 ut_delay
545303 3.2735 ha_innodb_plugin.so.0.0.0 btr_search_guess_on_hash
317570 1.9064 ha_innodb_plugin.so.0.0.0 row_search_for_mysql
298271 1.7906 vmlinux tcp_ack
291739 1.7513 libc-2.5.so vfprintf
264704 1.5891 vmlinux .text.super_90_sync
248546 1.4921 vmlinux blk_recount_segments
244474 1.4676 libc-2.5.so _int_malloc
226738 1.3611 ha_innodb_plugin.so.0.0.0 _ZL14build_template
P19row_prebuilt_structP3THDP8st_tablej
206057 1.2370 HandlerSocket.so dena::hstcpsvr_worker::run_one_ep()
183330 1.1006 ha_innodb_plugin.so.0.0.0 mutex_spin_wait
175738 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_nowait
165337 0.9925 libc-2.5.so memcpy
149611 0.8981 ha_innodb_plugin.so.0.0.0 row_sel_store_mysql_rec
148967 0.8943 vmlinux generic_make_request
这次,原来很多镜像名是 mysqld 的线程已经不在列表上了,SQL Parser 相关的线程已经全部消失,倒是
有了很多 InnoDB 相关的线程上来了,说明优化已经奏效。那个镜像名是 bnx2 的线程是网络设备驱动,也
说明性能瓶颈开始从 CPU 转向网卡了。
测试环境:
• mysql15
• 8 core at 2.93GHZ
• 32G(所有数据都缓冲在内存里)
8. 特点
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 的网络协议包非常简洁。
9. • 服务线程可控,能尽可能避免 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 HandlerSocket
tar xvfz ahiguti-HandlerSocket-Plugin-for-MySQL.tar.gz
cd ahiguti-HandlerSocket-Plugin-for-MySQL/
./autogen.sh
./configure --with-mysql-source=${DIR}/mysql --with-mysql-bindir=${DIR}/mysql/bin
make
sudo make install
10. # Install the Perl dependency
cd perl-Net-HandlerSocket
perl Makefile.PL
Writing Makefile for Net::HandlerSocket
make
sudo make install
${DIR}/mysql/bin/mysql -uroot
mysql> 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 parameters
cd ${DIR}/mysql
echo "[mysqld]
plugin-load=handlersocket.so
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)" >>
my.cnf
# test table
CREATE 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 data
INSERT 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 connection
my $args = { host => '172.29.1.115', port => 9998 };
11. 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 9998
Trying 172.29.1.115...
Connected to mysql15 (172.29.1.115).
Escape character is '^]'.
P 0 test user PRIMARY user_name,user_email,created
0 1
0 = 1 100
0 3 test name test user null