четверг, 10 июля 2014 г.

MongoDB replication

В контексте MongoDB процедура репликации связана с понятие репликасет. Реплика сет это:
- группа нод, в которых одна является праймери, другие - секондари;
- приложение подключается к праймери, все изменения, которые приходят на праймери, дублицируются на секондари ноды;
- когда праймери падает, репликасет решает какая из секондари нод становится новой праймери, и приложение переподключается на нее, таким образом если быть более точным, то приложение подключается к репликесету, а уже репликасет решает к какой ноде полючить соединение;
- когда упавший праймери подымается, он уже становится одним из секондари и вступает в работу с записи всех изменений, который пришли в репликусет без него;
- минимальное количесвто нод в репликесете 3.




Types of ReplicaSet Nodes

- Regular - это обычный тип ноды, такая нода может играть роль праймери или секондари.
- Arbiter - нода для "голосования", которая определяет какая нода станет праймери, если поточная праймери упала, вернее эта нода делает количество нод нечетными, на ней нет базы она просто учавствует в голосовании - нам нужно чтобы было нечетное количество нод.
- Delayed/Regular - это нода, которая хранит некие исторические данные, и не учавствует в общем цикле обновлении риплик, она для случая, когда все полетит, тогда включится она, как некий недавний бекап.
- Hidden - нода в реплике, но приложение не получает доступ на чтение из этой ноды, это для каких-то других приложений, которые читают из базы и нуждаются в постоянной пропускной способности; такая нода не может стать праймери.
- Priority 0 - любая нода, которая получит такой приоритет, не будет участвовать в голосования за праймери - оно не станет праймери.


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


Для примера вот скрипт для создание репликасет на одной локальной ноде - нужно его подправить под конкретику - имена баз(rs1, rs2, rs3), и дать соотвествующее имя реплике(m101)
 mongo_create_replica_set.sh:
#!/usr/bin/env bash

mkdir -p /data/rs1 /data/rs2 /data/rs3
mongod --replSet m101 --logpath "1.log" --dbpath /data/rs1 --port 27017 --oplogSize 64 --fork --smallfiles
mongod --replSet m101 --logpath "2.log" --dbpath /data/rs2 --port 27018 --oplogSize 64 --smallfiles --fork
mongod --replSet m101 --logpath "3.log" --dbpath /data/rs3 --port 27019 --oplogSize 64 --smallfiles --fork

Если мы запустим так, то мы увидим в логах, что процессы запущены, но они никак между собой не связаны, ибо не владеют никакой информацией о друг друге.

Для этого есть еще один скрипт mongo_init_replica.js:
config = { _id: "m101", members:[
          { _id : 0, host : "localhost:27017", priority:0, slaveDelay:5},
          { _id : 1, host : "localhost:27018"},
          { _id : 2, host : "localhost:27019"} ]
};

rs.initiate(config);
rs.status();
Мы уже можем заметить, что нода на порте 27017 не может быть праймери из-за приоритета.
Теперь же запустим нам репликусет:
$ mongo --port 27018 < mongo_init_replica.js
Так мы подключимся к работающему процессу mongod на порту 27018 и запустим на нем указанный скрипт - мы увидим ответ и после этого mongo shell завершит свою работу. Если без ошибок, то мы получим работающий репликасет.
Мы также можем все сделать немного по-другому через шел
$ mongo --port 27018

> rs.status()
> rs.initiate()
> rs.add("localhost:27017")
> rs.add("localhost:27019")
При этом нода в которой мы запускаем эти команды становится праймери.
После этого мы можем подключаться к конкретной монгод из запущенной репликисет, но писать можем только на PRIMARY, на секондари мы можем только читать и то, только после ввода команды rs.slaveOk() 


Процесс репликации происходит следующим образом:
1) Праймери нода пишет все, что с ней происходит в базу local в колекцию oplog.rs
2) Потом эта колекция синхринизируется по всей реплике-сет.
3) Между нодами из одной репликасеты происходит постоянный хартбит(пинг), когда какая-то нода находит на других отличия в оплоге она загружает их к себе.
Колекция oplog является Capped Collection - то есть у нее статический размер, и когда она заполняется полностью, новые записи переписывают самые старые. 



Есть одна ситуация, которая требует внимания: когда праймери нода, записала в себя какие-то данный и в этот момент упала, при этом данные не успели распространиться по секондари. При подьеме в роли секондари эта нода сольет такие изменения в файл ролбек, и подгонит себя к состаянию нового праймери. Если случится зацикливание, то поднявшаяся нода сотрет из себя все и полностью заново сольет базу с праймери.


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

Пример на js:

var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:30001," +
                    "localhost:30002," +
                    "localhost:30003/course", function(err, db) {
  ...
});

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


Положить праймери ноду мы можем не убийством процесса, а такими вот командами к подключенному к праймери mongo shell:
> use admin
> db.shutdownServer();

Write Concern 

Это параметр настройки подключения к репликасету. Мы можем его определить для подключения, но потом изменить его для конкретной операции.
w=0 - означает что как-только драйвер отправит запись в репликусет, он тут же вернет ответ каксес, не дожидаясь результата. Этот вариант подходит, когда у нас потеря записи не так важна, как важно обеспечить огромный поток входящих данных в базу - это может быть например запись поведения пользователя на сайте - записть движения его курсора по странице.
w=1 - драйвер ждет пока данные не будут записаны в праймери, и только потом отдает ответ об успешном завершении команды.
w=n(n>1) - драйвер ждет пока репликасет запишет в n-1 слейв.
{ w: m, j: true }(m>0) - journalate - означает, что успешный ответ вернется только после того, как данный кроме базы также запишутся в журнал, что означает они могут быть восстановлены в случае падения сервера.
w="majority" - документ сохранился на большинстве из нод репликасете(я так понимаю из 3-х на 2-х; из 11-ти на 6-ти и т.д.)
Мы можем считать, что данные надежно сохранены, только в случае, если операция записана на основном количестве нод в репликасете в журнале.

Райтконсерны можно установить по-умолчанию без специальных указаний в каждом запросе:

cfg = rs.conf()
cfg.settings = {}
cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 }
rs.reconfig(cfg)

А ось так замінюються для конкретного запиту:
db.products.insert(
   { item: "envelopes", qty : 100, type: "Clasp" },
   { writeConcern: { w: 2, wtimeout: 5000 } }
)
var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:30001," +
                    "localhost:30002," +
                    "localhost:30003/course?w=1", function(err, db) {
  //write concern 1
  db.collection('course').insert({my: 'data'}, function(err, doc) {});
  
  //write concern 2
  db.collection('course').insert({my: 'data2'}, {w: 2}, function(err, doc) {});
});

Если мы укажим райт консерн выше числа нод в репликесете, то запрос будет ответа об результате вечно или до тех пор пока мы не добавим еще нужно количество нод.

Read Preferences

Мы можем настроится на чтение:
- только из праймери;
- только из одного из слейвов (допустим, когда у нас не критично прочитать какую-то запись сразу, как-только она появилась);
- поставить предпочтение на праймери - чтение с праймери, но если он лежит в этот момент, то читаем со секондари;
- поставить предпочтение на секондари - если все упало и остался только один сервер, то будем читать только с него.
- ближайший - пока не знаю деталей.

var MongoClient = require('mongodb').MongoClient;
var ReadPreference = require('mongodb').ReadPreference;

MongoClient.connect("mongodb://localhost:30001," +
                    "localhost:30002," +
                    "localhost:30003/course?readPreference=secondary", function(err, db) {
  //read secondary
  db.collection('course').find({my: 'data'}, function(err, doc) {});
  
  //write concern 2
  db.collection('course').find({my: 'data2'}, {readPreference: ReadPreference.PRIMARY}, function(err, doc) {});
});

Комментариев нет:

Отправить комментарий