7

我有一个整数 MySQL 列,每次查看页面时都会递增。SQL 查询如下所示:

UPDATE page SET views = views + 1 WHERE id = $id

当同一页面(相同的 id)每秒被查看多次(记录将锁定在 MySQL 中)并且查询会使 MySQL 停止运行时,我们开始遇到扩展问题。为了解决这个问题,我们一直在使用以下策略:

每次页面加载时,我们都会在 Memcache 中增加一个计数器,并将作业放入队列(Gearman)中,该队列将在后台(在 3 个工作机器中)更新 MySQL 中的计数器。简化的代码如下所示:

在页面视图中:

$memcache->increment("page_view:$id");
$gearman->doBackground('page_view', json_encode(array('id' => $id)));

在后台工作人员中:

$payload = json_decode($payload);
$views = $memcache->get("page_view:{$payload->id}");
if (!empty($views)) {
    $mysql->query("UPDATE page SET views = views + $views WHERE id = {$payload->id}");
    $memcache->delete("page_view:{$payload->id}");
}

这运作良好。它允许我们减少对数据库的查询(因为我们在写入数据库之前在 memcache 中聚合视图)并且数据库写入发生在后台,而不是阻止页面加载。

不幸的是,我们又开始看到 MySQL 锁了。似乎非常活跃的页面仍在几乎同时运行,导致 MySQL 再次锁定。锁正在减慢写入速度并经常杀死我们的工人。这导致队列变得非常大,通常有 70k 多个“落后”作业

我的问题:下一步我们应该做什么来扩大规模?

4

3 回答 3

3

我对Gearman了解不多,所以我可能错了。

每次增加计数器时,您都在排队执行 gearman 任务。我想只有当结果$memcache->increment为 1 时才将任务排入队列会更好。我的理由是,当 gearman 任务清除后下一次更新到达时page_view:$i,你不会有很长的 gearman 任务队列急于更新这个新的数据库中的值。这应该使您的代码独立于您的更新率,并限制了 gearman 选择新任务的速度(希望这会足够慢)。在一个完美的世界里,你可以让 gearman 延迟这个任务~1s。这将确保您仅以 1 qps 的速率更新此计数器。

独立于 gearman,如果您可以接受较慢的 READ 并假设您使用的是 InnoDB,则可以对这个计数器进行分片。

为此,只需添加一个分片列并使其成为主键的一部分,例如

CREATE TABLE page (
     id INTEGER,
     shard INTEGER,
     views INTEGER,
     PRIMARY KEY (id, shard)
)

当你更新这个计数器时,随机选择一个介于 1 - 10 之间的分片。当你读取它时,对id你想要读取的所有分片求和。这将使读取速度减慢 10 倍,但它允许您在写入时扩展 10 倍。(当然不需要是10,你可以选择任何你想要的数字。)

于 2013-01-31T14:08:31.260 回答
1

不确定您使用页数的目的以及记录所有页数的重要性。也许您可以将计数缓存在每台服务器的内存中,然后仅将它们保留在某个固定的时间表上。这样您就可以控制对数据库的访问次数。

当然,这显然不能保证在服务器因任何原因出现故障时计数会保持不变。因此,如果它用于任何重要的审计日志记录或任​​何丢失某些页面视图的问题,这将是行不通的。

于 2013-01-31T02:21:25.060 回答
0

使用 MySQL 的INSERT DELAYED....插入语句。它不会锁定,并会在可以写入时写入。

于 2013-01-31T02:11:41.287 回答