随笔 - 76, 评论 - 94, 访问 - 443760

导航


  • 访问:443760次
  • 积分:808分
  • 排名:第15名
  • 随笔:76篇
  • 评论:94条

随笔分类

随笔档案

相册

最新评论

阅读排行榜

评论排行榜

好饱 阅读(4387) 评论(4)

 

 - 在一台Commodity Server上实现超过750000 QPS的故事

 

QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

 

Oracle官方已经发布memcached守护程序插件,用于InnoDB,我很高兴地看到,NoSQL + MySQL已经成为一个正式的解决方案。这将是非常有前途的。让我们尝试它,使其更好地发展。

 

由于mysql的局限性,很多站点都采用了mysql+memcached的架构。

另外一些站点放弃了mysql,采用了NoSQL,比如TokyoCabinet/Tyrant等等。

不可否认,在做一些简单查询(尤其是primary key 查询)的时候,NoSQL比mysql要快很多很多。而且网站上的绝大多数查询都是这样的简单查询。

 

但是DeNA公司却一直使用mysql和memcached,并且在单台普通服务器上创造了每秒750,000次简单查询的记录。

 

或许你们不会相信单台mysql可以做到750,000 qps,但这是事实,下面就来详细说说。

 

SQL真的适合做primary key 查询么?

DeNA 公司的应用经常要进行primary key(PK)查询。比如根据user id取出userinfo,根据diary id取出日志内容, memcached和NoSQL很适合做这种事情。

如果进行memcached 的测试,很有可能每秒可以进行400,000次get操作,即使memcached和client位于不同的服务器。

在一台2.5GHz8核Nehalem 3块千M网卡的服务器上,libmemcached和memcached每秒可以进行420,000次get 操作。

 

mysql5呢?每秒可以做多少次PK查询?

innodb:

[matsunobu@host ~]$ mysqlslap --query="select user_name,..
from test.user where user_id=1" \
--number-of-queries=10000000 --concurrency=30 --host=xxx -uroot -p

此外也可以使用 sysbench或者super-smack等工具进行benchmark。

 

新开一个shell看一下:

 

[matsunobu@host ~]$ mysqladmin extended-status -i 1 -r -uroot \
| grep -e "Com_select"
 
 
| Com_select | 107069 |
| Com_select | 108873 |
| Com_select | 108921 |
| Com_select | 109511 |
| Com_select | 108084 |
| Com_select | 108115 |
...

100,000 qps 大概是memcached的1/4左右。

为什么会慢这么多呢?

服务器内存足够,这些数据应该都在内存中。

同样是内存操作,为什么mysql比memcached慢这么多?

 

vmstat数据如下:

[matsunobu@host ~]$ vmstat 1
r b swpd free buff cache in cs us sy id wa st
23 0 0 963004 224216 29937708 58242 163470 59 28 12 0 0
24 0 0 963312 224216 29937708 57725 164855 59 28 13 0 0
19 0 0 963232 224216 29937708 58127 164196 60 28 12 0 0
16 0 0 963260 224216 29937708 58021 165275 60 28 12 0 0
20 0 0 963308 224216 29937708 57865 165041 60 28 12 0 0

%user和%system占用的CPU都相当高。

 

再看看oprofile统计出来的信息:

ps:这个工具不错,内核级别的。

 

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
46957 0.8190 vmlinux .text.elf_core_dump
46499 0.8111 libc-2.5.so malloc

MYSQLparse是5.x版本中的,在4.x中是YYparse

 

MYSQLparse() 和 MYSQLlex()是在mysql解析sql语句的时候调用到的。

 

make_join_statistics()和JOIN::optimize() 是在query optimization(查询优化)阶段调用到的。

 

正是因为使用了SQL语句,才会有这些额外的负担。

 

从oprofile的输出可以得到如下结论:

SQL层严重影响到了mysql查询的性能。

 

与memcached和SQL比起来,mysql要额外做一些工作:

* Parsing SQL statements 解析sql语句

* Opening, locking tables 打开并锁定表

* Making SQL execution plans ???

* Unlocking, closing tables 解锁并关闭表

 

花荣注:使用mysqli 中的prepared statement API可以避免解析sql语句。

 

mysql还必须要做大量的并发控制,比如在发送/接收网络数据包的时候,fcntl()就要被调用很多很多次。

Global mutexes :LOCK_open LOCK_thread_count 也被频繁地调用。

所以在oprofile的输出中,排在第二位的是my_pthread_fastmutex_lock()。并且%system占用的CPU相当高(28%)。

其实 mysql开发团队和一些外围的开发团体都了解大量并发控制对性能的影响,

他们在mysql 5.5中已经解决了某些问题。未来的mysql中,%system占用的cpu会越来越少。

 

但是,%user占用的60%cpu怎样处理呢?

 

Mutex contentions result in %system increase, not %user increase

 

即使所有的并发问题都得到处理,估计也很难做到300,000 qps。

 

或许你听说过HANDLER statement的性能也不错。

可是HANDLER statement需要query parsing,需要open/close table。

不会有太大的帮助。

 

在完全内存操作的情况下,CPU的效率非常重要

如果只有一小部分数据能够进入内存,那么SQL语句的解析带来的额外负担已经不算什么了。

因为磁盘的IO操作会消耗更长的时间。

 

在我们的mysql服务器中,内存是大大的,几乎所有的数据都可以放进内存。

SQL层就变成了额外负担,占用了大量的cpu资源。

 

在线上的应用中,我们要进行大量的PK查询。即使70-80%的查询都是在同一张表上进行的, mysql还是每次都要parse/open/lock/unlock/close,看起来就感觉效率低下。

 

We needed to execute lots of primary key lookups(i.e. SELECT x FROM t WHERE id=?) or limited range scans. Even though 70-80% of queries were simple PK lookups from the same table (difference was just values in WHERE), every time MySQL had to parse/open/lock/unlock/close, which seemed not efficient for us.

 

花荣注:难道说mysql中的table_open_cache不是用来减少table open的次数的么。。

 

NDBAPI

有没有办法在sql层进行优化呢?

 

http://dev.mysql.com/doc/ndbapi/en/index.html

 

如果你使用mysql cluster, NDBAPI会是最佳解决方案。

 

It’s recommended using NDBAPI for frequent access patterns,

and using SQL + MySQL + NDB for ad-hoc or infrequent query patterns.

 

这就是我们想要的:

1 faster access API.

2 sql语句仍然要可用,以处理一些特定的或者复杂的查询。

 

但是,把innodb转化成ndb可不是一件轻松的事情。

 

HandlerSocket Plugin

最好的办法是在mysql内部实现一个NoSQL的网络服务。daemon plugin。

它监听在某个端口,接受NoSQL 协议/API的数据包,使用Mysql internal storage engine API直接在innodb数据表上进行操作,并且返回相应的数据。

关于mysql internal storage engine API可以看这个文档:

 

http://forge.mysql.com/wiki/MySQL_Internals_Custom_Engine

 

这个概念首先被Cybozu Labs 的Kazuho Oku 提出,然后他写了一个MyCached UDF,用的是memcached的协议。

 

http://developer.cybozu.co.jp/kazuho/2009/08/mycached-memcac.html

 

随后,Akira Higuchi 写了另外一个plugin: HandlerSocket。

 

http://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL

 

从图中可以看到,客户端既可以使用普通的mysql api来操作mysql(3306端口),

也可以使用HandlerSocket API对数据库进行PK查询,以及INSERT/UPDATE/DELETE操作(9998与9999端口)。

 

在使用HandlerSocket操作的时候,省去了SQL parsing, Opening table, Making Query Plans, Closing table等步骤。

 

而且数据都是保存在原来的INNODB表中的。

 

在HandlerSocket操作innodb数据表的时候,显然也需要open/close table。

但它只打开一次,然后会reuse。

由于open/close table非常耗时,并且会带来严重的mutex竞争,所以这种改进,极大地提升了性能。

 

Of course HandlerSocket closes tables when traffics become small etc so that it won’t block administrative commands (DDL) forever.

上图是memcached的原理图。

memcached主要用来缓存数据集(database records)。

因为memcached的get操作比mysql的PK查询要快得多。

如果HandlerSocket获取数据的速度比memcached还要快,那么我们就没必要再使用memcached来缓存数据集了。做做其它缓存还是可以的。比如生成出来的HTML代码,还有一些统计数据等等。

 

花荣注:貌似我从来没有直接把mysql的数据集扔到memcached中。一直都是把中间结果保存到里面。难道走偏了?

 

使用 HandlerSocket

看下面这张user表:

 

CREATE TABLE user (

user_id INT UNSIGNED PRIMARY KEY,

user_name VARCHAR(50),

user_email VARCHAR(255),

created DATETIME

) ENGINE=InnoDB;

在mysql中取出数据的方法如下:

 

mysql> SELECT user_name, user_email, created FROM user WHERE user_id=101;

+---------------+-----------------------+---------------------+

| user_name | user_email | created |

+---------------+-----------------------+---------------------+

| Yukari Takeba | yukari.takeba@dena.jp | 2010-02-03 11:22:33 |

+---------------+-----------------------+---------------------+

在HandlerSocket中怎样操作呢?

 

安装HandlerSocket

详细文档看这里:

 

http://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/installation.en.txt

 

大概安装步骤如下:

1 下载

 

http://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL

 

2 编译 HandlerSocket客户端和服务器端程序:

 

./configure --with-mysql-source=... --with-mysql-bindir=... ; make; make install

3 安装插件

 

mysql> INSTALL PLUGIN 'HandlerSocket' SONAME 'HandlerSocket.so';

安装完毕。不需要修改mysql的源代码。

mysql需要5.1或者以后版本。

 

编写HandlerSocket 客户端代码

目前HandlerSocket客户端只有C++ 和 Perl的库。还没有php和C的。

 

#!/usr/bin/perl
 
use strict;
use warnings;
use Net::HandlerSocket;
 
#1. establishing a connection
my $args = { host => 'ip_to_remote_host', port => 9998 };
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',
'user_name,user_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)
$res = $hs->execute_single(0, '=', [ '101' ], 1, 0);
die $hs->get_error() if $res->[0] != 0;
shift(@$res);
for (my $row = 0; $row < 1; ++$row) {
my $user_name= $res->[$row + 0];
my $user_email= $res->[$row + 1];
my $created= $res->[$row + 2];
print "$user_name\t$user_email\t$created\n";
}
 
#4. closing the connection
$hs->close();

 

 

这段脚本从user表中取出来了 user_name, user_email 和created字段。

 

[matsunobu@host ~]$ perl sample.pl
Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33

对于HandlerSocket,推荐使用persistent connection。以减少数据库连接的次数。

 

HandlerSocket协议 is a small-sized text based protocol。

与memcached类似,可以使用telnet来获取数据。

 

[matsunobu@host ~]$ telnet 192.168.1.2 9998

Trying 192.168.1.2...

Connected to xxx.dena.jp (192.168.1.2).Escape character is '^]'.

P 0 test user PRIMARY user_name,user_email,created

0 1

0 = 1 101

0 3 Yukari Takeba yukari.takeba@dena.jp 2010-02-03 11:22:33Benchmarking

测试环境:

CPU: Nehalem 8 cores, E5540 @ 2.53GHz

RAM: 32GB (all data fit in the buffer pool)

MySQL Version: 5.1.50 with InnoDB Plugin

memcached/libmemcached version: 1.4.5(memcached), 0.44(libmemcached)

Network: Broadcom NetXtreme II BCM5709 1000Base-T x 3

 

仍然使用上面的user表,数据大概100w行。

SQL语句为:

SELECT user_name, user_email, created FROM user WHERE userid=?

memcached和HandlerSocket的客户端代码都用C/C++编写。

所有的客户端程序都位于另外一台机器上。通过TCP/IP与MYSQL/memcached服务器连接。

 

测试结果如下:

 

approx qps server CPU util
MySQL via SQL 105,000 %us 60% %sy 28%
memcached 420,000 %us 8% %sy 88%
HandlerSocket 750,000 %us 45% %sy 53%

HandlerSocket比传统的mysql快了7.5倍,而且%us的cpu使用率为mysql的3/4。

说明SQL层还是相当耗时的。避开SQL层之后,性能得到了明显的提升。

 

HandlerSocket比memcached快了78%。而且%sy占用的cpu比memcached要少得多。

虽然memcached是一个优秀的产品,但是还有很大的提升空间。

 

花荣注:比如七夜就改写了memcached的部分代码。

 

再来看一下HandlerSocket测试的时候oprofile的输出:

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

 

 

大部分的CPU用在了网络数据包的处理,取出数据,等。

排在首位的bnx2是网卡驱动程序。

 

由于HandlerSocket只是一个插件,最终还会调用innodb引擎的函数去取数据,

所以我们仍然可以使用mysql命令来获取到统计数据。

 

$ mysqladmin extended-status -uroot -i 1 -r -p| grep “InnoDB_rows_read”

…
| Innodb_rows_read | 750192 |
| Innodb_rows_read | 751510 |
| Innodb_rows_read | 757558 |
| Innodb_rows_read | 747060 |
| Innodb_rows_read | 748474 |
| Innodb_rows_read | 759344 |
| Innodb_rows_read | 753081 |
| Innodb_rows_read | 754375 |

作者注:memcached和HandlerSocket的性能都会受到网络IO的限制。

上面的测试是在3块千M网卡的环境下进行的。如果换成单网卡,HandlerSocket大概每秒处理260,000次查询,memcached每秒处理220,000次查询。

 

HandlerSocket的优势和特性支持很多类型的查询

PK肯定没问题

unique key也可以。

普通key也可以。

limit 语句也可以。

IN 也没问题。

INSERT/UPDATE/DELETE也没问题。

不过,一定要用到索引才行。

不使用索引的操作不被支持。 Operations that do not use any index are not supported。

详细说明看这里:http://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/tree/master/docs-en/

 

高并发

HandlerSocket employs epoll() and worker-thread/thread-pooling architecture,the number of MySQL internal threads is limited 。

所以放心地使用persistent connection吧。不会导致mysql connection太高的。

 

高性能

Not only HandlerSocket eliminates SQL related function calls, but also it optimizes around network/concurrency issues

HandlerSocket不仅避开了SQL层,而且优化了网络层,并解决了并发的一些问题。

 

更小的网络数据包

与mysql协议相比,HandlerSocket协议的数据包更简单更小。

从总体上来看,网络传输的数据量会减少。

 

Running limited number of MySQL internal threads

不会导致mysql connection太高。

 

Grouping client requests

当HandlerSocket接受到请求的时候,每一个处理线程都会收集尽可能多的请求,

然后一次把这些请求执行完毕,并返回数据。

看起来没什么神奇的,但是却可以大大地提升性能。

代价是每个请求的响应时间略微变长。

*** Can reduce the number of fsync() calls

*** Can reduce replication delay

 

至于详细情况,作者会再写其它文章。很期待。

 

No duplicate cache

我们使用memcached的时候,数据会同时缓存到innodb的buffer pool与memcached中。

算是重复的cache吧。

HandlerSocket自身没有缓存,它完全听从InnoDB storage engine。

 

No data inconsistency

缓存的一大问题就是数据何时过期。

HandlerSocket没有缓存,也就不存在这种问题。

 

Crash-safe

InnoDB还是相当可靠的。

innodb-flush-log-at-trx-commit=1 加上这句就更保险了。

最多会丢失死机前1秒内的数据。

 

SQL can be used from mysql clients

我们仍然可以使用SQL语句进行复杂的查询。

确实方便很多啊。

 

All operational benefits from MySQL

HandlerSocket作为插件运行于mysql内部,所以mysql的操作,比如SQL, 热备份,主从,Nagios监视等等,都是支持的。

通过 show global status, show engine innodb status , show processlist 这些都可以看到HandlerSocket的状态。

 

No need to modify/rebuild MySQL

无需改动mysql的源代码或者重新编译mysql。

线上的环境可以直接使用HandlerSocket。

 

Independent from storage engines

理论上讲,HandlerSocket支持任何的存储引擎,比如myisam,memory?

不过目前只在mysql5.1 5.5 InnoDB的环境下进行过测试和应用。

 

缺点与注意事项Need to learn HandlerSocket APIs

目前只有C++和PERL库可用。以后肯定会有php扩展出现。

 

No security

就像其它的NoSQL数据一样,HandlerSocket没有任何安全方面的功能。比如权限控制之类的。

HandlerSocket的工作线程使用system权限,所以你的应用可以存取所有的表。

当然可以使用防火墙来限制访问。

 

No benefit for HDD bound workloads

如果你的内存不够大,内存要与硬盘交换数据,那么HandlerSocket的优势就体现不出来了。

我们在服务器上使用HandlerSocket,将绝大多数的数据装入内存。

 

DeNA在生产中使用HandlerSocket

 

DeNA公司已经在生产环境上使用HandlerSocket了。

结果令人满意!

有了HandlerSocket可以省掉很大一批memcached服务器与mysql从服务器。

而且带宽占用也得到了缓解。我们很满意这个结果。

 

HandlerSocket插件是开源的,随意尝试. 如果你能给我们反馈,我们将非常感激。

 

转载自:http://hi.baidu.com/noirwinter/blog/item/a935935025ddd8718535245b.html

 


评论列表
Ronald
I went to tons of links before this, what was I thiignnk?

发表评论
切换编辑模式