分布式协调组件Zookeeper

分布式架构与集中式架构:

  集中式架构:就是把所有的程序,功能,模块,都集中在一个模块中,部署在一台服务器上,从而对外提供服务.(单体架构,单体服务,单体应用),基本上,一个war包闯天下.

  分步式架构:就是把所有的程序,功能,模块,拆分成不同的子项目,部署在多台不同的服务器上,这些子项目协同合作,共同对外提供服务.

分布式的难题:

随着社会的发展,单体应用已经不能满足人们对于大流量,高并发的需求,现在大型互联网项目基本都是分布式架构,但是分布式因此也带来了如下难题.

  1:如何合理的拆分系统,基于什么粒度?

  2拆分后各个子系统之间如何通信

  3:如何适应不断增长的业务需求,使其具有良好的扩展性?

  4:如何保证各个子系统的可靠性和数据一致性.

由于分布式的复杂性,各个环节出现了很多优秀的开源解决方案,进行分布式架构就需要用到很多分布式架构.

分布式应用程序协调服务:Zookeeper

  Zookeeper是什么?

    Zookeeper是一个开源的分布式应用程序协调服务,也是一个服务器.是Google的Chubby的一个开源实现,是Hadoop和Hbase的重要组件,在分布式领域应用广泛.

  Zookeeper可以做什么?

   配置管理:在上面存储项目的配置信息

   命名服务:服务名与ip的映射信息存在上面

   分布式锁:多个并发访问的协调

   集群管理:服务器的宕机感知与处理

Zookeeper的运行环境:

  官网:http://zookeeper.apache.org

  下载地址:https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar  

     安装:直接解压缩即可  tar -zxvf apache-zookeeper-3.5.5-bin.tar.gz  -C /usr/local

     配置:需要java运行环境,在Zookeeper的conf目录下:cp zoo_sample.cfg zoo.cfg,zookeeper启动时会读取该文件作为默认配置文件

  配置文件详解:

    tickTimeZookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,单位毫秒

    initLimit集群中的follower服务器与leader服务器之间初始连接时能容忍的最多心跳数(tickTime的数量)

    syncLimit集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量)

    dataDir存储zookeeper的快照文件、pid文件,默认为/tmp/zookeeper,建议在zookeeper安装目录下创建data目录,将dataDir配置改为/usr/local/zookeeper-3.6.1/data

    clientPort客户端连接zookeeper的端口,即zookeeper对外的服务端口,默认为2181

  启动:(zk的bin目录下) ./zkServer.sh start

  关闭:(zk的bin目录下) ./zkServer.sh stop

  查看状态:(zk的bin目录下) ./zkServer.sh status

  常见命令:  ./zkServer.sh {start|start-foreground|stop|restart|status|print-cmd}

 

Zookeeper的数据结构:

  Zookeeper的数据结构类似属性结构,每个节点称之为znode,可以存储信息,可以进行增删改查,可以增加子节点.

  节点分为四种类型:

    PERSISTENT 持久化目录节点           客户端与Zookeeper断开连接后,该节点依旧存在

    PERSITENT_SEQUENTIAL 持久化顺序目录节点 客户端与Zookeeper断开连接后,节点依旧存在,只是Zookeeper给该节点名称顺序编号

    EPHEMERAL 临时目录节点

    EPHEMERAL_SEQUENTIAL 临时顺序编号目录节点

 Zookeeper客户端

  图形界面客户端

    Zookeeper图形客户端工具下载地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip

    下载解压即可使用,进入ZooInspector\build\zookeeper-dev-Zooinspector.jar 使用java -jar启动,需要java运行环境,运行时需耐性等待.

  命令行客户端

    使用zkCli连接Zookeeper , 默认直接连127.0.0.1:2181 ./zkCli.sh 命令行客户端主要通过命令对zookeeper进行操作,一般都是增删改查,输入无效命令会给出提示信息.

  查询:

     ls /path  列出path节点下的节点 例: ls /   列出根节点下节点

    get /path 查看节点数据   

    stat /path查看节点状态

  cZxid:节点创建时的zxid

  ctime:节点创建时间

  mZxid:节点最近一次更新时的zxid

  mtime:节点最近一次更新的时间

  pZxid:操作当前节点的子节点列表的事务id(这种操作包含增加子节点,删除子节点)

  cversion:子节点数据更新次数

  dataVersion:本节点数据更新次数

  aclVersion:节点ACL(授权信息)的更新次数

  ephemeralOwner:如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id,如果该节点不是临时节点,ephemeralOwner值为0

  dataLength:节点数据长度

  numChildren:子节点个数

  增加

    create [-s] [-e] path data acl

    其中 -s 表示顺序节点,-e表示临时节点  -s 和 -e 可以同时结合使用 临时节点,当客户端会话结束后,临时节点会被删除;顺序节点特性可用于生成在分布式环境下的主键生成器;

    create /root 123456 创建节点并且指定数据;

    close 命令关闭当前会话;

    quit 命令退出连接的客户端命令行;

  修改

    set path data [version]

    比如:set /node1 998

  删除

    delete path [version]

    删除指定节点数据;注意:delete只能删除不包含子节点的节点,如果要删除的节点包含子节点,使用deleteall命令;

Java客户端

  Zookeeper服务器有三种客户端

    Zookeeper:Zookeeper官方提供的原生java客户端;API文档: https://zookeeper.apache.org/doc/current/api/index.html

    ZkClient:在原生Zookeeper基础上进行扩展的开源第三方Java客户端;Github:https://github.com/sgroschupf/zkclient

    Curator:Netflix公司在原生Zookeeper基础上扩展的开源第三方客户端;官网:http://curator.apache.org Netflixhttps://github.com/Netflix/curator

 Zookeeper原生客户端操作:

  创建Maven项目,引入依赖:

  <!--zookeeper的官方客户端jar包依赖-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.5</version>
        </dependency>

API实例代码

package com.blue.client;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;


public class ZookeeperClient01 {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String ROOT_NODE = "/root";

    private static CountDownLatch countDownLatch = new CountDownLatch(1);


    public static void main(String[] args) throws Exception {

        System.out.println("创建开始1");

        ZooKeeper zooKeeper = new ZooKeeper(ZK_ADDR, 3000, watchedEvent -> {
            //获取事件状态
            KeeperState state = watchedEvent.getState();
            //获取事件类型
            EventType type = watchedEvent.getType();
            //如果连接建立
            System.out.println("创建开始2");
            if (KeeperState.SyncConnected == state) {
                if (EventType.None == type) {
                    System.out.println("连接成功");
                    countDownLatch.countDown();
                    System.out.println("创建开始3");
                }
            }
        });
        System.out.println("创建开始4");
        countDownLatch.await();
        System.out.println("创建开始5");

        //创建节点:   /root ,数据:Zookeeper      开放的访问控制策略   持久化节点
        String nodePath = zooKeeper
                .create(ROOT_NODE, "zookeeper".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
        System.out.println(nodePath);

        String nodePath1 = zooKeeper
                .create(ROOT_NODE + "/home", "zookeeper-home".getBytes(), Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
        System.out.println(nodePath1);

        //获取数据
        byte[] data = zooKeeper.getData(ROOT_NODE, false, null);
        System.out.println(new String(data));

        //修改数据
        Stat stat = zooKeeper.setData(ROOT_NODE, "修改数据了".getBytes(), -1);
        System.out.println(stat);

        //删除节点  -1标识任何版本
        zooKeeper.delete(ROOT_NODE, -1);
        Stat exists = zooKeeper.exists(ROOT_NODE, false);
        System.out.println(exists);

        //获取子节点
        List<String> list = zooKeeper.getChildren(ROOT_NODE, false);
        list.forEach(o -> System.out.println("遍历1:" + o.toString()));


    }


}
View Code

ZkClient客户端操作:

 创建Maven项目,引入依赖:

   <!--zkclient客户端的jar包依赖-->
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.11</version>
        </dependency>

API示例代码

package com.blue.client;

import java.util.List;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

public class ZookeeperClient02 {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String ROOT_NODE = "/root";

    public static void main(String[] args) {
        //建立连接
        ZkClient client = new ZkClient(ZK_ADDR, 3000, 30000, new ZkSerializer() {
            @Override
            public byte[] serialize(Object o) throws ZkMarshallingError {
                return o.toString().getBytes();
            }

            @Override
            public Object deserialize(byte[] bytes) throws ZkMarshallingError {
                return new String(bytes);
            }
        });

        //创建持久化顺序节点
        client.create(ROOT_NODE, "this is test zk", CreateMode.PERSISTENT);
        client.create(ROOT_NODE + "/home", "this is test zk", CreateMode.PERSISTENT);
        //读取节点数据
        String nodeData = client.readData(ROOT_NODE);
        System.out.println(nodeData);

        //读取节点数据返回节点状态信息
        Stat test_home_node = client.writeDataReturnStat(ROOT_NODE + "/home", "test home node", -1);
        System.out.println(test_home_node);

        //查询节点是否存在?
        boolean exists = client.exists(ROOT_NODE);
        System.out.println(ROOT_NODE + "节点是否存在?->" + exists);

        //更新节点信息
        client.writeData(ROOT_NODE, "this is new Data");
        Object newData = client.readData(ROOT_NODE);
        System.out.println("节点更新数据是:" + newData);

        //删除节点
        boolean delete = false;
        List<String> children = client.getChildren(ROOT_NODE);
        if (children.size() > 0) {
            //递归删除当前节点和所有子节点
            delete = client.deleteRecursive(ROOT_NODE);
        } else {
            //删除当前节点,若有子节点报错
            delete = client.delete(ROOT_NODE);
        }
        System.out.println("删除节点是否成功?->" + delete);

    }
}
View Code

Curator客户端

  创建Maven项目,引入依赖

  <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.2.0</version>
        </dependency>

Curator客户端的监听  官网:http://curator.apache.org 

Curator采用多种cache机制实现监听,简单来说,cache在客户端缓存了znode的各种状态,当监听了Zookeeper的znode状态变化,将触发event事件,注册的监听器会处理这些事件

Curator支持Path CacheNode CacheTree Cache 三种cache类型;

Path Cache

Path Cache用来观察ZNode的子节点并缓存状态,如果ZNode的子节点被创建、更新或删除,那么Path Cache会触发事件给注册的监听器;

Path Cache是通过PathChildrenCache类实现事件监听,监听器注册接口为PathChildrenCacheListener;

Node Cache

Node Cache用来观察ZNode自身,如果ZNode节点本身被创建、更新或删除,那么Node Cache触发事件给注册的监听器;

Node Cache是通过NodeCache类来实现,监听器注册接口为NodeCacheListener;

Tree Cache

Tree Cache用来观察所有节点和所有数据的变化,监听器注册接口为TreeCacheListener;

API示例代码

package com.blue.client;

import java.util.List;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorEventType;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Stat;

public class ZookeeperClient03 {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String ROOT_NODE = "/root";

    public CuratorFramework client = null;

    //连接zk
    public ZookeeperClient03() {
        RetryPolicy retryPolicy = new RetryNTimes(3, 3000);
        //新版本
        //         client = CuratorFrameworkFactory.newClient(ZK_ADDR, retryPolicy);
        //老版本
        client = CuratorFrameworkFactory.builder().connectString(ZK_ADDR).sessionTimeoutMs(3000)
                .connectionTimeoutMs(8000).retryPolicy(retryPolicy).build();
        //启动客户端
        client.start();
    }

    public static void main(String[] args) throws Exception {
        ZookeeperClient03 client03 = new ZookeeperClient03();
        System.out.println("Zookeeper连接成功:" + client03.client);
        CuratorFrameworkState state = client03.client.getState();
        System.out.println("Zookeeper连接状态信息:" + state);

        //创建节点 可以创建多层级节点信息
        String node = client03.client.create().creatingParentContainersIfNeeded()
                .withMode(CreateMode.PERSISTENT)
                .forPath(ROOT_NODE + "/home", "curator create".getBytes());

        System.out.println("创建节点返回节点路径信息:" + node);

        //读取节点数据
        byte[] bytes = client03.client.getData().forPath(ROOT_NODE + "/home");
        System.out.println("查询节点返回信息:" + new String(bytes));

        //查询子节点
        List<String> stringList = client03.client.getChildren().forPath(ROOT_NODE);
        stringList.forEach(str -> System.out.println("查询获取子节点:" + str));

        //监听节点事件,一次性监听------------------------------------------------------
        client03.client.getData().usingWatcher((Watcher) event -> {
            System.out.println("对节点" + event.getPath() + "进行了操作,操作事件:" + event.getType().name());
            System.out.println("catcher:" + Thread.currentThread().getId());
        }).forPath(ROOT_NODE);

        client03.client.create().inBackground((client, event) -> {
            //创建回调
            if (event.getType() == CuratorEventType.CREATE) {
                System.out.println("监听到了创建:" + event.getResultCode());
            } else if (event.getType() == CuratorEventType.DELETE) {
                System.out.println("监听到了删除:" + event.getResultCode());
            }
        }).forPath(ROOT_NODE);

        //监听节点,对具体节点进行监听,可以一直监听---------------------------------
        NodeCache nodeCache = new NodeCache(client03.client, ROOT_NODE);
        nodeCache.start(true);
        if (nodeCache.getCurrentData() != null) {
            ChildData childData = nodeCache.getCurrentData();
            byte[] data = childData.getData();
            System.out.println("持续监听缓存节点数据" + new String(data));
        } else {
            System.out.println("缓存节点数据是空的");
        }
        nodeCache.getListenable().addListener(() -> {
            System.out.println("监听到事件了........");
            ChildData childData2 = nodeCache.getCurrentData();
            byte[] bytes12 = childData2.getData();
            System.out.println("缓存节点数据1:" + new String(bytes12));
            System.out.println("缓存节点1:" + nodeCache.getPath());
        });

        //子节点监听----------------------------------------------------
        PathChildrenCache pathChildrenCache = new PathChildrenCache(client03.client, ROOT_NODE,
                true);
        // 启动模式:POST_INITIALIZED_EVENT模式才可以监听
        pathChildrenCache.start(StartMode.POST_INITIALIZED_EVENT);

        pathChildrenCache.getListenable().addListener((client, event) -> {
            System.out.println("子节点监执行....");
            byte[] data = event.getData().getData();
            System.out.println("子节点监听数据" + new String(data));
            Type type = event.getType();
            System.out.println("子节点监听类型:" + type.name());
        });

        //对指定节点下的所有节点进行监听-------------------------------------
        TreeCache treeCache = new TreeCache(client03.client, ROOT_NODE);
        treeCache.start();
        //添加监听器

        treeCache.getListenable().addListener((client, event) -> {
            System.out.println("指定节点下所有子节点监听:..........");
            byte[] data = event.getData().getData();
            System.out.println("监听指定节点下所有子节点数据" + new String(data));

            TreeCacheEvent.Type type = event.getType();
            System.out.println("监听指定节点下所有子节点类型:" + type.name());

        });

        // 更新节点信息
        Stat stat = client03.client.setData().withVersion(-1)
                .forPath(ROOT_NODE, "update curator zookeeper client".getBytes());
        System.out.println("更新节点信息" + stat);

        Stat stat1 = client03.client.setData().withVersion(-1)
                .forPath(ROOT_NODE, "update curator zookeeper client1".getBytes());
        System.out.println("更新节点信息" + stat1);

        //删除节点,子节点一起删除
        client03.client.delete().
                guaranteed().
                deletingChildrenIfNeeded().
                forPath(ROOT_NODE);

        Thread.sleep(1000000000);
    }


}
View Code

Zookeeper之ACL

ACL(Access Control List),Zookeeper 作为一个分布式协调框架,其内部存储的都是一些关于分布式系统运行时状态的元数据,默认状态下,所有应用都可以读写任何节点,在任何复杂应用中,这不太安全,ZK通过ACL机制来解决访问权限问题.

Zookeeper节点权限模式:即Scheme;

开发人员做多适用的如下四种权限模式:

ip:    ip模式通过ip地址粒度进行权限控制模式,例如配置了:192.168.122.132即表示权限控制都是针对这个ip地址的,同时也支持按网段分配

digest:  digest是最常用的权限控制模式,采用""username:password"形式的权限标识进行权限配置,Zk会对形成的权限标识先后进行两次加密处理,分别是SHA-1加密算法和Base64编码"

world:  world是一种最开放的权限控制模式,这种模式可以看做是特殊的Digest,他仅仅是一个标识而已,有一个唯一的id,anyone,代表所有人,

auth:  不使用任何id,代表任何已认证的用户.

Zookeeper节点操作权限:权限就是指那些通过权限检测后可以被允许执行的操作,在Zk中,对数据的操作权限分为以下五大类

create,delete,read,write,admin增删改查,管理权限,这五种权限简写为crwda

命令行操作:

1.

增加认证用户

  addauth digest 用户名:密码明文:权限

  addauth digest zs:123456;cdrwa

设置权限

  setAcl /path auth:用户名:密码明文:权限

  setAcl /root auth:zhangsan:123456:cdrwa

查看Acl设置

  getAcl /root

2.

setAcl /root digest:用户名:密码密文:权限

这里的加密规则是SHA1加密,然后base64编码

 

节点权限API示例代码

package com.blue.client;

import java.util.ArrayList;
import java.util.List;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

public class ZookeeperClient04 {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String ROOT_NODE = "/acl";

    CuratorFramework client = null;

    public ZookeeperClient04() {

        //设置重试策略
        RetryPolicy retryPolicy = new RetryNTimes(3, 2000);

        //创建Zookeeper客户端,新版本
        client = CuratorFrameworkFactory.newClient(ZK_ADDR, retryPolicy);
        //老版本创建连接客户端
        client = CuratorFrameworkFactory.builder()
                .authorization("digest", "zhangsan:123456".getBytes()).connectString(ZK_ADDR)
                .sessionTimeoutMs(5000).connectionTimeoutMs(10000).retryPolicy(retryPolicy).build();
        client.start();
    }

    public static void main(String[] args) throws Exception {
        ZookeeperClient04 zookeeperClient04 = new ZookeeperClient04();
        CuratorFrameworkState state = zookeeperClient04.client.getState();
        System.out.println("状态信息:" + state);
        if (state == CuratorFrameworkState.STARTED) {
            System.out.println("启动成功:" + state);
        }

        List<ACL> aclList = new ArrayList<>();
        Id zhangsan = new Id("digest",
                DigestAuthenticationProvider.generateDigest("zhangsan:123456"));
        Id lisi = new Id("digest", DigestAuthenticationProvider.generateDigest("lisi:123456"));
        Id wangwu = new Id("digest", DigestAuthenticationProvider.generateDigest("wangwu:123456"));

        aclList.add(new ACL(Perms.ALL, zhangsan));
        aclList.add(new ACL(Perms.READ, lisi));
        aclList.add(new ACL(Perms.READ | Perms.WRITE, wangwu));

        //完全开放的节点权限
        //        String s1 = zookeeperClient04.client.create().creatingParentsIfNeeded()
        //                .withMode(CreateMode.PERSISTENT).withACL(Ids.OPEN_ACL_UNSAFE) //指定ACL
        //                .forPath(ROOT_NODE + "/home", "curator open zookeeper".getBytes());

        // 指定访问控制权限的节点
        String s = zookeeperClient04.client.create().creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT).withACL(aclList)
                .forPath(ROOT_NODE + "/home4", "curator close zookeeper".getBytes());

        Stat stat = zookeeperClient04.client.setData()
                .forPath(ROOT_NODE + "/home4", "1111111111111111".getBytes());
        byte[] bytes = zookeeperClient04.client.getData().forPath(ROOT_NODE + "/home3");

        System.out.println("当前数据节点" + new String(bytes));
    }

}
View Code

 

分布式全局唯一ID生成:

1.全局唯一,不能重复

2,递增,下一个ID大于上一个ID(常规)

3,信息安全,非连续id,避免恶意用户/竞争对手发现ID规则,从而猜出下一个ID或者根据ID总量猜出业务总量.

4.高可用.高性能,低延迟.

解决方案:

  1:UUID,randomUUID() ,多数场景不适用

  2.数据库自增,过度依赖数据库,数据库高并发易发生性能瓶颈.

  3.Redis方案,redis原子操作 INCR和INCRBY(redis自增)实现递增,同时可使用集群,设置每台初始值 1,2,3,4,5,递增5

  4:Twiitter的snowflake算法(https://github.com/twitter/snowflake)

  5:MongoDB的ObjectID

  6:Zookeeper方案: 持久顺序节点,节点版本号

 

顺序节点&&节点版本号

package com.blue.generator;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;


public class IdGenerator {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String ID_NODE = "/id";

    CuratorFramework client = null;

    /**
     * 倒计数器
     */
    CountDownLatch countDownLatch = new CountDownLatch(1);

    public IdGenerator() {
        //重试策略
        RetryPolicy retryPolicy = new RetryNTimes(3, 3000);
        //创建Zookeeper
        //         client = CuratorFrameworkFactory.newClient(ZK_ADDR, retryPolicy);
        //老版本创建连接客户端
        client = CuratorFrameworkFactory.builder().connectString(ZK_ADDR).sessionTimeoutMs(5000)
                .connectionTimeoutMs(10000).retryPolicy(retryPolicy).build();
        //启动客户端
        client.start();
    }

    /**
     * ID生成方法  基于顺序节点
     */

    public String idGenerator() throws Exception {
        if (null == client.checkExists().forPath(ID_NODE)) {
            //未创建 创建节点
            String s = client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(ID_NODE);
            client.delete().forPath(s);
            return s;
        }
        return null;
    }

    /**
     * ID生成方法  基于版本号
     */
    public long idGeneratorByVersion() throws Exception {
        if (null == client.checkExists().forPath(ID_NODE)) {
            //未创建 创建节点
            client.create().withMode(CreateMode.PERSISTENT).forPath(ID_NODE);
        }
        Stat stat = client.setData().withVersion(-1).forPath(ID_NODE);

        return stat.getVersion();
    }


    public static void main(String[] args) throws InterruptedException {
        IdGenerator idGenerator = new IdGenerator();
        try {
            idGenerator.runThread();
        } catch (Exception e) {
            e.printStackTrace();
        }

        Thread.sleep(10000);
    }

    //多线程测试
    void runThread() {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 16; i++) {
            executorService.submit(() -> {
                try {
                    //所有线程全部在此等候
                    countDownLatch.await();
                    System.out.println(
                            "Thread:" + Thread.currentThread().getName() + ", time: " + System
                                    .currentTimeMillis());
                    //执行业务代码
                    String s = idGenerator();
                    String substring = s.substring(3);
                    System.out.println(substring);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out
                            .println("Thread:" + Thread.currentThread().getName() + e.getMessage());
                }


            });
        }
        //倒计时完毕所有线程同时执行
        countDownLatch.countDown();
        try {
            // 传达完毕信号
            executorService.shutdown();
            // (所有的任务都结束的时候,返回TRUE)
            if (!executorService.awaitTermination(5 * 1000, TimeUnit.MILLISECONDS)) {
                // 超时的时候向线程池中所有的线程发出中断(interrupted)。
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            // awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
            System.out.println("awaitTermination interrupted: " + e);
            executorService.shutdownNow();
        }
    }
}
View Code

 

Zookeeper分布式锁

  实现分布式锁有多重方式:

  第一种:基于数据库实现分布式锁:

      1.在数据库新建一个表(lock)

create table lock(

  id

  method_name(唯一约束)

...

)

      2.获取锁时向表中插入一条数据,由于有唯一约束,只会有一条线程插入成功,插入成功的线程获得锁,可以继续操作,没有插入成功的线程没有获得锁,不能操作.

      3.解锁时删除这条数据

主要问题:

  可用性差,数据库故障会导致业务系统不可用;

  数据库性能存在瓶颈,不适合高并发场景

  删除锁失败容易导致死锁

  锁的失效时间难以控制

 

第二种:基于Redis实现分布式锁

  加锁:

  setnx命令加锁,并设置锁的有效时间和持有人id标识;

  expire命令设置锁的过期时间

  解锁:检查是否持有锁,然后删除锁delete

  基于redis实现分布式锁,采用一个开源项目 http://redisson.org

第三种:基于zookeeper实现分布式锁

  zookeepr是分布式系统的协调服务,实现原理主要是临时有序节点+监听来实现

Curator客户端给我们提供现成的分布式互斥锁来实现分布式锁

1、创建分布式互斥锁
InterProcessMutex lock = new InterProcessMutex(zookeeperCuratorClient.client, "/storeLock");

2、获取分布式互斥锁
if (lock.acquire(10, TimeUnit.SECONDS))
3、释放分布式互斥锁
lock.release();

具体示例,仅供参考

package com.blue.lock;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.RetryNTimes;


public class CuratorLock {

    public static final String ZK_ADDR = "127.0.0.1:2181";

    public static final String LOCK_NODE = "/lock";

    CuratorFramework client = null;

    CountDownLatch countDownLatch = new CountDownLatch(1);
    public static int j = 1;


    public CuratorLock() {
        RetryPolicy retryPolicy = new RetryNTimes(3, 3000);
        //新版本
        //         client = CuratorFrameworkFactory.newClient(ZK_ADDR, retryPolicy);
        //老版本
        client = CuratorFrameworkFactory.builder().connectString(ZK_ADDR).sessionTimeoutMs(3000)
                .connectionTimeoutMs(8000).retryPolicy(retryPolicy).build();
        //启动客户端
        client.start();
    }

    public static void main(String[] args) throws Exception {
        runThread();

    }

    //多线程测试
    static void runThread() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        CuratorLock curatorLock = new CuratorLock();
        InterProcessLock lock = new InterProcessMutex(curatorLock.client, LOCK_NODE);
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> {
                try {
                    //所有线程全部在此等候
                    countDownLatch.await();
                    if (lock.acquire(10, TimeUnit.SECONDS)) {
                        System.out.println("-----------执行了业务代码" + (j++));
                        lock.release();
                    }
                    System.out.println(
                            "Thread:" + Thread.currentThread().getName() + ", time: " + System
                                    .currentTimeMillis());
                    //执行业务代码
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out
                            .println("Thread:" + Thread.currentThread().getName() + e.getMessage());
                }


            });
        }
        //倒计时完毕所有线程同时执行
        countDownLatch.countDown();
        try {
            // 传达完毕信号
            executorService.shutdown();
            // (所有的任务都结束的时候,返回TRUE)
            if (!executorService.awaitTermination(50 * 1000, TimeUnit.MILLISECONDS)) {
                // 超时的时候向线程池中所有的线程发出中断(interrupted)。
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            // awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
            System.out.println("awaitTermination interrupted: " + e);
            executorService.shutdownNow();
        }
    }

}
View Code

 Zookeeper原生参考

package com.blue.lock;

import java.io.IOException;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZookeeperLock {

    public static final String ZK_ADDR = "127.0.0.1";

    public static final String ZK_NODE = "zkLocks";

    public static final int sessionTimeout = 10000;

    public static final byte[] bytes = new byte[0];

    /**
     * zookeeper原生客户端
     */
    private ZooKeeper zooKeeper;

    /**
     * 锁节点的名称
     */
    private String lockName;

    /**
     * 当前锁节点的名称
     */
    private String currentLockName;

    CountDownLatch countDownLatch = new CountDownLatch(1);

    /**
     * 构造方法
     *
     * @param lockName 锁节点的名称通过构造方法初始化
     */
    public ZookeeperLock(String lockName) {
        this.lockName = lockName;
        try {
            new ZooKeeper(ZK_ADDR, sessionTimeout, watchedEvent -> {
                if (watchedEvent.getState() == KeeperState.SyncConnected) {
                    System.out.println("Zookeeepr连接上了");
                    countDownLatch.countDown();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            //阻塞,等待Zookeeper连接上
            countDownLatch.await();
            //连接上之后 
            Stat exists = zooKeeper.exists(lockName, false);
            if (null == exists) {
                //节点不存在  创建节点
                zooKeeper.create(lockName, bytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * zookeeper分布式锁:加锁(获取分布式锁)
     */
    public void lock() {

        try {
            String myNode = zooKeeper.create(ZK_NODE + "/" + lockName, bytes, Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);
            //拿到根节点下的所有临时有序子节点
            List<String> children = zooKeeper.getChildren(ZK_NODE, false);
            //所有根节点全部拿到

            //TreeSet
            TreeSet<String> sortNodes = new TreeSet<String>();
            for (String node : children) {
                sortNodes.add(ZK_NODE + "/" + myNode);
            }
            //排好顺序的set集合中取
            String minNode = sortNodes.first();
            System.out.println("当前节点:" + myNode);
            System.out.println("最小节点:" + minNode);

            //当前节点若是最小节点,可以拿到分布式锁
            if (myNode.equals(minNode)) {
                //当前节点是最小节点,赋值,返回
                currentLockName = myNode;
                return;
            }
            //拿到前一个节点
            String preNode = sortNodes.lower(myNode);
            System.out.println("前一个节点preNode=" + preNode);

            //其他进来的线程没有拿到分布式锁,创建的节点不是最小的,监听前一个节点的删除事件

            CountDownLatch countDownLatch = new CountDownLatch(1);

            if (null != preNode) {
                //前一个节点不为空,监听前一个节点的删除事件
                Stat exists = zooKeeper.exists(preNode, watchedEvent-> {
                    if (watchedEvent.getType() == EventType.NodeDeleted) {
                        countDownLatch.countDown();
                    }
                });
                if (null != exists) {
                    //等待监听到删除事件,执行
                    countDownLatch.await();
                    currentLockName = myNode;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解锁操作
     */
    public void release() {
        //解锁 删除当前节点
        if (currentLockName != null) {
            try {
                zooKeeper.delete(currentLockName, -1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }


}
View Code

 

Zookeeper集群:

Zookeeper集群有两种工作的模式,一种是单机版本,另一种是集群方式;Zookeeper作为一个服务,本身也有可能发生故障,所以我们需要将Zookeeper进行集群,避免发生单点故障,以保证Zookeeper本身的高可用性;

Zookeepr集群一般有三种角色:领导者(leader),跟随者(follower),一个观察者(observer),集群只要有半数以上工作,集群就整体可用,一般奇数台服务器最佳.

下载三台纯净的zookeeper服务器,cp zoo_sample.cfg为zoo.cfg,添加以下配置,这样三台服务器就可以通过端口进行通信和投票,

clientPort 每台服务器都要不一样,防止端口被占用.修改zoo.cfg

clientPort=2181
#server.myid=ip:通信端口:投票端口
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2890:3890

 #zookeeper内嵌的server服务器的端口,默认是8080

 admin.serverPort=3181

 #配置数据目录,此data目录若没有需要手动创建

 dataDir=/usr/local/apache-zookeeper-3.6.1-bin-01/data

 

 三台服务器创建data目录 mkdir data,创建myid文件,分别写入zoo.cfg中配置的server.后面对应的服务器编号  1 ,每台对应各自编号

启动三台服务器,./zkServer.sh start

查看集群状态 ./zkServer.sh status

Mode: leader 领导者  Mode: follower 跟随者

领导者:处理改变节点状态的请求,也叫作事务请求,事务请求的唯一调度者和处理者,保证集群事务处理的顺序性;

跟随者:处理查询请求,事务请求会转发给领导者处理.

观察者:特殊的跟随者,不参与投票,观察集群状态,同步数据,也可以处理非事务请求,转发事务请求给领导者.

观察者配置:

peerType=observer
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2890:3890
server.4=localhost:2891:3891:observer

观察者的出现是为了避免领导者挂掉,过多的服务器参与投票,导致选举过程缓慢,影响写性能而出现的.

posted @ 2020-07-17 10:31  蚂蚁style  阅读(567)  评论(0编辑  收藏  举报