【问题标题】:best apache server & mysql & php fpm configuration最好的 apache 服务器 & mysql & php fpm 配置
【发布时间】: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


【解决方案1】:

锁定表需要多长时间(以毫秒为单位)?

应该确保表是ENGINE=InnoDB,然后使用“事务”而不是表锁。如果您还没有到达那里,那么有一条“学习曲线”可以帮助您到达那里。但是,一旦你到了那里,你就放弃了表锁并将组 SQL 语句构建到事务中。

每个事务都是“原子的”。也就是说,它像互斥锁一样排除其他线程,但在 row 级别,而不是 table 级别。

确保表上有合适的索引。

请务必在事务中对SELECTs 使用FOR UPDATE(需要时)。

如果您需要“锁定”某物超过几秒钟,则需要重新考虑锁定机制。 (我们需要更多详细信息来帮助您。)

像你这样的问题很难“扔硬件”。虽然一个线程锁定了“表”,但没有其他线程可以接触该表 - 从而有效地导致单个 CPU 内核等。通过移动到“行锁定”,也许更多线程可以具有不同的行 同时锁定。

请提供在“锁定”期间运行的主要查询。我们或许可以为您提供更多提示。

如果您在具有 8GB 内存的“服务器”上从 MyISAM 切换到 InnoDB:

key_buffer_size = 20M
innodb_buffer_pool_size = 5G
max_allowed_packet=80M
open_files_limit=5000
max_connections=500

您一直在慢日志中收集数据;用pt-query-digest 总结一下。然后向我们展示前几个查询。

如果您有数百名用户,请不要将 Apache 设置设置为数千名。它浪费内存。 8GB 应该足够了,但在不必要的分配时就不行了。

这不是“可分析的”:

WHERE TIMESTAMPDIFF(SECOND, NOW(),
       (q.end_date - INTERVAL (q.execution_time*(q.threads-1)) SECOND))
       <= 0

因此,它不能使用索引,这可能会导致表扫描。

我不确定这是否会有所帮助:

WHERE q.end_date <=
       NOW() + INTERVAL (q.execution_time*(q.threads-1)) SECOND

请详细说明测试中需要3列。

交换,特别是如果它是 HDD,会大大降低性能。我对my.cnf 和 Apache 配置的建议旨在避免交换。

Max_used_connections 的值是多少?

【讨论】:

  • 嗨瑞克,非常感谢您的重播。我已经在用innoDB了,之前想知道怎么用事务,但是没有锁我造成了很多错误,我需要锁,原因如下:当一个玩家进行攻击任务时,例如有5000名士兵,没有锁定表士兵由于某种原因变成 10K(双倍),我需要锁定直到执行查询以禁止这种情况发生。
  • @Zeyad -(我添加了更多。)可能缺少FOR UPDATE
  • 我在帖子中添加了查询,非常感谢。我现在也会在 my.cnf 中尝试这些设置。我真的很感谢你的帮助..
  • 我是第二个建议。确保您了解 mysql 并发以及 select for update 的作用。使用 InnoDB 的一个重要原因是获得增加并发性的行级锁定。对于您拥有的每个查询,您应该运行 EXPLAIN query_sql,并评估结果。您需要确保涵盖所有 where 条件以及任何有意义的索引连接。
  • Max_used_connections 的值为 31 。我的服务器是 ssd ,我尝试使用随机值多次编辑 my.cnf 或从 github 处理(因为我不知道如何计算它们)但没有一个起作用,有些停止了整个服务器的工作..
猜你喜欢
  • 2012-12-23
  • 2015-05-12
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
相关资源
最近更新 更多