前面简单介绍了 memcached 的一些基本特性,因为它实在是家喻户晓,以至于我觉得不需要太多详细地介绍它本身,否则你 会觉得我在浪费你的时间,好,我们还是来看看如何用它来帮助站点提高吞吐率,这也许是你最关心的。 还记得磁盘缓存区中的两部分吗?没错,读缓存区和写缓存区。那么我们也按照这个思路,先将 memcached 作为数据库的读 缓存区,来看一个例子。
重复的身份验证
大多数站点都有自己的用户系统,这给我们带来了不少的麻烦,有些事情不可不做,那就是在每个用户请求页面的时候,都 需要检查用户的登录状态。
很早以前,我喜欢将登录用户的 ID 直接写入浏览器 cookies,并以此为荣,可是在随后的几年里,破坏者对 cookies 更是情有 独钟,他们可以很容易地利用 cookies 的无知,篡改本地 cookies 来冒充其他用户,这让我感到时代在飞速前进,我们必须改 变。
新的方式问世了,用户在登录站点的时候会获得一个 ticket 字符串,并将它写入浏览器 cookies,随后每次请求新的页面时, 都需要上报这个 ticket,接受重复的身份检查。非常好,它工作得一切正常,破坏者由于不知道我们发给其他登录用户的 ticket, 所以无法冒充。补充一点,事实上,永远不要低估破坏者的本领,它们可以通过其他手段获得他们想要的一切东西。
现在,我们将身份检查部分的代码抽取出来,它的工作很简单,读取 cookies 中的 ticket 字符串,然后通过条件 SQL 语句查 询数据库,寻找拥有这个 ticket 的用户。我们来看这部分代码,它用 PHP 编写而成。
<?php $user_ticket = '010f06c4c7e74f82fe7fb2aea97c50b2'; $sql = "select * from user_info where user_ticket='" . $user_ticket . "'"; $conn = mysql_connect('localhost', 'root', '', 'db_user'); $res = mysql_query($sql, $conn); $row = mysql_fetch_array($res, MYSQL_ASSOC); var_dump($row); ?>
以上的代码不难理解,我们这里省略了从 cookies 中获取 ticket 的过程,而直接将 ticket 赋值给$user_ticket 变量,接下来是数
据库查询,然后将获得的用户信息进行打印。这里涉及一个数据表 user_info,它有大约 20 个字段,1 万多行的记录。
我们来对它进行压力测试,结果如下所示:
我不认为这个结果很出色,你也许跟我有同样的想法。
数据库索引
不过,我并没有对数据库失去信心,我们来看看以上程序中执行的一段 SQL 语句:
select * from user_info where user_ticket='010f06c4c7e74f82fe7fb2aea97 c50b2'
它包含一个 where 条件,我想我们可能没有使用索引,通过 MySQL 的查询分析工具来分析这条 SQL 语句,结果如下所示:
mysql> explain select * from user_info where user_ticket='010f06c4 c7e74f82fe7fb2aea97c50b2'; +----+-------------+-----------+------+---------------+------+---------+------+------+---------- ---+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+------+---------- ---+ | 1 | SIMPLE | user_info | ALL | NULL | NULL | NULL | NULL | 10807 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+------+---------- ---+ 1 row in set (0.02 sec)
的确,请看 key 字段,结果为 NULL,这导致它要遍历所有的 10807 行记录来寻找目标,毫无疑问时间都花在这里了
我们这里要做的就是为 user_ticket 字段加上索引,然后再来看看压力测试结果:
Server Software: lighttpd/1.4.20 Server Hostname: www.liveinmap.com Server Port: 8001 Document Path: /book_readcache_mysql.php Document Length: 850 bytes Concurrency Level: 500 Time taken for tests: 5.970 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 10140721 bytes HTML transferred: 8500000 bytes Requests per second: 1675.11 [#/sec] (mean) Time per request: 298.489 [ms] (mean) Time per request: 0.597 [ms] (mean, across all concurrent requests) Transfer rate: 1658.86 [Kbytes/sec] received
虽然难以置信,但是合乎情理,我们再来看看 SQL 分析:
mysql> explain select * from user_info where user_ticket='010f06c4c7 e74f82fe7fb2aea97c50b2'; +----+-------------+-----------+------+---------------+-------------+---------+-------+------+-- -----------+ | id | select_type | table | type | possible_keys | key | key_ len | ref | rows | Extra | +----+-------------+-----------+------+---------------+-------------+---------+-------+------+-- -----------+ | 1 | SIMPLE | user_info | ref | user_ticket | user_ticket | 35 | const | 1 | Using where | +----+-------------+-----------+------+---------------+-------------+---------+-------+------+-- -----------+ 1 row in set (0.00 sec)
这时候 key 的结果为 user_ticket,这意味着此次查询利用了该索引,直接找到目标,的确,rows 为 1。
关于数据库索引的探讨,我们暂且放下,这里之所以提出索引,目的在于希望数据库和 memcached 保持相似的压力测试条件,
因为 memcached 也使用了基于 hash 算法的 key 来直接定位目标。另外,这里拿出数据库索引小试牛刀,也算是给下一章做个
预热,下一章将详细介绍包括索引在内的一系列数据库优化方法。
缓存用户登录状态
即便使用了索引,查询本身还是存在开销,这很大程度上在于数据库的 I/O 操作,这时候,轮到 memcached 出场了。我们接
下来希望将用户状态缓存在 memcached 中,没错,对象序列化派上用场了,它帮你简化了工作量,我们来看看新的代码:
<?php $user_ticket = '010f06c4c7e74f82fe7fb2aea97c50b2'; $memcache = memcache_connect('10.0.1.12', 11711); $user = $memcache->get($user_ticket); if ($user !== false) { var_dump($user); exit(1); } $sql = "select * from user_info where user_ticket='" . $user_ticket . "'"; $conn = mysql_connect('localhost', 'root', '', 'db_user'); $res = mysql_query($sql, $conn); $user = mysql_fetch_array($res, MYSQL_ASSOC); $memcache->add($user_ticket, $user, false, 3600); var_dump($user); ?>
这样一来,用户在 1 个小时的缓存有效期内,便不需要访问数据库。有一点需要注意,当用户选择注销时,你必须主动清空
memcached 中该用户的登录状态缓存,否则会让用户感到匪夷所思。
还是老办法,压力测试见分晓,结果如下所示:
Server Software: lighttpd/1.4.20 Server Hostname: www.liveinmap.com Server Port: 8001 Document Path: /book_readcache_memcache.php Document Length: 844 bytes Concurrency Level: 500 Time taken for tests: 3.767 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 10036411 bytes HTML transferred: 8440000 bytes Requests per second: 2654.64 [#/sec] (mean) Time per request: 188.349 [ms] (mean) Time per request: 0.377 [ms] (mean, across all concurrent requests) Transfer rate: 2601.86 [Kbytes/sec] received
相比于前面的访问数据库,这次的吞吐率提高了大约 58%,因为我们使用了数据库的“前置读缓存”。事实上,数据库本身也 有查询缓存,后面我们会介绍,但是那属于数据库的控制范围,而我们这里的分布式缓存则由动态内容来控制,它更加灵活。 是的,它可以缓存一切。
值得说明的是,以上的压力测试结果都不具备绝对意义,因为有很多不确定的环境因素,比如数据库环境设置、数据表记录 数、opcode 缓存、机器硬件配置、当前系统负载等。另一方面,它们的相对意义也有一定的约束条件,也许在某种环境下, 你可以让访问数据库获得比访问 memcached 缓存更好的表现,比如将 memcached 服务器部署在另一个数据中心,当然,你不 会那么做。总之,你只需要从本质上理解这些对比背后的原因即可,它们往往都是一些简单的道理,类似于索引优于没有索 引、内存优于磁盘、近距离优于远距离。
感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程