У меня есть приложение Laravel, которое использует много запросов AJAX POST и GET (одностраничное приложение). После сохранения элемента через POST отправляется запрос GET для перезагрузки частей страницы и получения любых новых данных.
После включения разделенных подключений к базе данных для чтения и записи с использованием Конфигурация соединения Laravel приложение работает невероятно быстро (никогда не думал, что это может быть проблемой!). Он сохраняет, а затем запрашивает так быстро, что база данных RO (с отставанием всего в 22 мс) не имеет возможности обновиться, и я получаю старую информацию.
Я включил параметр sticky
в конфигурации базы данных, который, как я думал, смягчит проблему, но запросы POST и GET разделены, поэтому прилипание теряется.
Я мог бы переписать большую часть POST-запросов приложений, отвечающих правильными данными, но это не работает для перезагрузки многих компонентов одновременно и требует огромной работы, поэтому я рассматриваю это как последнее средство.
Еще одна идея, которая у меня была, заключалась в том, чтобы изменить метод getReadPdo(){...}
и значение $recordsModified
внутри класса базы данных Connection
, чтобы прилипание сохранялось в сеансе пользователя до 1 секунды. Я не был уверен, что это вызовет какие-либо дополнительные проблемы со скоростью или чрезмерной загрузкой сеанса, что вызовет больше проблем.
Кто-нибудь сталкивался с этим раньше или есть какие-либо идеи о том, как решить эту проблему?
Заранее спасибо.
Прирост производительности по всему приложению за счет разделения огромен. У нас довольно много периодов высокой нагрузки, и разделение нагрузки означает, что мы можем легко горизонтально масштабировать базы данных RO без перегрузки RW. Число миллисекунд, которое я дал, было задержкой данных между базой данных RW и RO, а не приростом скорости. Спасибо за комментарий!
горизонтальное масштабирование швов здесь является неправильным определением, также я не уверен, что означает RO/RW в этом контексте..? Но, скорее всего, я неправильно понимаю настройку сервера(ов) вашего приложения/базы данных... Можете ли вы показать некоторый лаваральный код, в котором вы определили соединения и как вы используете его в своем приложении.. Возможно, другие также получат лучшее впечатление и могут увидеть лучшее путь или решение проблемы..
... также, если я думаю о горизонтальном масштабировании с базами данных MySQL.. я думаю о Кластер MySQL, который также может быть хорошим решением вашей проблемы..
Думал, что обновлю и отвечу на это, если кто-то еще столкнется с той же проблемой.
Это не идеальное решение, но оно хорошо сработало за последнюю неделю или около того.
Внутри метода 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» внутри пользовательского сеанса отметку времени, превышающую текущее время. Если это так, мы обманываем текущее соединение с БД, используя ReadPDO, используя метод recordsHaveBeenModified()
— это как обычно обнаруживаются основные липкие сессии Laravel.
Если текущий запрос не является SELECT
(скорее всего, запись в БД), мы устанавливаем переменную сеанса для «force_pdo_write_until» на 1 секунду в будущем.
Каждый раз, когда отправляется запрос POST, если следующий запрос GET находится в пределах 1 секунды от предыдущего запроса, мы можем быть уверены, что текущий пользователь будет использовать соединение RW DB и получит правильные результаты.
Обновлять (12.09.19):
Оказывается, приведенное выше решение на самом деле вообще не изменяет соединение с БД, оно просто добавляет несколько миллисекунд времени обработки к любому запросу, поэтому выглядело так, как будто оно работает примерно в 75% случаев (поскольку задержка реплики БД колеблется в зависимости от в процессе).
В конце концов я решил пойти немного глубже и напрямую переопределить класс соединения с БД и изменить соответствующие функции. Мои экземпляры 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 или эффектов от задержки реплики.
Я опубликовал как пакет!
Installing
composer require mpyw/laravel-cached-database-stickiness
The default implementation is provided by
ConnectionServiceProvider
, however, package discovery is not available. Be careful that you MUST register it inconfig/app.php
by yourself.<?php return [ /* ... */ 'providers' => [ /* ... */ Mpyw\LaravelCachedDatabaseStickiness\ConnectionServiceProvider::class, /* ... */ ], /* ... */ ];
Это все! Все проблемы будут решены.
каков прирост производительности, если вы разделяете или не разделяете соединения с базами данных? если речь идет о «миллисекундах», не тратьте часы на размышления, чтобы решить эту проблему. вызвать другие ошибки где-то еще в коде.