[试读]分布式缓存+-+节选
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
直接更新
下面我们来看一个有趣的例子,就拿站点访问量统计功能来说,我们需要记录每个URL的累计访问量,所以每次页面刷新都会伴随着一次访问量的增加,我们将访问量数据保存在数据库中,这毫无疑问,因为我们要长久的保存它。
为此,我们编写了一段有代表性的代码,它可以让某个页面的访问量加1,代码如下:
$page = 'article_090222.htm';
$sql = "update page_view set view_count=view_count+1 where page='"
. $page . "'";
$conn = mysql_connect('localhost', 'root', '', db_page);
mysql_select_db('db_map_main', $conn);
mysql_query($sql, $conn);
?>
这段代码很简单,虽然我不知道article_090222.htm这个页面目前的访问量是多少,但是我知道
它的访问量增加了1,而且结果将保存在数据库中。我们可以称它为“直接更新”,这来源于之前介绍的文件访问中的直接I/O标记(O_Direct),它可以跳过内核写缓存,将数据毫无延迟的直接写入磁盘。
我们对上边的动态程序进行压力测试,结果如下:
Server Software:lighttpd/1.4.20
Server Hostname:
Server Port:8001
Document Path:/book_writecache_mysql.php
Document Length:0 bytes
Concurrency Level:500
Time taken for tests: 5.239 seconds
Complete requests:10000
Failed requests:0
Write errors:0
Total transferred:1690000 bytes
HTML transferred:0 bytes
Requests per second:1908.89 [#/sec] (mean)
Time per request:261.933 [ms] (mean)
Time per request:0.524 [ms] (mean, across all concurrent
requests)
Transfer rate:315.04 [Kbytes/sec] received
从测试结果可以看出,我们对这个动态程序一共请求了10000次,这也意味着article_090222.htm 这个页面的访问量被增加了10000。下面我们引入memcache作为写缓存,它也许会使得数据更新出现延迟,但是我们可以接受,因为我们并不需要访问量数据实时更新。
线程安全和锁竞争?
在此之前,我们先来看一种传统的分布式加运算,以下的代码只是例子中的一个片段,它先从缓存服务器上取回一个数值,然后在本地加1,接下来写回缓存服务器。
$count = $memcache->get($key);
$count++;
$memcache->set($key, $count, false, 0);
?>
看起来没有任何问题,但是,别忘了可能会有多个用户同时触发这样的计算,你一定能想象的到会有什么糟糕的后果,最后的累计访问量总是小于实际访问量。
事实上,这并不涉及memcache本身线程安全的问题,而是我们可以说,以上这种加运算的方式,不是线程安全的。如果要保证这种加运算可以正常无误的同时进行,那就要考虑一定的事务隔离机制,简单的办法是使用锁竞争,并且将锁保存在memcache上,存在竞争关系的动态内容可以争夺这个锁,一旦某个会话抢到锁,那么其它的会话必须等待。
这里要说的不是如何实现这种分布式锁机制,而是并不鼓励这样做,因为锁竞争带来的等待时间是无法容忍的,这将使得引入memcache作为写缓存的唯一优势立刻烟消云散。
原子加法
幸运的是,memcache提供了原子递增操作,事实上,也正是因为它,我们才考虑在访问量递增更新的应用中引入写缓存。
我们再来修改代码,加入memcache的支持,如下:
$page = 'article_090222.htm';
$memcache = memcache_connect('10.0.1.12', 11711);
$count = $memcache->increment($page, 1);
if ($count === false)
{
$memcache->add($page, 1, false, 0);
exit(1);
}
if ($count ==1000)
{
$memcache->set($page, 0, false, 0);
$sql = "update page_view set view_count=view_count+" .
$count . " where page='" . $page . "'";
$conn = mysql_connect('localhost', 'root', '', 'db_page');
mysql_query($sql, $conn);
}
?>
新的代码中,完全改变了之前的“直接更新”方式,当需要增加一次访问量的时候,它做了以下工作:
1.为memcache缓存中的对应数据项加1,如果该数据项不存在,则创建该数据项,并且赋值为
1,代表这个页面是第一次访问;
2.如果memcache缓存中存在对应数据项,并且累加后的数值为1000,则将这个数据项置0,
同时更新数据库,将数据库中的对应数值加1000。
也就是说,改造后的程序每经历1000次递增后才写一次数据库,究竟效果如何呢?我们再来进行压力测试,结果如下:
Server Software:lighttpd/1.4.20
Server Hostname:
Server Port:8001
Document Path:/writecache_memcache.php
Document Length:0 bytes
Concurrency Level:500
Time taken for tests: 3.599 seconds
Complete requests:10000
Failed requests:0
Write errors:0
Total transferred:1690000 bytes
HTML transferred:0 bytes
Requests per second:2778.24 [#/sec] (mean)
Time per request:179.970 [ms] (mean)
Time per request:0.360 [ms] (mean, across all concurrent
requests)
Transfer rate:458.52 [Kbytes/sec] received
吞吐率提高了大约46%,这同样是一个不小的飞跃。所以,如果你的数据库因为大量的写操作而繁忙不堪,那么仔细考虑一下,哪些写操作可以缓存在memcache中呢?
监控状态