Репликация MongoDB
MongoDB поддерживает асинхронную репликацию данных между серверами, в целях отказоустойчивости и избыточности данных. Без этого функционала немыслим ни один высоко нагруженный сервис.
Репликация — механизм синхронизации содержимого базы данных с несколькими серверами (репликами). То есть, один инстанс mongod на сервере 127.0.0.1 передает (синхронизирует) содержимое своей БД test другому инстансу mongod на сервере 127.0.0.2 (теперь это реплика). Таким образом в нашей системе создается избыточность данных: одна и та же база test обслуживается двумя серверами. И это в некотором смысле хорошо. Так как если один сервер упадет, другой перехватит его знамя и продолжит раздавать и принимать данные.В MongoDB репликация асинхронная. Это значит, что данные синхронизируются между репликами не в момент непосредственного изменения данных, а отложенно, через какое-то время. В этом есть плюс: не тратится время на репликацию в момент изменения данных (insert'ы происходят быстрее). И, как водится, минус: в определенные моменты времени данные между репликами могут быть не согласованными (читай разными).На данный момент MongoDB поддерживает две основные парадигмы репликации: master-slave и replica sets. Ниже познакомимся подробнее с каждой из них.
Master-SlaveПарадигма Master-Slave применяется во многих СУБД. Грубо говоря, есть несколько серверов: один мастер, и два слейва. Удалить и писать в базу можно только в мастер-сервере, а читать как из мастера так и из слейвов (для этого-то они и нужны). Слейвов может быть несколько. Такая схема полезна, когда у нашей системы много запросов на получение данных. Так как приложение может обращаться к слейвам для чтения, то мы можем ± равномерно распределить нагрузку между ними.Теперь посмотрим, как реализовать master-slave в MongoDB. Допустим у нас три машины: 127.0.0.1 будет мастером, а 127.0.0.2 и 127.0.0.3 соответственно слейвами. Для начала запустим mongod в режиме мастера:
127.0.0.1 $bin/mongod --master и другие настройки
Теперь на нашей первой машине крутится мастер, к которому можно обращаться для записи/чтения уже сейчас. Теперь на двух других серверах запустим mongod в режиме слейвов:
127.0.0.2 $bin/mongod --slave --source 127.0.0.1:порт и другие настройки
127.0.0.3 $bin/mongod --slave --source 127.0.0.1:порт и другие настройки
Стало быть наши слейвы знают, где находится мастер (параметр source ). Что произойдет далее? Если в мастер-сервер кто-то что-то писал/удалял пока мы настраивали слейвы, то слейвы автоматически и асинхронно синхронизируются с мастером, и у них будет свежая копия данных. Но всё может быть не так просто.
Для того, чтобы понять какие сложности могут возникнуть со слейвом, надо понять как работает мастер. Когда мы запустили мастера-сервер, он создал в своей базе коллекцию local.oplog.$main (лог операций). В этой коллекции хранятся все последние операции (изменения данных), которые применялись на мастере. Именно из этой коллекции по слейвам расходятся команды обновления их локальных копий. Но не зря я сказал последние операции, ибо размер лога операций ограничен (мы его можем задать сами). И в случае, если лог будет заполнен полностью, более старые операции будут удаляться из него, а новые соответственно добавляться.
Внимание: Если слейв выпадет из системы надолго, то есть вероятность, что на мастере за это время будет применено столько новых операций, что лог заполнится и самые старые операции удалятся. А это значит, что мастер не сможет отослать введенному в строй слейву весь лог изменений и на слейве окажется не синхронная копия мастера. А это очень плохо. Нам в этом случае поможет полное копирование базы мастера на слейв. Это можно сделать операцией {resync:1} на слейве, или запуском слейва с ключом --autoresync. После таких танцев, на слейве будет точная копия мастера и наша система снова в строю.
Администрирование master-slave репликации в MongoDB простое, отчасти потому что предоставляется нормальный API. Например, можно указывать мастер-сервер на уже работающем слейве или выполнять диагностические команды или еще что-то в этом духе. Хотя возможно это не такой уж мощный функционал, но имеется возможность автоматического администрирования репликаций: добавление новых слейвов, бэкапы, подмена мастера и прочее. Впрочем на master-slave репликациях это видно не так хорошо, как, например, на replica sets в купе с шардингом. Там возможность автоматического расширения кластера представлена во всей красе. Второй способ репликации — replica sets.
Replica Sets
Характеризуется автоматической отказоустойчивостью. В наборе реплик также присутствует мастер, и при том только один в каждый момент времени. Отличие же в автоматической смене мастера в случае если это необходимо (например, если прежний мастер упадет). Суть в том, что создается несколько процессов mongod с ключом --replSet и указанием имени набора.
127.0.0.1 $bin/mongod --replSet r1 и другие настройки
127.0.0.2 $bin/mongod --replSet r1 и другие настройки
127.0.0.3 $bin/mongod --replSet r1 и другие настройки
Затем серверы реплик необходимо инициализировать. В это время реплики решат между собой, кто будет выполнять функции мастера.
>config = {_id: 'r1', members: [
{_id: 0, host: '127.0.0.1:27017'},
{_id: 1, host: '127.0.0.2:27017'},
{_id: 2, host: '127.0.0.3:27017'}]
}
> rs.initiate(config);
После запуска серверов и их инициализации можно приступать к изменению данных на мастере и чтению с него. Да, да, чтение как и запись возможны только с мастера. Хотя со временем обещают чтение и с реплик. В любом случае даже сейчас есть преимущества использования набора реплик, так как они с легкостью помогают сохранить работоспособность системы, даже в случае падения мастера. Особое значение набор реплик приобретает совместно с шардингом. В этом случае один и тот-же шард можно реплицировать на несколько серверов, обеспечивая отличную отказоустойчивость. Управляющий процесс mongos самостоятельно определяет к какой из реплик осуществлять запрос, осуществляя тем самым автоматическую балансировку.
Итак, в наборе реплик в случае падения мастера, оставшиеся сервера реплик автоматически решают кто из них станет мастером. Для программы, использующей реплицирование этот процесс прозрачный, так как драйверы MongoDB для всех языков поддерживают указание всех адресов реплик. То есть, программа всегда знает где и сколько реплик и кто из них мастер. Драйверы определяют мастера, и соответственно к нему осуществляют запрос. Это возможно благодаря тому, что каждый реплик-сервер хранит данные и о мастере и о других репликах (для этого существует команда rs.status()).
В заключении рассмотрим процесс «голосования» при выборе мастера. Так уж устроено в MongoDB, что чтобы назначить нового мастера нужно набрать больше половины «голосов» реплик-серверов. Этот выбор происходит в процессе инициализации. Когда мы используем три сервера реплик, то всё в порядке, так как сам сервер за себя не может проголосовать, но зато два других могут. В итоге 1+1 больше половины. Новый мастер назначен! Но если мы используем два сервера, то нам, как ни странно, необходим третий, арбитр. Арбитр — легковесный процесс, выполняющий функцию только лишь голосования, и не хранящий данных. Создаются арбитр-процессы точно так-же как и реплик-серверы, но инициализация происходит следующим образом:
>config = {_id: 'r1', members: [
{_id: 0, host: '127.0.0.1:27017'},
{_id: 1, host: '127.0.0.2:27017'},
{_id: 2, host: '127.0.0.3:27017, arbiterOnly: true'}]
}
> rs.initiate(config);
Разумеется, выделять отдельную машину для арбитра вовсе необязательно. Его можно запустить совместно с одной из реплик.
Original source: habrahabr.ru (comments).
MongoDB и репликация с Replica Set
MongoBD документо-ориентированная СУБД с JSON-подобными схемами данныхДля установки первым делом необходимо добавить официальный репозиторий продукта
# vim /etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1
Устанавливаем, запускаем и проверяем работоспособность сервера
# yum install mongo-10gen mongo-10gen-server
# systemctl start mongod
# netstat -lntpu | grep mongo
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN 13709/mongod
Основной задачей в данный момент является настройка репликации.
MongoDB в отличии от MySQL поддерживает 2 формы репликации:
— реплисеты (Replica Sets)
— ведущий-ведомый (Master-Slave)
Подробности можно узнать на официальном сайте
Обыкновенная настройка Master-Slave не интересна да и не рекомендуется разработчиками. Я хочу проверить реплисеты. Для этого указываю имя реплики для всего сервера, раскоментровав отвечающую за это строку
# vim /etc/mongod.conf
replSet = FirstReplica
Перезапускаю сервер для применения изменений.
# systemctl restart mongod
Есть вероятность, что этот шаг можно было пропустить, так как при создании инстансов вручную указывается одно и тоже имя реплики для каждого процесса. Если кто то попробует — напишите в коментариях о результате
В нашем тестовом окружении необходимо запустить 3 экземпляра сервера. 3 это минимум, необходимый для «правильной» реплики: один из серверов выступает исключительно в роли арбитра и не принимает на себя никакие данные.
Он всего лишь помогает выбрать сервер, который будет PRIMARY. Это его свойство позволяет использовать в качестве арбитра сервер с минимальными ресурсами.
Для имитирования ситуации с 3 разными серверами я создам 3 процесса mongod c отдельным портом и базой:
Создание самих баз и установка необходимого владельца:
# mkdir /var/lib/mongo/{db1,db2,db3}
# chown mongod:mongod /var/lib/mongo/{db1,db2,db3}
Этот сервер будет PRIMARY (Мастер)
# mongod --dbpath /var/lib/mongo/db1 --port 27001 --replSet FirstReplica --fork --logpath /var/lib/mongo/db1/db1.log
about to fork child process, waiting until server is ready for connections.
forked process: 18042
child process started successfully, parent exiting
Этот сервер будет SECONDARY (слейв)
# mongod --dbpath /var/lib/mongo/db2 --port 27002 --replSet FirstReplica --fork --logpath /var/lib/mongo/db2/db2.log
about to fork child process, waiting until server is ready for connections.
forked process: 18115
child process started successfully, parent exiting
Этот сервер будет арбитром, не принимающим данных
# mongod --dbpath /var/lib/mongo/db3 --port 27003 --replSet FirstReplica --fork --logpath /var/lib/mongo/db3/db3.log
about to fork child process, waiting until server is ready for connections.
forked process: 18226
child process started successfully, parent exiting
--dbpath параметр, указывающий дирикторию для файлов БД
--port номер корта, к которому смогут подключаться клиенты. В данном случае используется 3 разных порта для разделения инстансов
--replSet название набора реплик. Должно быть одинаково на всех реплицируемых серверах/процессах mongod
--fork параметр, отвечающий за запуск mongod в режиме демона.
--logpath файл для перенаправления вывода
Подключаемся к основному серверу и настраиваем реплику из консоли mongo
# mongo --port 27001
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27001/test
Server has startup warnings:
2015-06-08T11:43:29.342+0300 [initandlisten]
2015-06-08T11:43:29.342+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db1 is set to 4096KB
2015-06-08T11:43:29.342+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T11:43:29.342+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
> rs.status()
{
"startupStatus" : 3,
"info" : "run rs.initiate(...) if not yet done for the set",
"ok" : 0,
"errmsg" : "can't get local.system.replset config from self or any seed (EMPTYCONFIG)"
}
> rs.initiate({"_id" : "FirstReplica", members : [
... {"_id" : 0, priority : 3, host : "127.0.0.1:27001"},
... {"_id" : 1, host : "127.0.0.1:27002"},
... {"_id" : 2, host : "127.0.0.1:27003", arbiterOnly : true}
... ]
... });
{
"info" : "Config now saved locally. Should come online in about a minute.",
"ok" : 1
}
> rs.status()
{
"set" : "FirstReplica",
"date" : ISODate("2015-06-08T08:45:57Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27001",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 148,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"electionTime" : Timestamp(1433753133, 1),
"electionDate" : ISODate("2015-06-08T08:45:33Z"),
"self" : true
},
{
"_id" : 1,
"name" : "127.0.0.1:27002",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 32,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"lastHeartbeat" : ISODate("2015-06-08T08:45:57Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T08:45:55Z"),
"pingMs" : 0,
"syncingTo" : "127.0.0.1:27001"
},
{
"_id" : 2,
"name" : "127.0.0.1:27003",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 32,
"lastHeartbeat" : ISODate("2015-06-08T08:45:57Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T08:45:56Z"),
"pingMs" : 0
}
],
"ok" : 1
}
FirstReplica:PRIMARY> quit()
Для проверки подключимся к оставшимся серверам и посмотрим их статус
# mongo --port 27002
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27002/test
Server has startup warnings:
2015-06-08T11:43:40.736+0300 [initandlisten]
2015-06-08T11:43:40.736+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db2 is set to 4096KB
2015-06-08T11:43:40.736+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T11:43:40.736+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
FirstReplica:SECONDARY> quit()
Видим, что сервер видит реплику и его статус SECONDARY
# mongo --port 27003
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27003/test
Server has startup warnings:
2015-06-08T11:43:49.369+0300 [initandlisten]
2015-06-08T11:43:49.369+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db3 is set to 4096KB
2015-06-08T11:43:49.369+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T11:43:49.369+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
FirstReplica:ARBITER> quit()
Аналогично видим, что этот инстанс является частью реплики FirstReplica и выступает в качестве арбитра
Вроде как завелось. Теперь попробую создать реальные условия:
Инициируем «падение» PRIMARY сервера:
# netstat -lntpu | grep mongod
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN 15726/mongod
tcp 0 0 0.0.0.0:27001 0.0.0.0:* LISTEN 18042/mongod
tcp 0 0 0.0.0.0:27002 0.0.0.0:* LISTEN 18115/mongod
tcp 0 0 0.0.0.0:27003 0.0.0.0:* LISTEN 18226/mongod
# kill -HUP 18042
Я прибил процесс, на котором висел PRIMARY сервер. Нет процесса — нет доступа к серверу.
Вернёмся к арбитру и проверим состояние реплики:
# mongo --port 27003
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27003/test
Server has startup warnings:
2015-06-08T11:43:49.369+0300 [initandlisten]
2015-06-08T11:43:49.369+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db3 is set to 4096KB
2015-06-08T11:43:49.369+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T11:43:49.369+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
FirstReplica:ARBITER> rs.status()
{
"set" : "FirstReplica",
"date" : ISODate("2015-06-08T09:00:10Z"),
"myState" : 7,
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27001",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"lastHeartbeat" : ISODate("2015-06-08T09:00:05Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T08:59:35Z"),
"pingMs" : 0
},
{
"_id" : 1,
"name" : "127.0.0.1:27002",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 882,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"lastHeartbeat" : ISODate("2015-06-08T09:00:08Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T09:00:10Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1433753984, 1),
"electionDate" : ISODate("2015-06-08T08:59:44Z")
},
{
"_id" : 2,
"name" : "127.0.0.1:27003",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 981,
"self" : true
}
],
"ok" : 1
}
FirstReplica:ARBITER>
Видно, что главный сервер недоступен («stateStr» : «(not reachable/healthy)»), а сервер с id 1 стал PRIMARY
Теперь инициируем «поднятие» основного мастера:
# mongod --dbpath /var/lib/mongo/db1 --port 27001 --replSet FirstReplica --fork --logpath /var/lib/mongo/db1/db1.log
about to fork child process, waiting until server is ready for connections.
forked process: 27144
child process started successfully, parent exiting
И снова интересуемся у арбитра как там дела у сервачков:
# mongo --port 27003
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27003/test
Server has startup warnings:
2015-06-08T11:43:49.369+0300 [initandlisten]
2015-06-08T11:43:49.369+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db3 is set to 4096KB
2015-06-08T11:43:49.369+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T11:43:49.369+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
FirstReplica:ARBITER> rs.status()
{
"set" : "FirstReplica",
"date" : ISODate("2015-06-08T09:04:42Z"),
"myState" : 7,
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27001",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 49,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"lastHeartbeat" : ISODate("2015-06-08T09:04:41Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T09:04:40Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1433754240, 1),
"electionDate" : ISODate("2015-06-08T09:04:00Z")
},
{
"_id" : 1,
"name" : "127.0.0.1:27002",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1154,
"optime" : Timestamp(1433753125, 1),
"optimeDate" : ISODate("2015-06-08T08:45:25Z"),
"lastHeartbeat" : ISODate("2015-06-08T09:04:40Z"),
"lastHeartbeatRecv" : ISODate("2015-06-08T09:04:40Z"),
"pingMs" : 0,
"lastHeartbeatMessage" : "syncing to: 127.0.0.1:27001",
"syncingTo" : "127.0.0.1:27001"
},
{
"_id" : 2,
"name" : "127.0.0.1:27003",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 1253,
"self" : true
}
],
"ok" : 1
}
FirstReplica:ARBITER> rs.config()
{
"_id" : "FirstReplica",
"version" : 1,
"members" : [
{
"_id" : 0,
"host" : "127.0.0.1:27001",
"priority" : 3
},
{
"_id" : 1,
"host" : "127.0.0.1:27002"
},
{
"_id" : 2,
"host" : "127.0.0.1:27003",
"arbiterOnly" : true
}
]
}
Как видно выше сервер после возвращения из даунтайма снова принялся за свои обязанности, став мастером всей реплики. В этом ему помогает арбитр.
На этом простом примере мы рассмотрели создание простой репликации с особенностями, присущими программному продукту MongoDB. Следует учесть, что в данном случае реплика была создана до начала работ с базой данных.
Если имеется уже настроенный продакшн-сервер, то метод создания реплики может отличаться. Обязательно потренеруйтесь в тестовом окружении.
В заключении — очистка всего нашего хозяйства:
# ps aux | grep mongod
root 7823 0.0 0.0 112640 960 pts/1 R+ 13:36 0:00 grep --color=auto mongod
root 22820 0.1 2.8 4985748 29460 ? Sl 11:43 0:07 mongod --dbpath /var/lib/mongo/db2 --port 27002 --replSet FirstReplica --fork --logpath /var/lib/mongo/db2/db2.log
root 22866 0.1 2.9 744312 30432 ? Sl 11:43 0:07 mongod --dbpath /var/lib/mongo/db3 --port 27003 --replSet FirstReplica --fork --logpath /var/lib/mongo/db3/db3.log
root 27144 0.1 3.0 4989860 31240 ? Sl 12:03 0:06 mongod --dbpath /var/lib/mongo/db1 --port 27001 --replSet FirstReplica --fork --logpath /var/lib/mongo/db1/db1.log
Так как я запускал процессы вручную, то и останавливать их нужно самостоятельно.
Для этого воспользуемся db.shutdownServer()
# mongo --port 27001
MongoDB shell version: 2.6.10
connecting to: 127.0.0.1:27001/test
Server has startup warnings:
2015-06-08T12:03:52.468+0300 [initandlisten]
2015-06-08T12:03:52.468+0300 [initandlisten] ** WARNING: Readahead for /var/lib/mongo/db1 is set to 4096KB
2015-06-08T12:03:52.468+0300 [initandlisten] ** We suggest setting it to 256KB (512 sectors) or less
2015-06-08T12:03:52.468+0300 [initandlisten] ** http://dochub.mongodb.org/core/readahead
FirstReplica:PRIMARY> use admin
switched to db admin
FirstReplica:PRIMARY> db.shutdownServer()
2015-06-08T13:41:18.996+0300 DBClientCursor::init call() failed
server should be down...
2015-06-08T13:41:18.999+0300 trying reconnect to 127.0.0.1:27001 (127.0.0.1) failed
2015-06-08T13:41:18.999+0300 reconnect 127.0.0.1:27001 (127.0.0.1) ok
FirstReplica:SECONDARY> quit()
Видим что сервер выключился и в репликации он переключился на SECONDARY
Аналогично останавливаем и остальные, но сначала арбитра, а потом наш SECONDARY
Проверяем
# ps aux | grep mongod
root 12528 0.0 0.0 112640 956 pts/1 R+ 13:50 0:00 grep --color=auto mongod
Нет процессов mongod, только наш, который их ищет
Если базы, которые мы создали для теста более не нужны — их тоже можно удалить. Более того, они не так уж мало занимают
# du -h --max-depth=1 /var/lib/mongo/
0 /var/lib/mongo/journal
2.1G /var/lib/mongo/db1
2.1G /var/lib/mongo/db2
81M /var/lib/mongo/db3
4.4G /var/lib/mongo/
Удаляем и снова проверяем
# rm -rf /var/lib/mongo/{db1,db2,db3}
# du -h --max-depth=1 /var/lib/mongo/
0 /var/lib/mongo/journal
81M /var/lib/mongo/
Комментарии
Отправить комментарий