Php 为SilverStripe网站添加读取副本

Php 为SilverStripe网站添加读取副本,php,postgresql,load-balancing,silverstripe,Php,Postgresql,Load Balancing,Silverstripe,我已经设法获得了一个稳定的负载平衡前端服务器,它可以很好地横向扩展,但是下一个瓶颈将是数据库。有一次讨论了如何水平扩展dbs,但没有多少细节。我目前正在使用PostgreSQL,因此我发现的唯一一种方法不起作用 我唯一的选择是创建自己的HAProxy还是重写PostgreSQL插件以允许与读取副本连接 首先,我的所有主机都使用AWS,我希望得到纠正 只快速浏览了SilverStripe 3.5站点中的一些ORM类,看起来ORM确实支持多个数据库连接(请参见DB::get_conn,并使用参数表示

我已经设法获得了一个稳定的负载平衡前端服务器,它可以很好地横向扩展,但是下一个瓶颈将是数据库。有一次讨论了如何水平扩展dbs,但没有多少细节。我目前正在使用PostgreSQL,因此我发现的唯一一种方法不起作用

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


首先,我的所有主机都使用AWS,我希望得到纠正

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

您需要的是在框架内对此的本机和自动支持,以便所有读操作都转到您的从机,而写操作转到您的主机。不幸的是,它看起来不像是从盒子里出来的。您可以通过使用注入器重载几个核心SQL类来实现它

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

作为一个使用
SQLSelect
实现这一点的快速示例,您会注意到它是可注入的,这意味着您可以轻松地重载它

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

文件:mysite/code/ReadOnlySQLSelect.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);
}
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\u query
的地方被替换,因为prepared query方法调用
DB::get\u conn
时没有参数,所以总是返回默认连接。您可以将
DB::prepared_query
行替换为与上面使用的相同:

$conn = DB::get_conn('default_read');
$result = $conn->preparedQuery($sql, $innerParameters);
如果您实现了上述方法,还可以将
new SQLSelect()
更改为
SQLSelect::create()
,否则您将得到一些查询,这些查询仍然会命中主服务器,因为它将通过不使用注入器绕过您的类

SQLConditionalExpression
中还有一个实例,您也应该替换它(
::toSelect
),但这可能会影响该类的其他子实现的查询转换,如果没有(A)对框架进行修复或(B)的话,您将无法完成很多工作重载所有其他SQL*类

此时,您应该拥有将select查询路由到
default\u read
连接所需的一切

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


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

您还可以考虑对框架进行拉请求,以将附加的参数添加到诸如“代码> d::PravaReSQL查询< /代码>”的方法中,以接受连接名。


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

感谢您提供了非常详细的答案。我想你已经给了我足够的时间去写一个模块来实现拆分。我将基础设施设置为一个测试,但当我看到不同的端点时,我意识到它无法实现开箱即用的负载平衡,因此正在寻找替代路线。我已经有一段时间没有在RDS中使用读取副本功能了,但我相信您可以设置多个读取副本,这些副本应该由AWS进行负载平衡。值得调查!无论哪种方式——拥有一个读取副本至少会带来一些好处。我所读到的都是使用HA代理,我相信它可以作为AMI使用。我认为在应用程序级别这样做会更好,因为你可以有更多的控制权,尤其是当它变成多区域时。嘿@Rudiger-仅供参考,我一直在SS4上测试这一点,实际上在这个阶段不太可能工作,因为SS框架中仍然有很多手动
DB::query()
语句。我发现,由于使用了这个而不是SQLSelect etcThanks Robbie,发布页面目前失败了,尽管它不会很好地实现负载平衡,但是使用
SQLSelect
的任何东西都可以路由到只读,并且任何手动
DB::query()
都可以路由到默认值(即主机)?虽然不是最好的,但它仍然比目前更具可扩展性。