缩放MySQL中的递增计数器(用于跟踪综合浏览量)

时间:2013-01-31 02:08:47

标签: php mysql performance optimization scalability

我有一个整数MySQL列,每次查看页面时都会递增。 SQL查询看起来像这样:

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

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

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

在页面浏览中:

$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}");
}

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

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

我的问题:接下来我们应该做些什么来扩展它?

3 个答案:

答案 0 :(得分:3)

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

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

独立于gearman,如果你可以接受较慢的READ并假设你正在使用InnoDB,你可以对这个计数器进行分区。

要做到这一点,只需添加一个分片列并使其成为主键的一部分,如

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

更新此计数器时,从1到10之间随机选择一个分片。当您阅读它时,请对要读取的id的所有分片进行SUM。这将使读取速度降低10倍,但它允许您在写入时缩放10倍。 (当然它不需要是10,你可以选择你想要的任何数字。)

答案 1 :(得分:1)

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

当然,这显然无法保证在服务器因任何原因而关闭的情况下,计数会持续存在。因此,如果它适用于任何重要的审计日志记录或丢失某些页面查看的任何问题,那么这将无效。

答案 2 :(得分:0)

使用MySQL的INSERT DELAYED....插入语句。它不会锁定,并且会在可能的情况下写入。

相关问题