我有一个Laravel应用程序,它使用了很多AJAX POST和GET请求(单页应用程序)。通过POST保存项目后,将发送GET请求以重新加载页面的部分并获取任何新数据。
使用Laravel connection configuration启用拆分的读写数据库连接后,该应用程序运行得非常快(从来没有想到这会是一个问题!)。它可以保存然后请求的速度如此之快,以至于RO数据库(仅落后22ms的时间)没有机会进行更新,最终我得到了旧信息。
我已经在数据库配置中启用了sticky
参数,我认为这可以缓解该问题,但是POST和GET请求是分开的,因此粘性消失了。
我可以重写大部分应用程序POST请求以正确的数据响应,但这不适用于一次重新加载许多组件,这是一项艰巨的工作,所以我认为这是最后的选择。
我的另一个想法是修改数据库getReadPdo(){...}
类中的$recordsModified
方法和Connection
值,以便将粘性保留在用户会话中最多1秒钟。我不确定这是否会导致速度或会话加载过多的其他问题,是否会引起更多问题。
是否有人曾经经历过此事或对如何解决问题有任何想法?
谢谢。
答案 0 :(得分: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-这是how the core Laravel sticky sessions are normally detected
如果当前查询不是SELECT
(很可能是数据库写入),则我们将会话变量“ force_pdo_write_until”设置为将来的1秒钟。
每次发送POST请求时,如果下一个GET请求位于上一个查询的1秒之内,则可以确保当前用户将使用RW DB连接并获得正确的结果。
更新(09/12/19):
事实证明,上面的解决方案实际上并没有真正修改数据库连接,它只是对任何请求增加了几毫秒的处理时间,因此看起来它在大约75%的时间里正常工作(因为数据库副本)滞后随负载而变化)。
最后,我决定更深入一点,直接覆盖DB连接类并修改相关功能。我的Laravel实例使用MySQL,因此我覆盖了Illuminate\Database\MySqlConnection
类。这个新类是通过新的服务提供商注册的,该服务提供商又通过配置加载了该服务。
我已经复制了下面使用的配置和文件,以使新开发者都更容易理解。如果您要直接复制这些文件,请确保还将“ sticky_by_session”标志也添加到连接配置中。
config / database.php
'connections' => [
'mysql' => [
'sticky' => true,
'sticky_by_session' => true,
...
],
],
config / app.php
'providers' => [
App\Providers\DatabaseServiceProvider::class
...
],
app / Providers / DatabaseServiceProvider.php
<?php
namespace App\Providers;
use App\Database\MySqlConnection;
use Illuminate\Database\Connection;
use Illuminate\Support\ServiceProvider;
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 App\Database;
use Illuminate\Database\MySqlConnection 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竞争条件或影响。