没有不值得去解决的问题,也没有不值得去学习的技术!

Implementing Server Health Checks in Yii 2.0

After changing the database password to an incorrect one, the response is a 500 error as follows, which is expected.

1. In the implementation of health checks in Yii 2.0, a colleague had previously implemented a version, but I felt it was necessary to make some adjustments.

PHP

        Yii::$app->db->open();
        if(!Yii::$app->redis->ping()) {
            throw new ServerErrorHttpException('error','Redis is unavailable.',500);
        } elseif(!Yii::$app->db->getIsActive()) {
            throw new ServerErrorHttpException('error','Database is unavailable.',500);
        } else {
            return 200;
        }

2. Upon analyzing the code, I found that Yii::$app->db->getIsActive() is unnecessary. I reviewed the source code of the respective methods. The implementation related to $this->pdo in getIsActive() already exists in open().

PHP

    /**
     * Establishes a DB connection.
     * It does nothing if a DB connection has already been established.
     * @throws Exception if connection fails
     */
    public function open()
    {
        if ($this->pdo !== null) {
            return;
        }

        if (!empty($this->masters)) {
            $db = $this->getMaster();
            if ($db !== null) {
                $this->pdo = $db->pdo;
                return;
            }

            throw new InvalidConfigException('None of the master DB servers is available.');
        }

        if (empty($this->dsn)) {
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
        }

        $token = 'Opening DB connection: ' . $this->dsn;
        $enableProfiling = $this->enableProfiling;
        try {
            if ($this->enableLogging) {
                Yii::info($token, __METHOD__);
            }

            if ($enableProfiling) {
                Yii::beginProfile($token, __METHOD__);
            }

            $this->pdo = $this->createPdoInstance();
            $this->initConnection();

            if ($enableProfiling) {
                Yii::endProfile($token, __METHOD__);
            }
        } catch (\PDOException $e) {
            if ($enableProfiling) {
                Yii::endProfile($token, __METHOD__);
            }

            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
        }
    }

    /**
     * Returns a value indicating whether the DB connection is established.
     * @return bool whether the DB connection is established
     */
    public function getIsActive()
    {
        return $this->pdo !== null;
    }

3. Analyzing the code, I found that we can use commands directly. Redis has many useful commands that can be run directly from a connection. ping is among the available Redis commands. This command is often used to test whether a connection is still active or to measure connection latency. See Figure 1.

Analyzing the code, I found that we can use commands directly. Redis has many useful commands that can be run directly from a connection. ping is among the available Redis commands. This command is often used to test whether a connection is still active or to measure connection latency.
Figure 1

4. The adjusted version of the health check implementation in Yii 2.0.

PHP

            Yii::$app->db->open();
            Yii::$app->redis->open();
            return 200;

5. After changing the database password to an incorrect one, the response is a 500 error as follows, which is expected. See Figure 2.

After changing the database password to an incorrect one, the response is a 500 error as follows, which is expected.
Figure 2
JSON

{
    "name": "Database Exception",
    "message": "SQLSTATE[HY000] [1045] Access denied for user 'pcs-api'@'localhost' (using password: YES)",
    "code": 1045,
    "type": "yii\\db\\Exception",
    "file": "E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\db\\Connection.php",
    "line": 637,
    "stack-trace": [
        "#0 E:\\wwwroot\\pcs-api\\api\\rests\\openconf\\CreateAction.php(58): yii\\db\\Connection->open()",
        "#1 [internal function]: api\\rests\\openconf\\CreateAction->run()",
        "#2 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
        "#3 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
        "#4 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)",
        "#5 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/openconf/cre...', Array)",
        "#6 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
        "#7 E:\\wwwroot\\pcs-api\\api\\web\\index.php(17): yii\\base\\Application->run()",
        "#8 {main}"
    ],
    "error-info": null,
    "previous": {
        "name": "Exception",
        "message": "SQLSTATE[HY000] [1045] Access denied for user 'pcs-api'@'localhost' (using password: YES)",
        "code": 1045,
        "type": "PDOException",
        "file": "E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\db\\Connection.php",
        "line": 705,
        "stack-trace": [
            "#0 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\db\\Connection.php(705): PDO->__construct('mysql:host=loca...', 'pcs-api', 'jwpuOHTSX5hOcuX...', NULL)",
            "#1 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\db\\Connection.php(626): yii\\db\\Connection->createPdoInstance()",
            "#2 E:\\wwwroot\\pcs-api\\api\\rests\\openconf\\CreateAction.php(58): yii\\db\\Connection->open()",
            "#3 [internal function]: api\\rests\\openconf\\CreateAction->run()",
            "#4 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
            "#5 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
            "#6 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)",
            "#7 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/openconf/cre...', Array)",
            "#8 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
            "#9 E:\\wwwroot\\pcs-api\\api\\web\\index.php(17): yii\\base\\Application->run()",
            "#10 {main}"
        ]
    }
}

6. After changing the Redis password to an incorrect one, the response is a 500 error as follows, which is expected. Replacing Yii::$app->redis->open(); with Yii::$app->redis->ping() yields the same response, because open() must be executed before running the ping command. See Figure 3.

After changing the Redis password to an incorrect one, the response is a 500 error as follows, which is expected. Replacing Yii::$app->redis->open(); with Yii::$app->redis->ping() yields the same response, because open() must be executed before running the ping command.
Figure 3
JSON

{
    "name": "Database Exception",
    "message": "Redis error: ERR Client sent AUTH, but no password is set\nRedis command was: AUTH 88888888",
    "code": 0,
    "type": "yii\\db\\Exception",
    "file": "E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php",
    "line": 821,
    "stack-trace": [
        "#0 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php(789): yii\\redis\\Connection->parseResponse(Array, '*2\\r\\n$4\\r\\nAUTH\\r\\n$...')",
        "#1 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php(772): yii\\redis\\Connection->sendCommandInternal('*2\\r\\n$4\\r\\nAUTH\\r\\n$...', Array)",
        "#2 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php(634): yii\\redis\\Connection->executeCommand('AUTH', Array)",
        "#3 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php(744): yii\\redis\\Connection->open()",
        "#4 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2-redis\\src\\Connection.php(709): yii\\redis\\Connection->executeCommand('PING', Array)",
        "#5 E:\\wwwroot\\pcs-api\\api\\rests\\openconf\\CreateAction.php(57): yii\\redis\\Connection->__call('ping', Array)",
        "#6 [internal function]: api\\rests\\openconf\\CreateAction->run()",
        "#7 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
        "#8 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
        "#9 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)",
        "#10 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/openconf/cre...', Array)",
        "#11 E:\\wwwroot\\pcs-api\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
        "#12 E:\\wwwroot\\pcs-api\\api\\web\\index.php(17): yii\\base\\Application->run()",
        "#13 {main}"
    ],
    "error-info": []
}

7. In summary, whether checking database or Redis connections, it is best to maintain a consistent detection method. Therefore, I uniformly adopted the open() method. Yii::$app->db->getIsActive() is unnecessary and a redundant implementation.

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系