[试读]分布式缓存+-+节选

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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中呢?

监控状态

相关文档
最新文档