PHP on pageload、MYSQL或cronjob—用于检查时间是否已过


PHP on pageload, MYSQL, or cronjob - which to use for checking if time has passed?

我正在制作一个检查表,在该检查表中,项目可以在一段时间后再次单击。每个用户(可能高达100万,但可能在10000至100000之间)的清单上将有多达200个项目(可能在不同的ajax选项卡上分成不到20个的卡盘),这些项目都会在不同的时间间隔后更新——有些在2分30秒,有些在1小时,有些在20小时,以及在特定时间而不是间隔重置的棘手问题(我认为是针对特定时间项的cronjob)。

我的数据库行看起来像:

---------------------------------------------
| UserID |  D1  |  D2  |  D3  |  D4  | D150 |
---------------------------------------------
| 345    | time | time | time | time | time |
| 7294   | time | time | time | time | time |
| 2385   | time | time | time | time | time |
---------------------------------------------

我计划用这样的东西来节省重置时间:

mysql_query ("INSERT INTO checklists (D1) 
VALUES ((SYSDATE() + INTERVAL 20 HOUR))") 
or die (mysql_error()); 

我认为使用SYSDATE()会比使用NOW()更好,因为我读到NOW(。此处提供相关信息:https://coderwall.com/p/eslwuw/why-not-to-use-now-in-mysql.精确到毫秒在这里并不重要,但精确到秒才重要。

那么,在我用上面的代码将重置时间保存到数据库中之后,在页面上显示准确的最新检查表的最有效方法是什么?

我是在pageload上使用SELECT * FROM checklists WHERE D1 < NOW()和UserID来限制搜索,还是在pageload中使用某种PHP脚本,或者每分钟运行几次cronjob(我怀疑这是一个合适的方法,但我认为无论如何都应该包括它)?

哪种检查方法更适合快速加载页面?哪个会给服务器带来更大的压力?

最好有100个不同的表,将列表分成块来匹配选项卡内容,如:

-----------------    -----------------    -----------------         
| UserID |  D1  |    | UserID |  D2  |    | UserID |  D10 |        
-----------------    -----------------    -----------------         
| 345    | time |    | 345    | time |    | 345    | time |
| 7294   | time |    | 7294   | time |    | 7294   | time |
| 2385   | time |    | 2385   | time |    | 2385   | time |
-----------------    -----------------    -----------------

更多信息:
用户页面将有选项卡,每个选项卡上有10-20个检查表项目。
用户将单击一个按钮,显示他们完成了一项任务,此时重置时间将添加到数据库中
当他们重新加载选项卡时,它将显示是否有任何检查表项目可以再次单击。

"当他们重新加载选项卡时,它将显示是否有任何检查表项目可以再次单击。"——让我们从改进这一点开始。让我们去掉重载选项卡。每个检查表项目的"剩余时间"可以在页面加载时加载到页面上。一个相当简单的JavaScript函数可以每秒唤醒一次,运行项目(即使是200个项目),检查哪些项目"超时",并将项目从红色更改为绿色(或者您希望指示"现在是时候了!")。同时,每个项目上甚至可以显示一个倒计时计时器。此外,请注意,将负担推到用户的浏览器上会减轻服务器的巨大负载。

一旦用户单击该项,然后返回服务器,服务器会返回MySQL以重置该计时器。

那么,回到数据库设计

方案A:每个用户一行;200列,每项一列。UPDATE tbl SET item123 = ... WHERE user_id = 9876;但是,您必须"构造"SQL,因为需要构造列名:item123

方案B:每个项目每个用户一行。UPDATE tbl SET next = ... WHERE user_id = 9876 AND item_num = 123

任何一个方案都是"有效的";每分钟处理5公里以上的更新应该很容易。计划B将占用更多磁盘空间

但是,还有另一个需要担心的查询:加载页面。据我所知,这涉及到:给定一个user_id,为该用户获取200(或仅20?)个定时器。

SELECT * FROM tbl WHERE user_id = 9876;

计划A(如上所述):SELECT将获取一个宽行。

计划B:SELECT将获取200(或20)行。

尽管如此,两者都是"高效的",但有一个附带条件:

计划A的表格需要PRIMARY KEY(user_id)

计划B的表格需要PRIMARY KEY(user_id, item_num)

请记住,cronjob无法访问网页。因此,这种设计被"扭转"了。

现在针对一些数字。如果你有1000个用户在任何给定的时间"在线",他们平均每分钟点击一次"项目",。。。这是构建重新加载页面的1K更新和1K选择。2公里/分钟完全在我提到的5公里之内。然而,它正在突破极限——想想流量的峰值等。因此,可能需要在如何实现方面格外小心。如果这些数字有意义的话,我们可以做到这一点。

编辑

由于并非所有用户都拥有所有项目,让我们讨论一下不需要的项目占用(或不占用)空间的情况

  • 计划A:每个NULL列都有一个小开销
  • 方案B:您甚至不需要有未使用的行。也就是说,一个用户将拥有多达200行。因此,没有"浪费"空间

增加"200"怎么办

  • 计划A:停机以执行ALTER TABLE<这是a计划的一大缺点>
  • 计划B:不需要更改模式

RAM大小和数据集大小

如果所有内容都可以缓存在RAM中,那么唯一的I/O就是写入事务日志(InnoDB),并最终将数据持久化到磁盘。即使活动用户的数量使得他们的行可以缓存在RAM中直到他们注销,关于I/O的评论也是大致正确的。

如果您的活动用户数量超过了可以有效缓存的数量,那么该进程将成为I/O绑定,您将无法跟上。这可以通过(a)更多的RAM(并增加innodb_buffer_pool_size)或(b)"分片"(将用户分散在多台机器上)来解决。

1GB虚拟机意味着innodb_buffer_pool_size应该只有100M,但它可能足够大,可以处理您计划的活动负载。(正如你所说,数字是模糊的。)

多个数据库

一个数据库中有一个表,而不是PARTITIONed。按数据库、表或PARTITION进行拆分没有任何优势(据我所见)。如果你长了很多,那么碎片(上面提到的)可能会很有用。尽管如此,我还是会先加强一台服务器:两个硬件改进:更多的RAM和带写缓存的RAID条带。(这些还不需要。在决定何时/是否加强硬件之前,最好先了解活动用户的指标、点击率等。)

200个连接限制

这是max_connections=200吗?还是最大用户连接数?你使用的云服务不会让你增加吗?

建议你立即断开连接,而不是挂断连接,因为用户在一分钟内都不会接通。您可以通过将wait_timeout(或者它是interactive_wait_timeout?)设置为10秒来强制执行此操作。

在添加"实例"之前,让我们尝试在这个级别上解决问题。

您不能在一分钟内多次搜索1M*200个项目。所以,你需要另一种方法来完成任务。

(我认为NOW()与SYS_DATE()是问题最小的。)

我希望您使用的是InnoDB,而不是MyISAM。(MyISAM使用表锁,您将无法跟上。)

让我们考虑

CREATE TABLE Foo (
    ts TIMESTAMP ... NOT NULL,
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id MEDIUMINT UNSIGNED  NOT NULL,
    d TINYINT UNSIGNED NOT NULL  COMMENT 'D1..D200',
    PRIMARY KEY(timestamp, id),
    INDEX(id)   -- to make AUTO_INCREMENT happy
) ENGINE=InnoDB;

这样,所有的查询都将在表的"开头":

SELECT d, GROUP_CONCAT(user_id)
    FROM Foo
    WHERE ts < NOW()
    GROUP BY d;

这将获得页面的所有数据,并且非常有效。注意,在InnoDB中,数据被"集群"在PRIMARY KEY上,因此我确保ts是PK的开始。

由于此表将有200M个,因此它将比您建议的表大得多(201M个cells)。但我希望专注于SELECTs将超过规模成本。

由于我不了解数据是如何/何时更新的,我假设所有200个数字都会更新(重复更新?),一次更新1个(或一次更新所有200个?),因此更新工作可能会出现问题。

另一件需要认真考虑的事情是:批量更新。使用普通磁盘和默认配置,每秒只能执行100次更新。要达到1000秒通常并不太难。但是你需要多少?您的模式或我的模式都存在此更新性能问题(以不同的形式)。

建议您"计算"以计算每秒更新次数和每秒读取行数。

您应该考虑以下几点:

您的数据库不应依赖外部运行才能再次可用。如果您的外部运行由于任何原因失败,它将以静默方式失败,并且您的应用程序将锁定,而不会向用户发送任何消息。

此外,您应该使用合理的数据库布局。大多数人的清单上不会有150项,有些人可能想要151项。

我建议使用以下数据表布局:

ItemId INT NOT NULL PRIMARY_KEY auto_increment
UserId INT NOT NULL FOREIGN KEY,
ItemName VARCHAR (100),
Interval TIME NULL, -- the interval at which the check may be set again
NextCheckAllowed DATETIME NULL -- the datetime the check may be set again

现在,每当有人在列表中勾选某个项目时,您都可以将itemId发送到服务器,并将该特定项目的NextCheckAllowed更新为TIMEADD(NOW(),Interval)。每当有人读到清单时,你就会做一些类似的事情

SELECT *,NextCheckAllowed>NOW() AS IsChecked 
FROM dataTable WHERE UserId=@UserId

您将能够为用户提供一个所有任务的列表,并带有一个布尔检查/未检查标记。

此外,您应该考虑在客户端进行大部分计算,并且只在请求更新数据库时检查服务器端。客户端计算很便宜,即使有大约一百万人,但它们可能是伪造的,所以在您更改服务器端的任何内容之前(或者在您假设请求数据的人具有UserId@UserId之前),您必须运行后台检查是否真的允许此操作。