分布式协调组件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启动时会读取该文件作为默认配置文件
配置文件详解:
tickTime:Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,单位毫秒
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 Netflix:https://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())); } }
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); } }
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 Cache,Node Cache,Tree 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); } }
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)); } }
分布式全局唯一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(); } } }
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(); } } }
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); } } } }
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
观察者的出现是为了避免领导者挂掉,过多的服务器参与投票,导致选举过程缓慢,影响写性能而出现的.