Условия гонки в Laravel при использовании разделенных подключений к базе данных «чтение» и «запись»

У меня есть приложение Laravel, которое использует много запросов AJAX POST и GET (одностраничное приложение). После сохранения элемента через POST отправляется запрос GET для перезагрузки частей страницы и получения любых новых данных.

После включения разделенных подключений к базе данных для чтения и записи с использованием Конфигурация соединения Laravel приложение работает невероятно быстро (никогда не думал, что это может быть проблемой!). Он сохраняет, а затем запрашивает так быстро, что база данных RO (с отставанием всего в 22 мс) не имеет возможности обновиться, и я получаю старую информацию.

Я включил параметр sticky в конфигурации базы данных, который, как я думал, смягчит проблему, но запросы POST и GET разделены, поэтому прилипание теряется.

Я мог бы переписать большую часть POST-запросов приложений, отвечающих правильными данными, но это не работает для перезагрузки многих компонентов одновременно и требует огромной работы, поэтому я рассматриваю это как последнее средство.

Еще одна идея, которая у меня была, заключалась в том, чтобы изменить метод getReadPdo(){...} и значение $recordsModified внутри класса базы данных Connection, чтобы прилипание сохранялось в сеансе пользователя до 1 секунды. Я не был уверен, что это вызовет какие-либо дополнительные проблемы со скоростью или чрезмерной загрузкой сеанса, что вызовет больше проблем.

Кто-нибудь сталкивался с этим раньше или есть какие-либо идеи о том, как решить эту проблему?

Заранее спасибо.

каков прирост производительности, если вы разделяете или не разделяете соединения с базами данных? если речь идет о «миллисекундах», не тратьте часы на размышления, чтобы решить эту проблему. вызвать другие ошибки где-то еще в коде.

Raymond Nijland 26.07.2019 12:09

Прирост производительности по всему приложению за счет разделения огромен. У нас довольно много периодов высокой нагрузки, и разделение нагрузки означает, что мы можем легко горизонтально масштабировать базы данных RO без перегрузки RW. Число миллисекунд, которое я дал, было задержкой данных между базой данных RW и RO, а не приростом скорости. Спасибо за комментарий!

GhettoPancake 26.07.2019 12:37

горизонтальное масштабирование швов здесь является неправильным определением, также я не уверен, что означает RO/RW в этом контексте..? Но, скорее всего, я неправильно понимаю настройку сервера(ов) вашего приложения/базы данных... Можете ли вы показать некоторый лаваральный код, в котором вы определили соединения и как вы используете его в своем приложении.. Возможно, другие также получат лучшее впечатление и могут увидеть лучшее путь или решение проблемы..

Raymond Nijland 26.07.2019 14:58

... также, если я думаю о горизонтальном масштабировании с базами данных MySQL.. я думаю о Кластер MySQL, который также может быть хорошим решением вашей проблемы..

Raymond Nijland 26.07.2019 15:04
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
1
4
1 384
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Думал, что обновлю и отвечу на это, если кто-то еще столкнется с той же проблемой.

Это не идеальное решение, но оно хорошо сработало за последнюю неделю или около того.

Внутри метода AppServiceProviderboot() я добавил следующее

            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 или эффектов от задержки реплики.

Я опубликовал как пакет!

  • mpyw/laravel-cached-база данных-липкость: гарантирует привязку базы данных к последовательным запросам одного и того же пользователя.

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 in config/app.php by yourself.

<?php

return [

    /* ... */

    'providers' => [

        /* ... */

        Mpyw\LaravelCachedDatabaseStickiness\ConnectionServiceProvider::class,

        /* ... */

    ],

    /* ... */
];

Это все! Все проблемы будут решены.

Другие вопросы по теме