写操作缓存

2018年04月03日 08:19 | 2779次浏览 作者原创 版权保护

对于一个数据库写操作频繁的站点来说,通过引入写缓存来减少写数据库的次数显得至关重要。我们知道,通常的数据写操 作包括插入、更新、删除,这些操作又同时可能伴随着条件查找和索引的更新,所以它们的开销往往会令人望而生畏

直接更新 

下面我们来看一个有趣的例子,就拿站点访问量统计功能来说,我们需要记录每个 URL 的累计访问量,所以每次页面刷新都 会伴随着一次访问量的增加,我们将访问量数据保存在数据库中,这毫无疑问,因为我们要长久地保存它。 为此,我们编写了一段有代表性的代码,它可以让某个页面的访问量加 1,代码如下所示

<?php
$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:    www.liveinmap.com
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。下面我们引入 memcached 作为写缓存,它也许会使得数据更新出现延迟,但是我们可以接受,因为我们并不需要访 问量数据实时更新。 

线程安全和锁竞争 

在此之前,我们先来看一种传统的分布式加运算,以下的代码只是例子中的一个片段,它先从缓存服务器上取回一个数值, 然后在本地加 1,接下来写回缓存服务器。

<?php
$count = $memcache->get($key);
$count++;
$memcache->set($key, $count, false, 0);
?>

看起来没有任何问题,但是,别忘了可能会有多个用户同时触发这样的计算,你一定能想象的到会有什么糟糕的后果,最后的累计访问量总是小于实际访问量。

事实上,这并不涉及 memcached 本身线程安全的问题,而是以上这种加运算的方式不是线程安全的。如果要保证这种加运算可以正常无误地同时进行,那就要考虑一定的事务隔离机制,简单的办法是使用锁竞争,并且将锁保存在 memcached 上,存在竞争关系的动态内容可以争夺这个锁,一旦某个会话抢到锁,那么其他的会话必须等待。

这里要说的不是如何实现这种分布式锁机制,而是并不鼓励这样做,因为锁竞争带来的等待时间是无法容忍的,这将使得引入 memcached 作为写缓存的唯一优势立刻烟消云散.

原子加法 

幸运的是,memcached 提供了原子递增操作,事实上,也正是因为它,我们才考虑在访问量递增更新的应用中引入写缓存。 我们再来修改代码,加入 memcached 的支持,如下所示:

<?php
$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.为 memcached 缓存中的对应数据项加 1,如果该数据项不存在,则创建该数据项,并且赋值为 1,代表这个页面是第 一次被访问;

2.如果 memcached 缓存中存在对应数据项,并且累加后的数值为 1000,则将这个数据项置 0,同时更新数据库,将数 据库中的对应数值加 1000。 也就是说,改造后的程序每经历 1000 次递增后才写一次数据库,究竟效果如何呢?我们再来进行压力测试,结果如下所示:

Server Software:     lighttpd/1.4.20
Server Hostname:        www.liveinmap.com
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



小说《我是全球混乱的源头》
此文章本站原创,地址 https://www.vxzsk.com/750.html   转载请注明出处!谢谢!

感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程


上一篇:spring boot cache理论详解 下一篇:概述
^