【IT168 专稿】大多数大规模Web应用程序都使用MySQL+Memcached架构,其中许多应用也同时使用了NoSQL数据库,如TokyoCabinet/Tyrant,也有一些人全部放弃MySQL,转投NoSQL的怀抱,曾经有人将这称为NoSQL运动,因为NoSQL数据库在处理一些简单访问模式,如主键查找时,比MySQL的表现更好,大多数Web应用程序的查询都很简单,因此这看上去是一个很合理的决定。
和许多其它大规模网站一样,我们的DeNA(我于2010年8月离开Oracle,加盟了日本最大的社交游戏平台提供商DeNA)多年来都存在类似的问题,但我们得出了不同的结论,最终我们全部使用了MySQL,当然一如既往地使用Memcached作为前端缓存(如预处理的HTML,计数/摘要信息),但我们没有使用Memcached缓存数据行,我们也没有使用NoSQL,因为我们从MySQL获得的性能比其它NoSQL产品更好,在我们的基准测试中,我们在一台普通的MySQL/InnoDB 5.1服务器上获得了750000+QPS的成绩,在生产环境中的性能更优秀。(QPS每秒查询率,每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。)
也许你不相信这个成绩,但我说的是真的,在这篇文章中,我将分享一下我们是如何做到的。
SQL主键查询真得能很快吗?
每秒你可以运行多少次主键查询?DeNA的应用程序需要执行大量的主键查询,如通过用户id获取用户信息,通过日记id获取日志信息,Memcached和NoSQL都能很好地适应这种需求,当你运行简单的多线程“memcached get”基准测试时,每秒大约可以执行400000+次get操作,即使Memcached客户端位于远程服务器上,当我使用最新的libmemcached和memcached测试时,在一台2.5GHz 8核Nehalem处理器,四个Broadcom千兆以太网卡的服务器上,测试成绩是每秒执行420000次get操作。
MySQL执行主键查询需要多长时间?通过基准测试很容易找到答案,只需要从sysbench,super-smack和mysqlslap等运行并行查询即可。
from test.user where user_id=1" \
--number-of-queries=10000000 --concurrency=30 --host=xxx –uroot
你可以使用下面的命令检查每秒读取了多少InnoDB行:
| grep -e "Com_select" ...
| Com_select | 107069 |
| Com_select | 108873 |
| Com_select | 108921 |
| Com_select | 109511 |
| Com_select | 108084 |
| Com_select | 108483 |
| Com_select | 108115 | ...
每秒有100000+次查询似乎还不错,但却远远低于Memcached的结果,MySQL实际上做了些什么?从vmstat输出可以看出,%user和%system都很高。
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
Oprofile输出显示了更多关于CPU资源消耗的情况。
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
在SQL解析阶段调用了MYSQLparse()和MYSQLlex(),在查询优化阶段调用了make_join_statistics()和JOIN::optimize(),这些都是SQL开销,很明显,性能下降主要是由SQL层,而不是InnoDB(存储)层造成的,MySQL做了很多Memcached/NoSQL不需要做的事情,如:
打开,锁住表 ?
创建SQL执行计划
解锁 ?
关闭表
MySQL也做了许多并发控制,例如,在发送/接收网络数据包时多次调用了fcntl(),全局互斥,如LOCK_open,LOCK_thread_count很频繁地创建/释放,这就是为什么oprofile输出中my_pthread_fastmutex_lock()排名第二,%system也不小的原因。
MySQL开发团队和外部社区都知道并发问题,有些问题在5.5中已经得到解决,我很高兴地看到,大量的修复工作已经完成,但同样重要的是%user也达到了60%,互斥竞争导致%system上升,而不是%user上升。虽然MySQL中的所有互斥问题都得到了解决,但不要指望每秒超过30万次查询。
你可能听说过HANDLER语句,遗憾的是,HANDLER语句对提高吞吐量并不会有太多帮助,因为查询解析,打开/关闭表等操作仍然是需要的。
CPU效率对内存中的工作负载非常重要
如果内存中没有合适的活动数据,SQL开销相对来说可以忽略不计,很简单,因为磁盘I/O成本是非常高的,在这种情况下,我们不需要太关心SQL成本。
但在我们的热点MySQL服务器上,几乎所有数据都装入到内存中了,它们完全变成CPU限制,分析结果和上面类似:SQL层消耗了大部分资源。我们需要执行大量的主键查询(如SELECT x FROM t WHERE id=?)或限制范围的扫描,即使70-80%的查询是对相同表的简单主键查询(不同的只是Where子句中的值),每次MySQL都要解析/打开/锁住/解锁/关闭,这对我们来说效率是很低的。