我认为你需要退后一步,从不同的角度审视你的一些问题,才能得到答案。
“后台线程多久运行一次?” 要回答这个问题,您需要回答以下问题:您会丢失多少数据?数据在 MySQL 中的原因是什么,以及该数据的访问频率是多少?例如,如果每天只需要查询一次数据库以获取报告,那么您可能只需要每天更新一次。另一方面,如果 Redis 实例死了怎么办?您可以损失多少增量并且仍然“正常”?这些将提供关于多久更新一次 MySQL 实例的问题的答案,我们无法为您回答。
我会使用一种非常不同的策略将其存储在 redis 中。为了便于讨论,让我们假设您决定需要每小时“刷新到 db”。
将每个命中存储在哈希中,并按照以下方式使用键名结构:
interval_counter:DD:HH
interval_counter:total
使用页面 id(例如 URI 的 MD5 总和、URI 本身或您当前使用的任何 ID)作为哈希键并在页面视图上执行两次增量;每个哈希一个。这为您提供了每个页面的当前总数和要更新的页面子集。
然后,您将让您的 cron 作业在一小时开始后运行一分钟左右,以通过抓取前一小时的哈希来拉下所有具有更新视图计数的页面。这为您提供了一种非常快速的方法来获取数据以更新 MySQL 数据库,同时避免任何需要做数学运算或玩时间戳等技巧。通过从不再增加的键中提取数据,您可以避免由于竞争条件时钟偏斜。
您可以在每日密钥上设置过期时间,但我宁愿在成功更新数据库后使用 cron 作业将其删除。这意味着如果 cron 作业失败或无法执行,您的数据仍然存在。它还通过不变的键为前端提供一整套已知的命中计数器数据。如果您愿意,您甚至可以保留每日数据,以便能够对页面的受欢迎程度进行窗口查看。例如,如果您通过 cron 作业而不是删除设置过期,将每日哈希值保留 7 天,您可以显示上周每个页面每天的流量。
执行两个 hincr 操作可以单独或流水线完成,仍然表现得相当好,并且比在代码中进行计算和处理数据更有效。
现在是关于过期低流量页面与内存使用的问题。首先,您的数据集听起来不像需要大量内存的数据集。当然,这在很大程度上取决于您如何识别每个页面。如果您有一个数字 ID,则内存需求将相当小。如果你仍然有太多的内存,你可以通过配置调整它,如果需要,甚至可以使用 32 位的 redis 编译来显着减少内存使用。例如,我在这个答案中描述的数据是我曾经为 Internet 上十个最繁忙的论坛之一管理的,它消耗的数据不到 3GB。我还将计数器存储在比我在这里描述的更多的“时间窗口”键中。
也就是说,在这个用例中,Redis 是缓存。如果在上述选项之后您仍然使用太多内存,您可以设置密钥过期并为每个 ht 添加过期命令。更具体地说,如果您遵循上述模式,您将在每次点击时执行以下操作:
hincr -> total
hincr -> daily
expire -> total
这使您可以通过在每次访问时延长其过期时间来保持正在使用的任何内容的新鲜度。当然,要做到这一点,您需要包装您的显示调用以在总计哈希上捕获 hget 的空答案并从 MySQL DB 填充它,然后递增。您甚至可以将两者都作为增量进行。如果您的 Redis 节点需要重新填充,这将保留上述结构,并且可能是从 MySQL Db 更新 Redis 服务器所需的相同代码库。为此,您需要考虑并决定哪个数据源将被视为权威。
您可以通过根据您从前面的问题中确定的数据完整性参数修改间隔来调整 cron 作业的性能。要获得更快运行的 cron nob,请减小窗口。使用这种方法减小窗口意味着您应该有一个较小的页面集合来更新。这里的一大优势是您无需弄清楚需要更新哪些密钥然后去获取它们。您可以执行 hgetall 并迭代哈希的键以进行更新。这还通过一次检索所有数据来节省许多往返行程。在任何一种情况下,如果您可能想要考虑从属于第一个 Redis 实例的第二个 Redis 实例来进行读取。您仍然可以对 master 执行删除操作,但这些操作更快,并且不太可能在您的写入繁重的实例中引入延迟。
如果您需要 Redis DB 的磁盘持久性,那么肯定将其放在从属实例上。否则,如果您确实经常更改大量数据,您的 RDB 转储将不断运行。
我希望这会有所帮助。没有“固定”的答案,因为要正确使用 Redis,您首先需要考虑如何访问数据,这因用户和项目而异。在这里,我基于此描述采取的路线:两个消费者访问数据,一个仅显示,另一个确定更新另一个数据源。