CodeFest 2011. Высоцкий С. — Crawljax. Четвертый закон робототехники
ZooKeeper Java Cloud
1. ZooKeeper. Обзор теории и
примеры практических решений.
Владимир Бадовский
Vovo4kin@rambler.ru
2. ZooKeeper - это координационный сервис для распределённых приложений. С его
помощью можно конфигурировать и синхронизировать части распределённого приложения.
ZooKeeper - это сервер, в development целях можно установить на большинство
популярных платформ, в целях production рекомендуются GNU/Linux и Sun Solaris платформы.
Для повышения надёжности таких серверов в вашем приложении может быть несколько.
В дистрибутив включён консольный клиента zkCli.cmd.
Взаимодействие распределённых процессов осуществляется посредством общего
иерархического пространства имён, которое организованно по аналогии с обычной файловой
системой.
Znode - структурная единица, регистр данных, она вобрала в себя свойства и файла и
директории.
Вступление
4. Znode могут только создаваться и удалятся.
Может хранить информацию до 1 Mb.
Атомарность работы с данными. Данные сохраняемые в znode считываются или
записываются атомарно. Чтение вычитывает все данные из znode, запись полностью
уничтожает предыдущую информацию.
Access Control List (ACL). Каждая znode имеет механизм разграничения доступа.
Версирование всех изменений. Каждое изменение в znode вызывает увеличение одного
из трёх счётчиков версий: version - счётчик изменения данных, cversion - счётчик изменения
детей znode и aversion - счётчик изменения прав доступа к znode .
Ephemeral и Persistant znodes.
Sequentional znodes.
create("parent/child",...) -> "/parent/child0000000000"
create("parent/child",...) -> "/parent/child0000000001"
Свойства Znode
5. class SomeWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
// Do some work
}
}
Watching - механизм оповещения клиента о изменениях в/с интересующей клиента znode.
Клиент может зарегистрировать watcher на znode на определённый тип изменения znode. В
последствии при возникновении указанного изменения znode, происходит срабатывание
watcher. При этом zookeeper-сервер шлёт уведомление клиенту, который зарегистрировал
этот watcher.
Watcher срабатывает единоразово, и для последующего вотчинга необходимо его заново
регистрировать.
Watcher это объект класса имплементирующего интерфейс org.apache.zookeeper.
Watcher, в котором всего один метод вызываемый при срабатывании watcher.
Watcher
На клиентской стороне обработкой всех вотчеров занимается один поток, поэтому не
стоит перегружать метод process.
6. exists, create, delete, setData, getData, getChildren, getACL, setACL, sync
Команды exists, getData, getChildren могут дополнительно устанавливать watcher.
ExistWatcher, DataWatcher и ChildrenWatcher соответственно. Команды create, delete, setData
вызывают срабатывание заданного типа watcher.
Правила выставления и условия срабатывания watcher приведены в таблице.
Основные команды для работы с
ZooKeeper
Команды
выставляющие
watcher
Команды вызывающие срабатывание watcher
create delete setData
exist NodeCreated NodeDeleted NodeDataChanged
getData NodeDeleted NodeDataChanged
getChildren NodeChildrenChanged
NodeDeleted
NodeChildrenChanged
7. Установка - распаковать.
Подправить конфиг: tickTime единица исчисления времени, dataDir директория в которой
будет хранится так называемый the in-memory database snapshots при работе вся информация
содержится в оперативной памяти, а её слепок дублируется на диске в указанной папке,
clientPort порт на который будет стучаться клиент.
zkServer.cmd - запуск сервера.
При подключении с клиентской части помимо ip и порта надо указать session timeout в
tickTime. Это время на сколько максимум может пропадать связь между сервером и клиентом.
Кроме того важно указать так называемого default Watcher и озадачить его мониторингом
подключения к серверу.
Поскольку подключение к серверу происходит асинхронно, то прежде чем работать с
zookeeper-клиентом необходимо дождаться его подсоединения к серверу Event.KeeperState.
SyncConnected.
Подготовка и начало работы
8. Конструктор сразу возвращает экземпляр класса org.apache.zookeeper.ZooKeeper, но это
не значит что с ним уже можно работать. Связь устанавливается асинхронно, поэтому следует
предусмотреть в коде задержку и проверку состояния соединения. В представленной
реализации сервис обращается к ZooKeeper используя промежуточный класс zooClient.
getZooKeeper().
Пример подключения к серверу
public class ZooClient {
...
public ZooKeeper getZooKeeper() {
if ((zooKeeper == null)) {
final CountDownLatch connected = new CountDownLatch(1);
Watcher watcher = new Watcher() {
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connected.countDown();
}
}
};
zooKeeper = new ZooKeeper(hosts, sessionTimeout, watcher);
connected.await();
}
return zooKeeper;
}
...
}
9. Поднимается нечётное кличество серверов - ensemble. Данные полностью дублируются на
каждом сервере. При подъёме кластера происходит выбор Leader. Leader это тот сервер через
который происходит запись информации. Клиенты подключаются к followers, followers
перенаправляют эти запросы на Leader. В случае падения Leader переизбирается.
В конфиг каждого сервера добавляются ip и порты всех членов кластера (server.1,
server.2...), параметры связи между followers и Leader (initLimit, syncLimit). Кроме того в bin/
добавляется файл myid с уникальным номер своего сервера в кластере.
У клиента должны быть адреса всех членов кластера, для возможности переключения в
случае падения текущего сервера(hosts).
Кластер
11. Дано: множество процесов типа А, каждый из которых должен отслеживать появление
каждого процесса типа B.
При появлении процесс B создаёт sequentional znode в оговоренной parent znode.
Каждый процесс A заранее регистрирует childrenWatcher на parent znode. При
срабатывании атомарно происходит вычитывание текущего списка znode типа B, и выставления
такого же watcher, в простейшем случае самого себя. За счёт атомарности getChildren ни одна
znode типа B не появится незамеченной.
SequentialWatcher
public abstract class SequentialWatcher implements Watcher {
...
@Override
public void process(WatchedEvent event) {
String basePath = event.getPath();
List<String> seqNodesNames = zookeeperService.getChildren(basePath, this);
int previousSize = dtoList.getSequence();
int currentSize = seqNodesNames.size();
Collections.sort(seqNodesNames);
List<String> newNodesList = seqNodesNames.subList(previousSize, currentSize);
analyze(basePath, newNodesList); // Do some work with new process
}
...
}
12. Дано znode, которая возможно уже не существует, но если существует необходимо
получить оповещение о её удалении. Если использовать простой exist в случаях отсутствия
znode у нас остаётся заведомо несработающий watcher. Гораздо рациональнее использовать
getData.
Set watcher if znode exist
public class ZookeeperServiceImpl implements ZookeeperService {
...
@Override
public boolean setWatcherIfNodeExists(String path, Watcher watcher) throws TechnicalException {
try {
zooClient.getZooKeeper().getData(path, watcher, null);
return true;
} catch (KeeperException e) {
if (e.code().intValue() == KeeperException.Code.NONODE.intValue()) {
return false;
} else {
throw new TechnicalException(e, log);
}
} catch (Exception e) {
throw new TechnicalException(e, log);
}
}
...
}
13. Дано процесс типа А выдаёт задачу процессу типа В, процесс В возвращает результат
процессу типа А. Вычисление результата может происходить мгновенно.
В приведенном коде процесс изначально выставляет watcher на появление некоторой
znode с результатом. Процесс типа В следит за детьми оговоренной parent znode. Процесс типа
А создаёт sequentional znode в parent и атомарно записывет в неё условие задачи и путь znode
в которую положить результат. При этом как быстро процесс расчёта не происходил бы
результат всё равно будет зафиксирован.
Put task, get result
...
zooKeeper.exist(resultPath, new ProcessResultWatcher());
zooKeeper.create(path, taskWithResultPath, CreateMode.PERSISTENT_SEQUENTIAL);
...