【发布时间】:2021-12-29 19:13:13
【问题描述】:
我阅读了很多关于这些主题的内容,但由于我不是专家,所以我无法获得有用的东西
我正在使用 whm 在 php 上运行基于多线程(互斥)的游戏, 它通过锁定 db 中的单个表来工作,
如果 table = 1 ,那么它不会处理请求,直到它再次回到 = 0 (在第一个线程完成之后)
我的问题是,当有超过 100 名玩家同时在玩时,他们每个人通常会每秒钟向 db 发出 10-40 个请求
但是当我们有这么多的请求时,会出现延迟(最多 3 分钟),以便按 id 顺序处理每个线程 .. 这非常慢 ..
我有一个 vps 服务器试图通过获得大量 rams 和 cpu 来改善这一点,但没有任何改变..
我的代码是:
public function processQueue($type = 3, $playerId = 0)
{
global $gameConfig;
$this->load_model('Mutex', 'mutex');
$this->mutex->releaseOnTimeout();
if ($this->mutex->lock()) {
$this->processTaskQueue($type, $playerId);
// to make it weekly put "/2" after it
$row = db::get_row("SELECT gs.cur_week w1, CEIL((TO_DAYS(NOW())-TO_DAYS(gs.start_date))) w2 FROM g_settings gs");
if (($row['w2'] - $row['w1']) >= 1) {
db::query("UPDATE g_settings gs SET gs.cur_week=:cur", array(
'cur' => intval($row['w2'])
));
$allP = db::get_all("SELECT p.id FROM p_players p WHERE p.total_people_count>500 ");
$Ids2 = "";
foreach($allP as $Ids)
{
$Ids2 .= ($Ids2 == "" ) ? $Ids['id'] : ",".$Ids['id'];
}
$Ids2 = "(".$Ids2.")";
db2::query("UPDATE p_players p SET p.gold_num=p.gold_num+:gold WHERE p.id IN $Ids2",array('gold' =>200));
$this->setWeeklyMedals(intval($row['w2']));
}
$this->mutex->release();
}
}
// mutex lock
define("__QS_LOCK_FS_", MODELS_DIR . "lock");
class Mutex_Model extends Model
{
public function lock()
{
if (0 < db::count("UPDATE g_settings gs SET gs.qlocked=1, qlocked_date=NOW() WHERE gs.qlocked=0") && ($fp = fopen(__QS_LOCK_FS_, "r")) != FALSE) {
if (flock($fp, LOCK_EX)) {
fclose($fp);
return TRUE;
}
fclose($fp);
}
return FALSE;
}
public function release()
{
$this->_releaseInternal();
db::query("UPDATE g_settings gs SET gs.qlocked=0");
}
public function releaseOnTimeout()
{
if (0 < db::count("UPDATE g_settings gs SET \r\n\t\t\t\tgs.qlocked=0\r\n\t\t\tWHERE\r\n\t\t\t\tgs.qlocked=1\r\n\t\t\t\tAND\r\n\t\t\t\tTIME_TO_SEC(TIMEDIFF(NOW(), gs.qlocked_date)) > 120")) {
$this->_releaseInternal();
}
}
public function _releaseInternal()
{
if (($fp = fopen(__QS_LOCK_FS_, "r")) != FALSE) {
flock($fp, LOCK_UN);
fclose($fp);
}
}
服务器信息:
服务器负载 0.656738(4 个 CPU)
内存使用率 48.06%(7,814,064 中的 3,755,752)
掉期使用率 25.71%(4,048,572 中的 1,040,692)
我尝试过但没有成功:
- 编辑 my.cnf
- 多次更改 apache 配置
- 多次更改 php fpm 配置
- 将 mpm 更改为 perfork、event、worker
我目前的设置:
启动服务器 [500] 最少备用服务器 [500]
最大备用服务器 [2000]
服务器限制 [5000]
5000 最大请求工作人员 [5000]
php fpm 设置:
最大请求数:55000(尝试 20 - 55000 个随机值) 最大孩子数:10000(尝试 30 - 10000 个随机值) 进程空闲超时:700(尝试从 10 到 700 个随机值)
我的 my.cnf 文件:
[root@server ~]# cat /etc/my.cnf
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
[mysqld]
disable-log-bin=1
default-authentication-plugin=mysql_native_password
performance-schema=0
sql_mode=""
innodb_autoinc_lock_mode=2
binlog_format=row
transaction-isolation=READ-COMMITTED
#Slow queries configuration
slow_query_log = 1
slow-query_log_file = /var/log/mysql-slow.log
long_query_time = 2
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
innodb_buffer_pool_size=30G
max_allowed_packet=568435456
open_files_limit=500000
# innodb_file_per_table=1
bind-address=127.0.0.1
mysqlx=0
max_connections=100000
对不起,我写了很多,我根本不是专家,我正在尽力而为,我保证我读了很多,然后写了很多对我没有用的东西,我需要的是让线程处理更快地完成排队的人不耽误
//编辑添加锁定工作的主要查询
public function processTaskQueue($type, $playerId)
{
$result = db::get_all("SELECT q.id, q.player_id, q.village_id, q.to_player_id, q.to_village_id, q.proc_type, q.building_id, q.proc_params, q.threads, q.execution_time, TIMESTAMPDIFF(SECOND, NOW(),q.end_date) remainingTimeInSeconds FROM p_queue q WHERE TIMESTAMPDIFF(SECOND, NOW(),(q.end_date - INTERVAL (q.execution_time*(q.threads-1)) SECOND)) <= 0 ORDER BY TIMESTAMPDIFF(SECOND, NOW(),(q.end_date - INTERVAL (q.execution_time*(q.threads-1)) SECOND)) ASC");
foreach ($result as $resultRow) {
$remain = $resultRow['remainingTimeInSeconds'];
if ($remain < 0) {
$remain = 0;
}
$resultRow['threads_completed_num'] = $resultRow['execution_time'] <= 0 ? $resultRow['threads'] : floor(($resultRow['threads'] * $resultRow['execution_time'] - $remain) / $resultRow['execution_time']);
if ($this->processTask($resultRow)) {
unset($result);
$this->processQueue($type, $playerId);
break;
}
}
unset($result);
}
public function processTask($taskRow)
{
$customAction = FALSE;
switch ($taskRow['proc_type']) {
case QS_ACCOUNT_DELETE:
{
$this->deletePlayer($taskRow['player_id']);
break;
}
case QS_BUILD_CREATEUPGRADE:
{
$customAction = $this->executeBuildingTask($taskRow);
break;
}
case QS_BUILD_DROP:
{
$customAction = $this->executeBuildingDropTask($taskRow);
break;
}
case QS_TROOP_RESEARCH:
{
}
case QS_TROOP_UPGRADE_ATTACK:
{
}
case QS_TROOP_UPGRADE_DEFENSE:
{
$this->executeTroopUpgradeTask($taskRow);
break;
}
case QS_TROOP_TRAINING:
{
$this->executeTroopTrainingTask($taskRow);
break;
}
case QS_TROOP_TRAINING_HERO:
{
$this->executeHeroTask($taskRow);
break;
}
case QS_TOWNHALL_CELEBRATION:
{
$this->executeCelebrationTask($taskRow);
break;
}
case QS_MERCHANT_GO:
{
$customAction = $this->executeMerchantTask($taskRow);
break;
}
case QS_MERCHANT_BACK:
{
if ($taskRow['building_id'] >= 1) {
$this->returnMerchantTask($taskRow);
}
break;
}
case QS_WAR_REINFORCE:
{
}
case QS_WAR_ATTACK:
{
}
case QS_WAR_ATTACK_PLUNDER:
{
}
case QS_WAR_ATTACK_SPY:
{
}
case QS_CREATEVILLAGE:
{
$customAction = $this->executeWarTask($taskRow);
break;
}
case QS_LEAVEOASIS:
{
$this->executeLeaveOasisTask($taskRow);
break;
}
case QS_PLUS1:
{
db::query('UPDATE p_players p SET p.active_plus_account=0 WHERE p.id=:id', array(
'id' => intval($taskRow['player_id'])
));
break;
}
case QS_PLUS2:
{
$this->executePlusTask($taskRow, 1);
break;
}
case QS_PLUS3:
{
$this->executePlusTask($taskRow, 2);
break;
}
case QS_PLUS4:
{
$this->executePlusTask($taskRow, 3);
break;
}
case QS_PLUS5:
{
$this->executePlusTask($taskRow, 4);
break;
}
case QS_TATAR_RAISE:
{
$this->load_model('Artefacts', 'A');
$this->A->createTatarVillages();
break;
}
case QS_SITE_RESET:
{
$this->load_model('Install', 'm');
$this->m->processSetup($GLOBALS['SetupMetadata']['map_size']);
$customAction = TRUE;
break;
}
case QS_CROP_DELETE:
{
$this->load_model('Crop', 'c');
$customAction = $this->c->deleteCrop($taskRow);
break;
}
case QS_ARTEFACTS_RAISE:
{
$this->load_model('Artefacts', 'A');
$this->A->createArtefacts();
break;
}
}
if (!$customAction) {
$remaining_thread = $taskRow['threads'] - $taskRow['threads_completed_num'];
if ($remaining_thread <= 0) {
db::query("DELETE FROM p_queue WHERE id=:id", array(
'id' => intval($taskRow['id'])
));
} else {
db::query("UPDATE p_queue q SET q.threads=:th WHERE q.id=:id", array(
'th' => intval($remaining_thread),
'id' => intval($taskRow['id'])
));
}
}
return $customAction;
}
非常感谢
【问题讨论】:
-
我没有尝试过,但它可能是锁定机制 - 尝试使用信号量之类的东西,它应该更轻量级。
-
快速浏览一下,似乎 SQL 逻辑可以作为存储过程移动到数据库服务器,以便数据库可以在必要时处理任何写锁定。它也可以返回
w2值,或者可以发出后续的 SELECT。 -
我也不确定您为什么同时使用文件系统锁和人工数据库锁?但无论如何,您希望在锁内执行尽可能少的逻辑,即使这意味着在同一请求上使用多个锁(如果可能的话)。
-
谢谢你们的热心回复,有什么可以帮我解决这个问题吗?即使你要我付钱..我真的很痛苦..
-
因此,如果 p_queue 增长,性能会下降,但是,主要问题是您已经序列化了整个游戏计算。您需要回到开始尝试锁定之前的位置,并返回您的逻辑。让查询正常工作,也许使用 SELECT FOR UPDATE 的新技术。
标签: php mysql multithreading apache whm