为 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,通过某种注入器重载实现,但所有处理都在低于应用程序的级别层.
我已经设法获得了一个稳定的负载平衡前端服务器,它可以很好地水平扩展,但下一个瓶颈将是数据库。我发现 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,通过某种注入器重载实现,但所有处理都在低于应用程序的级别层.