【问题标题】:Add read replicas for SilverStripe website为 SilverStripe 网站添加只读副本
【发布时间】:2017-03-13 04:40:38
【问题描述】:

我已经设法获得了一个稳定的负载平衡前端服务器,它可以很好地水平扩展,但是下一个瓶颈将是数据库。有一个blog post 讨论了水平扩展 dbs,但是关于它的细节很少。我目前正在使用 PostgreSQL,所以我发现的唯一 plugin 不起作用。

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

我使用 AWS 进行所有托管

【问题讨论】:

    标签: php postgresql load-balancing silverstripe


    【解决方案1】:

    首先 - 我很乐意在这方面得到纠正!

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

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

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

    作为一个快速示例,说明如何使用 SQLSelect 实现此目的,您会注意到它是可注入的,这意味着您可以轻松地对其进行重载。

    文件:mysite/_config/injector.yml

    Injector:
      SQLSelect:
        class: ReadOnlySQLSelect
    

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

    文件: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 类并替换它调用 DB 类方法的部分。该类继承自 SQLExpression,该类包含您在此实例中实际关心的方法:

    文件: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 没有参数,所以总是返回默认连接。您可以将DB::prepared_query 替换为与上面相同的行:

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

    如果你实现了上面的方法,还要把new SQLSelect()改成SQLSelect::create(),否则你最终会得到一些仍然访问主服务器的查询,因为它会通过不使用注入器绕过你的类。

    SQLConditionalExpression 中还有一个实例,您也应该替换它 (::toSelect),但这可能会影响来自该类的其他子实现的查询转换,如果没有它,您将无法做很多事情(A) PRing 修复框架或 (B) 重载所有其他 SQL* 类。

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

    基础设施

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


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

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

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

    【讨论】:

    • 感谢您提供非常详细的答案。我认为您已经给了我足够的资源来继续编写一个模块来实现拆分。我已经将基础架构设置为测试,但当我看到不同的端点时,我意识到它不会开箱即用地进行负载平衡,因此正在寻找替代路线。
    • 我已经有一段时间没有使用 RDS 中的只读副本功能了,但我相信您可以设置多个应该由 AWS 进行负载平衡的只读副本。值得研究!无论哪种方式 - 拥有 a 只读副本至少会带来一些好处。
    • 所有我读过的都说使用 HA 代理,我相信它可以作为 AMI 使用。我认为在应用程序级别执行此操作会更好,因为您也可以拥有更多控制权,尤其是当它变成多区域时。
    • 嘿@Rudiger - 仅供参考,我一直在 SS4 上对此进行测试,但在这个阶段实际上不太可能工作,因为 SS 框架中仍然有很多手动的 DB::query() 语句。我发现由于使用 this 而不是 SQLSelect 等,目前发布页面失败
    • 谢谢 Robbie,虽然它不会很好地进行负载平衡,但是否可以将使用 SQLSelect 的任何内容路由到只读状态,并将任何手动 DB::query() 路由到默认值(即主服务器)?虽然不是最好的,但它仍然会比目前更具可扩展性。
    猜你喜欢
    • 1970-01-01
    • 2016-03-07
    • 2021-01-19
    • 2017-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 1970-01-01
    相关资源
    最近更新 更多