为 SilverStripe 网站添加只读副本

Add read replicas for SilverStripe website

我已经设法获得了一个稳定的负载平衡前端服务器,它可以很好地水平扩展,但下一个瓶颈将是数据库。我发现 blog post discussing scaling dbs horizontally however very little detail on it. I'm currently using PostgreSQL and so the only plugin 行不通。

我唯一的选择是创建自己的 HAProxy 还是重写 PostgreSQL 插件以允许与只读副本建立连接?

我的所有托管都使用 AWS

首先 - 我很乐意得到纠正!

仅快速浏览了 SilverStripe 3.5 站点中的一些 ORM classes,看起来 ORM 确实支持 多个数据库连接(请参阅 DB::get_conn 和名称参数)它是为特定的用例而设计的。也就是说,您可能有一个模块需要写入特定的数据库,所以这将允许它。

您想要的是在框架内对此提供本机和自动支持,以便所有读取都转到您的奴隶,而写入则转到您的主人。不幸的是,这看起来并不是开箱即用的。您可以通过使用注入器重载几个核心 SQL classes 来实现它。

如果您要尝试this answer 概述了如何将 select 语句与其余语句分开并 运行 它们通过不同的数据库连接器。

作为您如何使用 SQLSelect 实现此目的的快速示例,您会注意到它是可注射的,这意味着您可以轻松地重载它。

文件:mysite/_config/injector.yml

Injector:
  SQLSelect:
    class: ReadOnlySQLSelect

您需要向数据库注册一个新的数据库连接class:

文件:mysite/_config.php

$readDatabaseConfig = array(/** define your DB credentials here, as with the default $databaseConfig **/);
if (!DB::connect($readDatabaseConfig, 'default_read')) {
    user_error('Failed to connect to read replica DB!', E_USER_ERROR);
}

现在,重载 SQLSelect class 并替换其中调用 DB class 方法的部分。此 class 继承自 SQLExpression,即 class,其中包含您在此实例中实际关心的方法:

文件:mysite/code/ReadOnlySQLSelect.php

class ReadOnlySQLSelect extends SQLSelect
{
    public function sql(&$parameters = array())
    {
        // Changed from SQLExpression: third parameter passed as connection name
        $sql = DB::build_sql($this, $parameters, 'default_read');

        if (empty($sql)) {
            return null;
        }

        if ($this->replacementsOld) {
            $sql = str_replace($this->replacementsOld, $this->replacementsNew, $sql);
        }

        return $sql;
    }

    public function execute()
    {
        $sql = $this->sql($parameters);
        // Changed from SQLExpression: skip DB::prepared_query since it doesn't allow
        // you to provide the connection name - replace it with its contents instead.
        $conn = DB::get_conn('default_read');
        return $conn->preparedQuery($sql, $parameters);
    }
}

注意: SQLSelect::unlimitedRowCount 在技术上应该在它调用 DB::prepared_query 的地方被替换,因为准备好的查询方法调用 DB::get_conn 没有参数,所以总是 return 默认连接。您可以像上面使用的那样替换 DB::prepared_query 行:

$conn = DB::get_conn('default_read');
$result = $conn->preparedQuery($sql, $innerParameters);

如果您实施上述方法,还要将 new SQLSelect() 更改为 SQLSelect::create(),否则您最终会得到一些仍然会访问主服务器的查询,因为它会绕过您的 class 不使用注射器。

SQLConditionalExpression 中还有一个您也应该替换的实例 (::toSelect),但这可能会影响该 class 的其他子实现的查询转换,您赢了在没有 (A) 修复框架或 (B) 重载所有其他 SQL* classes.

的情况下,能够做很多事情

此时您应该拥有将 select 查询路由到 default_read 连接所需的一切。

基础设施

在基础架构方面,您应该能够通过 RDS 控制台设置只读副本。当您这样做时,它将为您的副本节点提供一个 DNS 端点,您可以在 _config.php 中使用它来配置与只读副本数据库的连接。


如果这对你有用,你应该为它创建一个模块并把它放在 GitHub - 这对将来的其他人肯定有用!

您还可以考虑向框架发出拉取请求,以向 DB::prepared_query 等方法添加额外参数以接受连接名称。

另外值得注意的是,如果您使用的是 mysqlnd 数据库适配器,您可以利用 read/write splitting,通过某种注入器重载实现,但所有处理都在低于应用程序的级别层.