【问题标题】:How scalable is this file-based DB approach?这种基于文件的数据库方法的可扩展性如何?
【发布时间】:2016-12-14 08:00:47
【问题描述】:

我有一个简单的 PHP 脚本,可以计算给定字符串输入的一些内容。它将结果缓存到数据库中,我们偶尔会删除超过一定天数的条目。

我们的程序员将此数据库实现为:

function cachedCalculateThing($input) {
  $cacheFile = 'cache/' . sha1($input) . '.dat';
  if (file_exists($cacheFile) {
    return json_decode(file_get_contents($cacheFile));
  }
  $retval = ...
  file_put_contents(json_encode($retval));
}
function cleanCache() {
  $stale = time() - 7*24*3600;
  foreach (new DirectoryIterator('cache/') as $fileInfo) {
    if ($fileInfo->isFile() && $fileInfo->getCTime() < $stale) {
      unlink($fileInfo->getRealPath());
    }
}

我们使用 Ubuntu LAMP 和 ext3。缓存查找在多少条目数时会变得不固定或违反硬限制?

【问题讨论】:

  • 我可以建议serverfault.com/questions/43133/… 作为一个好的起点吗? IMO 与其说是“非常慢”(我认为这样的系统本质上是这样),不如说是“明显较慢”(也就是减速是否会影响工作流程)。

标签: php filesystems ext3


【解决方案1】:

虽然该特定代码根本不是非常“可扩展”*,但有许多事情可以改进它:

  1. sha1 接受一个字符串。因此,在计算哈希之前,必须首先对非字符串 $input 变量进行序列化或 json_encoded。只需更改顺序以防止意外输入。
  2. 使用 crc32 代替 sha1,它更快 (Fastest hash for non-cryptographic uses?)
  3. 'cache/' 目录是相对于当前目录的,因此当页面工作目录发生变化时,缓存目录也会发生变化。会有人为的大量缓存未命中。
  4. 每次将文件存储在 cachedCalculateThing() 中时,将文件名存储在 /dev/shm/indexedofcaches(或类似的东西)中的索引中。在调用 file_exists 之前检查索引。 ext3 很慢,缓存以及内核 ext3 索引将被分页。所以这意味着每次您询问 file_exists 时都会进行目录扫描。对于小型缓存,它足够快,但对于大型缓存,您会看到速度变慢。
  5. 写入会阻塞,因此当缓存为空时会达到服务器负载限制,并且当两个或多个 php 写入器同时出现试图写入以前不存在的缓存文件时,缓存文件名会​​发生冲突.因此,您可能想尝试捕获这些错误和/或进行锁定文件测试。
  6. 我们正在一个有点原始的环境中考虑这个代码。事实是,写入还会根据当前磁盘利用率阻塞不确定的时间量。如果您的磁盘是旋转磁盘或较旧的 ssd,您可能会看到写入速度非常慢。检查iostat -x 4 并查找您当前的磁盘利用率。如果它已经高于 25%,则打开磁盘缓存会在随机时间将其峰值增加到 100%,并减慢所有 Web 服务。 (因为对磁盘的请求必须(通常)按顺序排队和服务(并非总是如此,但不要依赖它)。
  7. 根据缓存文件的大小,可能直接将它们存储在 /dev/shm/my_cache_files/ 中。如果它们都非常适合内存,那么您就可以将磁盘完全排除在服务链之外。然后,您必须执行一项 cron 作业来检查整体缓存大小并确保它不会占用您所有的内存。缺点=非持久性。不过,您也可以对其进行备份计划。
  8. 不要在运行时/服务代码中调用 cleanCache()。该目录迭代扫描将非常缓慢且阻塞。

'*为了可扩展性,通常以线性请求速度或并行服务器资源来定义。该代码:

  1. (-) 取决于运行 cleanCache() 函数的时间/位置——它有效地阻塞目录索引,直到缓存目录中的所有项目都被扫描。所以它应该进入一个 cron 工作。如果在 cron/shell 作业中,有更快的方法来删除过期的缓存。例如:find ./cache -type f -mtime +7 -exec rm -f "{}" \;
  2. (-) 你说对了 ext3 —— ext3 对小文件和非常大的目录内容的索引和结果速度相对较差。 google noatime 索引,如果可以将缓存目录移动到单独的卷,则可以关闭日志,避免重复写入,或使用单独的文件系统类型。或者查看您是否有 dir_index 可用作挂载选项。这是一个基准链接:http://fsi-viewer.blogspot.com/2011/10/filesystem-benchmarks-part-i.html
  3. (+) 目录缓存条目通过 rsync 比数据库复制更容易分发到其他服务器。
  4. (+/-) 实际上,这取决于您将存储多少不同的缓存项以及访问频率。对于少量文件,比如 10-100、小于 100K、频繁命中,那么内核会将缓存文件保持在内存中分页,并且您根本不会看到严重的减速(如果实施得当)。

主要的要点是,要从缓存系统中获得真正的可扩展性和良好的性能,必须考虑比短代码块显示的更多的考虑。可能有比我列举的更多的限制,但即使是那些也受变量的影响,例如大小、条目数、每秒请求数、当前磁盘负载、文件系统类型等——外部的东西到代码。这是意料之中的,因为缓存存在于代码之外。列出的代码可以用于请求数量较少的小型精品缓存集,但可能不适用于需要缓存的较大尺寸。

另外,您是在线程模式还是 prefork 模式下运行 Apache?它将影响 php 如何阻止其读取和写入。

-- 嗯,我可能应该补充说你想跟踪你的对象和键/哈希。如果 $input 已经是一个字符串,它是它的基本形式/已经被计算、检索、序列化,等等。如果 $input 是关键,那么 file_put_contents() 需要放一些别的东西(实际的变量/内容)。如果 $input 是要查找的对象(可能是一个长字符串,甚至是一个短字符串),那么它需要一个查找键,否则不会绕过/保存任何计算。

【讨论】:

  • 看起来不错,感谢您的所有这些实际考虑。
猜你喜欢
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-27
  • 2011-12-06
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
相关资源
最近更新 更多