Что происходит с вашим набором реплик MongoDB, когда дело доходит до сбоев, таких как разделение сети, перезапуск, перенастройка существующей топологии и т.д.? Этот вопрос особенно важен в наши дни из-за популярности, которую приобрела модель с несколькими облаками , где шансы на эти сценарии вполне реалистичны.
Однако существует ли решение, предпочтительно бесплатное, для тестирования таких случаев, которое избавило бы от необходимости написания ручных сценариев и изучения официальной документации? Как разработчикам программного обеспечения, нам было бы лучше заранее подготовить наши приложения, чтобы пережить эти сбои.
Взглянув на MongoDB Atlas и их бесплатные Общие кластеры , мы не можем настроить набор реплик, заданный по умолчанию, который является основным с двумя вторичными элементами (далее P-S-S), не говоря уже о тестировании некоторых функций, таких как restartPrimaries
. Все это предназначено для выделенных кластеров, требующих оплаты по почасовой ставке.
В то время как облачные решения, по-видимому, являются фактическим выбором для производственных систем, некоторые разработчики программного обеспечения, скорее всего, будут нуждаться в бесплатных локальных альтернативах, что подтверждается такими проектами, как LocalStack, act и др.
Учитывая это, как насчет разработки решения для локального запуска некоторых функций, которые выполняет платная версия MongoDB Atlas? Одним из возможных способов является выбор в пользу следующего:
- Докер для запуска контейнеров MongoDB;
- Тестовые контейнеры для обработки контейнеров с помощью языка программирования, такого как Java;
- Foxyproxy для моделирования сетевых условий.
Все хорошо, но для создания кластера из более чем одного узла MongoDB требуется, чтобы “каждый член набора реплик был доступен с помощью разрешаемого DNS или имен хостов” . Если вы запускаете Docker версии 18.03+ на хостах, таких как Mac и Win, или Docker 20.04+ на Linux, вы можете свободно использовать специальное DNS-имя хост.докер.внутренний
. Во время установки Docker может добавить его в файл хоста вашей операционной системы. Однако для тех, кто по какой-либо причине не может обновить свой докер, мы можем использовать специальный контейнер, который перенаправляет трафик на хост, например Qoomon докер-хост . Аналогично, мы можем добавить docker host
в файл хоста ОС, а также запустить контейнер с NET_ADMIN
и NET_RAW
возможностями ядра.
Хорошей новостью является то, что нам не нужно соединять все вышеупомянутые части вместе вручную, а вместо этого мы можем воспользоваться бесплатным Набор реплик MongoDB .
Давайте начнем с создания нового проекта Gradle с Java и добавления некоторых зависимостей от Maven central:
testCompile("com.github.silaev:mongodb-replica-set:0.4.3") testImplementation("org.mongodb:mongodb-driver-sync:4.2.1")
1) Обратите внимание, что вам решать, какой драйвер MongoDB использовать здесь, примером является mongodb-driver-sync
.
Затем нам нужно проверить и, возможно, изменить файл хоста нашей операционной системы. Что касается host.docker.internal
, он может быть уже там, в противном случае добавьте 127.0.0.1 host.docker.internal
. Согласно dockerhost
, добавьте 127.0.0.1 dockerhost
. Вы должны выбрать только один из них и использовать его при выполнении теста.
Наше путешествие начинается, и мы готовы написать тест для имитации разделения сети в P-S-S. Вот его описание:
try ( final MongoDbReplicaSet mongoReplicaSet = MongoDbReplicaSet.builder() .mongoDockerImageName("mongo:4.4.4") .useHostDockerInternal(true) .addToxiproxy(true) .replicaSetNumber(3) .commandLineOptions(Arrays.asList("--oplogSize", "50")) .build() ) {
1) Используйте mongo:4.4.4
в качестве последнего образа докера MongoDB на момент написания; 2) Если внутренний докер хоста пользователя
верно, используйте host.docker.internal
докера, в противном случае используйте докерхост
общего хоста докера; 3) Поместите контейнер ToxiproxyContainer. ContainerProxy для каждого узла MongoDB; 4) Установите 3 (возможно до 7) элемента для построения ПРОШЛОГО; 5) При необходимости добавьте некоторые параметры командной строки, например, установите 50 МБ в качестве журнала операций репликации; 6) Автоматическое закрытие всех контейнеров с помощью try-with-resources в случае любого исключения или завершения.
Теперь мы можем запустить набор реплик с помощью mongo Replica Set.start()
, получить его URL-адрес и сделать некоторые утверждения:
final String replicaSetUrl = mongoReplicaSet.getReplicaSetUrl(); assertThat( mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()) ).containsExactlyInAnyOrder(PRIMARY, SECONDARY, SECONDARY);
1) Внутренне getMongoRsStatus() вызывает rs.status()
в оболочке MongoDB.
Тогда мы можем, например, создать MongoClient
для вставки некоторых данных и последующего их утверждения (см. полный пример на Github ).
try ( final MongoClient mongoSyncClient = MongoClients.create(new ConnectionString(replicaSetUrl)) ) {
1) Обратите внимание, что мы также можем использовать более удобные настройки MongoClient
в качестве параметра метода create
для установки тайм-аутов, проблем с чтением/записью, отключите повторные попытки на уровне соединения.
Первый сбой происходит здесь, поэтому позвольте нашему набору реплик пережить отключение главного узла:
// TODO: Insert a document here to assert total number at the end final MongoNode masterNodeBeforeFailure1 = mongoReplicaSet.getMasterMongoNode( mongoReplicaSet.getMongoRsStatus().getMembers() ); mongoReplicaSet.disconnectNodeFromNetwork(masterNodeBeforeFailure1); mongoReplicaSet.waitForMasterReelection(masterNodeBeforeFailure1); assertThat( mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()) ).containsExactlyInAnyOrder(PRIMARY, SECONDARY, DOWN);
1) Нам нужно дождаться нового главного узла, выбранного путем предоставления предыдущего главного узла методу waitForMasterReelection(...)
.
Идя дальше, следующая авария приводит к отключению главного узла новичка:
// TODO: Insert a document here to assert total number at the end final MongoNode masterNodeBeforeFailure2 = mongoReplicaSet.getMasterMongoNode( mongoReplicaSet.getMongoRsStatus().getMembers() ); mongoReplicaSet.disconnectNodeFromNetwork(masterNodeBeforeFailure2); mongoReplicaSet.waitForMongoNodesDown(2); assertThat( mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()) ).containsExactlyInAnyOrder(SECONDARY, DOWN, DOWN);
1) Мы ждем момента, когда наш единственный вторичный узел обнаружит, что остальные 2 узла не работают.
Наше путешествие подходит к концу, так что давайте вернем все отсоединенные узлы обратно в виде счастливого конца:
// TODO: Insert a document here to assert total number at the end mongoReplicaSet.connectNodeToNetwork(masterNodeBeforeFailure1); mongoReplicaSet.connectNodeToNetwork(masterNodeBeforeFailure2); mongoReplicaSet.waitForAllMongoNodesUp(); mongoReplicaSet.waitForMaster(); assertThat( mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()) ).containsExactlyInAnyOrder(PRIMARY, SECONDARY, SECONDARY);
1) Метод подождите, пока все узлы Монго будут запущены (...)
ожидает, пока все отключенные узлы будут запущены и запущены; 2) Затем метод wait For Master()
ожидает завершения выборов.
Вывод
Зачем писать такие тесты? Чтобы ответить на этот вопрос, давайте установим проблему записи
как большинство
с включенным ведением журнала
и проблемой чтения
как большинство
также. Затем мы можем заменить //TODO:...
в вышеупомянутых примерах кода с помощью
mongoSyncClient.insertOne(…)
Связи:
- Найдите пример из этой статьи здесь ;
- Набор реплик MongoDB на Github .
Оригинал: “https://dev.to/silaev/run-mongodb-atlas-locally-for-testing-1mp9”