Сводка службы распределенной координации zookeeper

задняя часть сервер ZooKeeper модульный тест

1. Введение в зоопарк

1.1 Введение

Zookeeper — это служба распределенной координации, другими словами, она предоставляет услуги координации для распределенных приложений пользователей.

  • zookeeper обслуживает другие распределенные программы
  • Zookeeper сам по себе является распределенной программой (пока выживает более половины нод, zk может нормально обслуживаться)
  • Услуги, предоставляемые Zookeeper, включают в себя: координацию мастер-подчиненный, динамический онлайн и автономный режим серверных узлов, унифицированное управление конфигурацией, распределенные общие блокировки, унифицированную службу имен.
  • Хотя он может предоставлять различные услуги, zookeeper на самом деле предоставляет только две функции внизу (управление данными и мониторинг данных):Управлять (хранить, читать) данными, представленными пользовательскими программами; И предоставлять услуги мониторинга узла данных для пользовательских программ;

1.2 Роли кластера Zookeeper: лидер и последователь

Zookeeper не указывает master и slave в конфигурационном файле, после запуска лидер и ведомый избираются через внутренний механизм выборов, причем лидер только один, а остальные ведомые. Пока выживет более половины узлов в кластере zookeeper, кластер может предоставлять услуги. 2. кластерный механизм Zookeeper Половинчатый механизм: более половины машин в кластере выживают, и кластер доступен. zookeeper подходит для установки на нечетное количество машин! ! !

2. установка и настройка Zookeeper

2.1 установка зоопарка

  • Установлен на 3 виртуальных машинах (JDK нужно установить заранее) Загрузите сжатый пакет zookeeper в каталог /apps/package и разархивируйте его.
tar -zxvf zookeeper-3.4.5.tar.gz
  • Переименовать
mv zookeeper-3.4.5 zookeeper(重命名文件夹zookeeper-3.4.5为zookeeper)
  • Изменить переменные среды
vi /etc/profile
添加内容:
export ZOOKEEPER_HOME=/apps/package/zookeeper
export PATH=$PATH:$JAVA_HOME/bin:$ZOOKEEPER_HOME/bin
  • Перекомпилируйте файл: источник /etc/профиль Все три машины нуждаются в доработке

2.2 Изменить файл конфигурации

  • Сначала сделайте копию cd zookeeper/conf cp zoo_sample.cfg zoo.cfg
  • редактировать vi зоопарк.cfg
添加内容:
dataDir=/apps/package/zookeeper/data
dataLogDir=/apps/package/zookeeper/log
server.1=mini1:2888:3888
server.2=mini2:2888:3888
server.3=mini3:2888:3888
  • Создайте папку:
cd /apps/package/zookeeper
mkdir -m 755 data
mkdir -m 755 log
  • Создайте новый файл myid в папке данных.Содержимое файла myid:
cd data
vi myid
添加内容:
1

Пожалуйста, измените серверы mini2 и mini3 на 2,3, и в будущем лидер и ведомый будут выбираться в соответствии с этим myid.

  • Скопируйте кластер на другую машину
scp -r /apps/package/zookeeper root@mini2:/apps/package/
scp -r /apps/package/zookeeper root@mini3:/apps/package/

Если вы не можете пинговать mini2 и mini3 в mini1, вам необходимо настроить ip адреса mini2 и mini3 в файле hosts

  • Измените файлы конфигурации других машин Для mini2: измените myid на: 2 Для mini3: измените myid на: 3 И не забудьте изменить путь /etc/profile
  • запуск (на машину)
zkServer.sh start
zkServer.sh start-foreground(可以看到启动日志)
  • Просмотр состояния кластера
jps(查看进程)
zkServer.sh status(查看集群状态,主从信息)

Если указанный порт занят, перейдите по следующей ссылке: http://blog.csdn.net/u014686180/article/details/51767863.

3. структура данных Zookeeper и общие операции

3.1 функции зоопарка

  • Zookeeper: лидер, группа последователей
  • Глобальная согласованность данных: каждый сервер сохраняет идентичную копию данных, и данные непротиворечивы независимо от того, к какому серверу подключается клиент.
  • Распределенное чтение и запись, перенаправление запросов на обновление, реализованное лидером
  • Запросы на обновление выполняются последовательно, а запросы на обновление от одного и того же клиента выполняются в том порядке, в котором они были отправлены.
  • Атомарность обновления данных, обновление данных либо успешно, либо нет
  • В режиме реального времени, в пределах определенного временного диапазона, клиент может считывать последние данные

3.2 структура данных зоопарка

  • Иерархическая структура каталогов;
  • Каждый узел называется znode в zookeeper и имеет уникальный идентификатор пути;
  • Узел Znode может содержать данные и дочерние узлы (но узел типа EPHEMERAL не может иметь дочерних узлов);
  • Клиентские приложения могут устанавливать мониторы на узлах.

3.3 Тип узла

  • Есть два типа Znodes: эфемерный (отключить и удалить себя) Постоянный (отключение не удаляет)
  • Znode имеет четыре формы узлов каталогов (по умолчанию постоянный) НАСТОЙЧИВЫЙ PERSISTENT_SEQUENTIAL (постоянная последовательность/test0000000019) Эфемерный EPHEMERAL_SEQUENTIAL
  • Идентификатор последовательности задается при создании znode, к имени znode добавляется значение, а порядковый номер представляет собой монотонно увеличивающийся счетчик, поддерживаемый родительским узлом.
  • В распределенной системе порядковые номера могут использоваться для глобального упорядочения всех событий, чтобы клиенты могли делать выводы о порядке событий на основе порядковых номеров.

Используйте клиент для управления узлами

  • Используйте команду для подключения к серверу zookeeper:
zkCli.sh -主机名(ip):2181
如:zkCli.sh -mini2:2181
  • Используйте команду ls, чтобы увидеть, что в данный момент содержится в ZooKeeper:
 ls /
  • Чтобы создать новый znode, используйте create /zk myData. Эта команда создает новый узел "zk" и связанную с ним строку:
create /zk "myData“
  • Мы запускаем команду get, чтобы убедиться, что znode содержит созданную нами строку:
get /zk

-Истень к изменениям этого узла, когда другой клиент меняется / ZK, вывод контролируемых изменений

get /zk watch
  • Используйте команду set, чтобы установить строку, связанную с zk:
set /zk "zsl“
  • Используйте delete для удаления узла znode:
delete /zk
  • Удалить узел (отличие от предыдущего в том, что этот может удалить каталог): rmr
rmr /zk

Справочный документ: http://www.cnblogs.com/likehua/tag/zookeeper/

4. Используйте java для управления API зоопарка.

  • Во-первых, вам нужно ввести пакет jar для zookeeper, Этот пакет jar должен зависеть от других jar-файлов и может быть загружен непосредственно из репозитория maven.
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.10</version>
    <type>pom</type>
</dependency>

  • Обычно используемые операции API, удаления, поиска и модификации следующие: Создать создает узел в дереве каталога Удалить Удалить узел существует проверять, существует ли целевой узел Получить / Установить данные чтения / обновления данных из целевого узла Получить / установить ACL Get / Set Target Unde Access Список контроля Получить детей извлекает список детей Синхронизация ждет данных для передачи данных Код операции, использующий Java следующим образом:
public class SimpleZkClient {
    private static final String CONNECT_URL = "mini1:2181,mini2:2181,mini3:2181";

    private static final int SESSION_TIME_OUT = 2000;

    ZooKeeper zkCli = null;
    @Before
    public void init() throws Exception{
        zkCli = new ZooKeeper(CONNECT_URL, SESSION_TIME_OUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println(event.getType()+"-----------"+event.getPath());
                try{
                    zkCli.getChildren("/", true);
                }catch (Exception e){

                }

            }
        });
    }

    /**
     * @Description 添加节点数据
     * @Author 刘俊重
     */
    @Test
    public void create() throws Exception{
        // 参数1:要创建的节点的路径 参数2:节点大数据 参数3:节点的权限 参数4:节点的类型。上传的数据可以是任何类型,但都要转成byte[]
        String s = zkCli.create("/zk", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    /**
     * @Description 判断节点是否存在
     * @Author 刘俊重
     */
    @Test
    public void isExist() throws Exception{
        Stat exists = zkCli.exists("/zk", false);
        System.out.println(null==exists?"不存在":"存在");
    }

    /**
     * @Description 获取节点数据
     * @Author 刘俊重
     */
    @Test
    public void getData() throws Exception{
        byte[] data = zkCli.getData("/zk", false, null);
        System.out.println("节点数据:"+new String(data));
    }

    /**
     * @Description 遍历节点数据
     * @Author 刘俊重
     */
    @Test
    public void getChildren() throws  Exception{
        List<String> children = zkCli.getChildren("/", false);
        for(String s : children){
            System.out.println("节点名称:"+s);
        }
        Thread.sleep(Long.MAX_VALUE);
    }

    /**
     * @Description 删除节点数据
     * @Author 刘俊重
     */
    @Test
    public void delete() throws Exception{
        //参数2:指定要删除的版本,-1表示删除所有版本
        zkCli.delete("/zk",-1);
        this.isExist();
    }

    /**
     * @Description 更新节点数据
     * @Author 刘俊重
     */
    @Test
    public void update() throws Exception{
        Stat stat = zkCli.setData("/zk", "newtest".getBytes(), -1);
        this.getData();
    }
}

Thread.sleep (long.max_value); для того, чтобы не позволить завершить программу, пусть она упадет, пусть она некоторое время спят, проверьте, реализован ли слушатель, и напишите работу уведомления в функции обратного вызова процесса, ZKCLI .getchildren ("/", true); в это время, если мы работаем узел операции зоопанчика через командную строку Linux, она запускает событие прослушивания здесь.

5. Сценарии использования Zookeeper

5.1 Сценарий 1. Клиенты динамически воспринимают изменения узлов сервера для достижения высокой доступности

Теперь предположим, что есть такое требование: есть несколько серверных узлов, которые могут быть динамически онлайн и офлайн; любой клиент должен иметь возможность ощущать изменения серверного узла в режиме реального времени, а затем подключаться к тем узлам, которые в данный момент могут обеспечить Сервисы. Идея реализации: мы можем использовать zookeeper, стороннее промежуточное ПО, для регистрации информации об узле сервера с помощью zookeeper при запуске каждого сервера (например: /servers/server01;/servers/server02); клиент передает информацию перед каждым вызов.Метод getChildren получает последнюю информацию об узле сервера.В то же время клиент регистрирует и отслеживает zookeeper для отслеживания изменений узлов сервера; если сервер server01 отключится в определенный момент, zookeeper отправит изменение узла уведомление клиенту и обратный вызов метода процесса, чтобы получить последнюю информацию о сервере. Код сервера выглядит следующим образом:

public class DistributeServer {

    private static final String CONNECT_URL = "mini1:2181,mini2:2181,mini3:2181";

    private static final int SESSION_TIME_OUT = 2000;

    private static final String PARENT_NODE = "/servers";

    private ZooKeeper zkCli = null;

    /**
     * @Description 获取连接
     * @Author 刘俊重
     * @Date 2017/12/13
     */
    public void getConnect() throws Exception{
        zkCli = new ZooKeeper(CONNECT_URL, SESSION_TIME_OUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println(event.getType()+"-----------"+event.getPath());
                try{
                    zkCli.getChildren("/", true);
                }catch (Exception e){

                }

            }
        });
    }

    /**
     * @Description 服务器启动时向zookeeper注册服务信息
     * @Author 刘俊重
     * @Date 2017/12/13
     */
    public void registerServer(String hostName) throws  Exception {
        String s = zkCli.create(PARENT_NODE + "/", hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("服务器:"+hostName+"已经注册完毕");
    }

    /**
     * @Description 模拟实际的业务操作
     * @Author 刘俊重
     * @Date 2017/12/13
     */
    public void handelBusiness(String hostname) throws  Exception {
        System.out.println("服务器:"+hostname+"正在处理业务。。。");
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws  Exception {
        DistributeServer server = new DistributeServer();
        server.getConnect();
        server.registerServer(args[0]);
        server.handelBusiness(args[0]);
    }
}

Код клиента выглядит следующим образом:

/**
 * @author 刘俊重
 * @Description 模拟客户端,拉取最新服务器节点列表并向zookeeper设置监听
 */
public class DistributeClient {

    private static final String CONNECT_URL = "mini1:2181,mini2:2181,mini3:2181";

    private static final int SESSION_TIME_OUT = 2000;

    private static final String PARENT_NODE = "/servers";

    private ZooKeeper zkCli = null;

    private volatile List<String> serverList = null;

    /**
     * @Description 获取连接
     * @Author 刘俊重
     * @Date 2017/12/13
     */
    public void getConnect() throws Exception{
        zkCli = new ZooKeeper(CONNECT_URL, SESSION_TIME_OUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
                System.out.println(event.getType()+"-----------"+event.getPath());
                try{
                    //重新更新服务器列表,并且注册了监听
                    getServerList();
                }catch (Exception e){

                }

            }
        });
    }

    /**
     * @Description 获取服务器子节点信息,并对父节点进行监听
     * @Author 刘俊重
     */
    public void getServerList() throws  Exception {
        List<String> children = zkCli.getChildren(PARENT_NODE, true);
        List<String> servers = new ArrayList<String>();
        for(String child : children){
            // child只是子节点的节点名
            byte[] data = zkCli.getData(PARENT_NODE + "/" + child, false, null);
            servers.add(new String(data));
        }
        //把servers赋值给成员变量serverList,以提供给各业务线程使用
        serverList = servers;
        System.out.println("节点数据:"+serverList);

    }
    /**
     * @Description 模拟实际的业务操作
     * @Author 刘俊重
     * @Date 2017/12/13
     */
    public void handelBusiness() throws  Exception {
        System.out.println("客户端开始工作。。。");
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
        DistributeClient client = new DistributeClient();

        client.getConnect();
        client.getServerList();
        client.handelBusiness();
    }
}

5.2 Сценарий 2: Реализация распределенной блокировки

Предположим, в кластере 50 машин, которые сейчас модифицируют один и тот же файл на определенной машине, как мы можем гарантировать, что файл не будет перепутан?Использование синхронизированной блокировки в java определенно недопустимо, потому что эта блокировка для определенной программы , С точки зрения, и мы вообще не на сервере, как мы можем его заблокировать, может быть достигнута распределенная блокировка, реализованная zookeeper. Идея дизайна: при запуске сервера перейдите в zookeeper, чтобы зарегистрировать узел znode «краткосрочный + серийный номер» (например, /lock/1; /lock/2) и настроить мониторинг изменений родительского узла; получить все дочерние узлы родительского узла и сравнить размер серийного номера; согласиться на получение блокировки с наименьшим серийным номером для работы с определенным файлом, удалить собственный узел после завершения операции (эквивалент снятия блокировки) , и зарегистрируйте новый узел znode «временный + серийный номер»; другие программы получают После уведомления об изменении узла, отправленного zookeeper, сравните размер серийного номера, чтобы увидеть, кто получит новую блокировку.

public class DistributedClientLock {
    // 会话超时
    private static final int SESSION_TIMEOUT = 2000;
    // zookeeper集群地址
    private String hosts = "mini1:2181,mini2:2181,mini3:2181";
    private String groupNode = "locks";
    private String subNode = "sub";
    private boolean haveLock = false;

    private ZooKeeper zk;
    // 记录自己创建的子节点路径
    private volatile String thisPath;

    /**
     * 连接zookeeper
     */
    public void connectZookeeper() throws Exception {
        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {

                    // 判断事件类型,此处只处理子节点变化事件
                    if (event.getType() == EventType.NodeChildrenChanged && event.getPath().equals("/" + groupNode)) {
                        //获取子节点,并对父节点进行监听
                        List<String> childrenNodes = zk.getChildren("/" + groupNode, true);
                        String thisNode = thisPath.substring(("/" + groupNode + "/").length());
                        // 去比较是否自己是最小id
                        Collections.sort(childrenNodes);
                        if (childrenNodes.indexOf(thisNode) == 0) {
                            //访问共享资源处理业务,并且在处理完成之后删除锁
                            doSomething();

                            //重新注册一把新的锁
                            thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,
                                    CreateMode.EPHEMERAL_SEQUENTIAL);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        // 1、程序一进来就先注册一把锁到zk上
        thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);

        // wait一小会,便于观察
        Thread.sleep(new Random().nextInt(1000));

        // 从zk的锁父目录下,获取所有子节点,并且注册对父节点的监听
        List<String> childrenNodes = zk.getChildren("/" + groupNode, true);

        //如果争抢资源的程序就只有自己,则可以直接去访问共享资源
        if (childrenNodes.size() == 1) {
            doSomething();
            thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);
        }
    }

    /**
     * 处理业务逻辑,并且在最后释放锁
     */
    private void doSomething() throws Exception {
        try {
            System.out.println("gain lock: " + thisPath);
            Thread.sleep(2000);
        } finally {
            System.out.println("finished: " + thisPath);
            //释放锁
            zk.delete(this.thisPath, -1);
        }
    }

    public static void main(String[] args) throws Exception {
        DistributedClientLock dl = new DistributedClientLock();
        dl.connectZookeeper();
        Thread.sleep(Long.MAX_VALUE);
    }

}

Справочный документ: http://www.cnblogs.com/likehua/tag/zookeeper/

6 Механизм выборов смотрителя зоопарка

6.1 Новый кластер paxos

Возьмите простой пример, чтобы проиллюстрировать весь избирательный процесс. Предположим, есть кластер zookeeper, состоящий из пяти серверов, их идентификаторы от 1 до 5, и все они недавно запущены, то есть нет исторических данных, и они одинаковы с точки зрения объема хранимых данных. что эти серверы расположены по порядку. Запустите его и посмотрите, что произойдет.

  1. Сервер 1 запущен, и в это время запущен только один из его серверов, и нет ответа на отправляемый им отчет, поэтому его статус выбора всегда LOOKING
  2. Запускается сервер 2, он связывается с сервером 1, который был запущен в начале, и обменивается результатами своих выборов друг с другом.Поскольку ни у одного нет исторических данных, выигрывает сервер 2 с большим значением id, но поскольку он не достигает более половины Согласитесь выбрать его (более половины из них 3 в этом примере), поэтому серверы 1, 2 продолжают оставаться в состоянии LOOKING.
  3. 3 Запустите сервер, основываясь на предыдущем теоретическом анализе, сервере сервера 1, 2, 3 стать боссом, а вышеупомянутое разница состоит в том, что на этот раз есть три сервера, избранные его, поэтому он стал лидером выборов.
  4. Запускается сервер 4. Согласно предыдущему анализу, теоретически сервер 4 должен быть самым большим среди серверов 1, 2, 3 и 4. Однако, поскольку более половины серверов выбрали сервер 3, он может получать только жизнь младшего брата.
  5. Сервер 5 запускается, как и 4, когда младший брат.

6.2 Механизм выбора не новых кластеров (восстановление данных)

Затем, во время инициализации, выборы выполняются в соответствии с приведенными выше инструкциями, но когда зоопарк работает в течение определенного периода времени, машина отключается, и процесс выборов становится относительно сложным, когда выборы переизбираются. Необходимо добавить идентификатор данных, идентификатор лидера и логические часы. Идентификатор данных: новый идентификатор данных больше, и идентификатор будет обновляться каждый раз при обновлении данных. Идентификатор лидера: это значение в myid, которое мы настроили, по одному для каждой машины. Логические часы: это значение увеличивается от 0, и каждому выбору соответствует значение, то есть: если оно находится в одном и том же выборе, то это значение должно быть постоянным; Чем больше значение логических часов, тем больше процесс выбора лидер на этот раз обновляется. Критерии выбора тогда становятся: 1. Результат выборов с маленькими логическими часами игнорируется, а голосование проводится повторно 2. После объединения логических часов побеждает тот, у кого наибольший идентификатор данных. 3. В случае одинакового id данных побеждает лидер с наибольшим id Лидер избирается по этому правилу.