我有一个Laravel应用程序,它使用了很多AJAX POST和GET请求(单页应用程序)。通过 POST 保存项目后,将发送 GET 请求以重新加载页面的某些部分并获取任何新数据。
使用 Laravel 连接配置启用拆分读写数据库连接后,应用程序运行速度非常快(从未想过这会是一个问题!它保存然后请求的速度如此之快,以至于 RO 数据库(仅报告落后 22 毫秒)没有机会更新,我最终得到了旧信息。
我已经在数据库配置中启用了sticky
参数,我认为这可以缓解问题,但 POST 和 GET 请求是分开的,因此粘性丢失了。
我可以重写大部分应用程序 POST 请求使用正确的数据响应,但这不适用于一次重新加载许多组件,并且是一项艰巨的工作,因此我认为这是最后的手段。
我的另一个想法是修改getReadPdo(){...}
方法和$recordsModified
数据库Connection
类中的值,以便粘性在用户会话中保存长达 1 秒。我不确定这是否会导致速度或会话加载过多的任何进一步问题,从而导致更多问题。
以前有没有人经历过这种情况,或者对如何解决这个问题有任何想法?
提前谢谢。
以为我会更新并回答这个问题,以防其他人遇到同样的问题。
这不是一个完美的解决方案,但在过去一周左右的时间里效果很好。
在AppServiceProvider
boot()
方法中,我添加了以下内容
DB::listen(function ($query) {
if (strpos($query->sql, 'select') !== FALSE) {
if (time() < session('force_pdo_write_until')) {
DB::connection()->recordsHaveBeenModified(true);
}
} else {
session(['force_pdo_write_until' => time() + 1]);
}
});
简而言之,这会侦听每个数据库查询。如果当前查询是SELECT
(数据库读取),我们会检查用户会话中的"force_pdo_write_until"键是否具有大于当前时间的时间戳。如果是,我们利用recordsHaveBeenModified()
方法诱使当前数据库连接使用 ReadPDO - 这就是通常检测核心 Laravel 粘性会话的方式
如果当前查询不是SELECT
(很可能是数据库写入),我们将将来将"force_pdo_write_until"的会话变量设置为 1 秒。
每当发送 POST 请求时,如果下一个 GET 请求在上一个查询的 1 秒内,我们可以确定当前用户将使用 RW DB 连接并获得正确的结果。
更新(09/12/19):
事实证明,上面的解决方案实际上根本没有修改数据库连接,它只是为任何请求增加了几毫秒的处理时间,所以看起来它大约工作了 75% 的时间(因为数据库副本滞后根据负载波动)。
最后,我决定更深入一点,直接覆盖数据库连接类并修改相关函数。我的Laravel实例使用MySQL,所以我覆盖了IlluminateDatabaseMySqlConnection
类。这个新类是通过新的服务提供程序注册的,而服务提供程序又通过配置加载。
我复制了下面使用的配置和文件,以便任何新开发人员更容易理解。如果要直接复制这些,请确保还将"sticky_by_session"标志添加到连接配置中。
配置/数据库.php
'connections' => [
'mysql' => [
'sticky' => true,
'sticky_by_session' => true,
...
],
],
配置/应用.php
'providers' => [
AppProvidersDatabaseServiceProvider::class
...
],
app/Providers/DatabaseServiceProvider.php
<?php
namespace AppProviders;
use AppDatabaseMySqlConnection;
use IlluminateDatabaseConnection;
use IlluminateSupportServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
if (config('database.connections.mysql.sticky_by_session')) {
Connection::resolverFor('mysql', function ($connection, $database, $prefix, $config) {
return new MySqlConnection($connection, $database, $prefix, $config);
});
}
}
}
app/Database/MySqlConnection.php
<?php
namespace AppDatabase;
use IlluminateDatabaseMySqlConnection as BaseMysqlConnection;
class MySqlConnection extends BaseMysqlConnection
{
public function recordsHaveBeenModified($value = true)
{
session(['force_pdo_write_until' => time() + 1]);
parent::recordsHaveBeenModified($value);
}
public function select($query, $bindings = [], $useReadPdo = true)
{
if (time() < session('force_pdo_write_until')) {
return parent::select($query, $bindings, false);
}
return parent::select($query, $bindings, $useReadPdo);
}
}
在recordsHaveBeenModified()
中,我们只是添加一个会话变量供以后使用。如前所述,此方法由正常的Laravel粘性会话检测使用。
在select()
内部,我们检查会话变量是否在不到一秒前设置。如果是这样,我们会手动强制请求使用 RW 连接,否则照常继续。
现在我们直接修改请求,我还没有看到副本滞后的任何 RO 竞争条件或影响。
我已经作为一个软件包发布了!
- mpyw/laravel-cached-database-stickiness:保证同一用户连续请求的数据库粘性
安装
composer require mpyw/laravel-cached-database-stickiness
默认实现由
ConnectionServiceProvider
提供,但是,包发现不可用。 请注意,您必须自己在config/app.php
中注册。<?php return [ /* ... */ 'providers' => [ /* ... */ MpywLaravelCachedDatabaseStickinessConnectionServiceProvider::class, /* ... */ ], /* ... */ ];
仅此而已!所有问题都将得到解决。