一、zookeeper的安装
1.下载linux环境zookeeper安装包(以3.6.3版本为例)
下载地址: zookeeper3.6.3
2.上传到服务器再解压:
tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz
3.修改安装目录名:
mv apache-zookeeper-3.6.3-bin zookeeper-3.6.3
4.修改配置文件名称:
mv ./conf/zoo_sample.cfg zoo.cfg
注意这里可以修改配置文件相关参数的:
5.在bin目录下启动zookeeper服务端:
./zkServer.sh start
如下图即为启动成功:
二、zookeeper的客户端和服务端的使用
操作zookeeper server端有两种方式,通过客户端连接或者java api代码实现连接:
1.服务端常用命令:
./zkServer.sh start //启动zk
./zkServer.sh status //zk状态
./zkServer.sh stop //关闭zk
./zkServer.sh restart //重启zk
2.客户端命令:
./zkcli.sh //不写默认连接本机
./zkcli.sh -server 127.0.0.1:2181 // 写ip端口可以连接其他机器的zk服务端
quit: //退出客户端连接
ls / //查看节点列表
ls /app1 //查询app1节点下的所有子节点
create /app1 //创建节点
get /app1 //获取节点数据
set /app1 123 // 设置节点数据
delete /app1 //删除节点
deleteall /app1 //删除节点app1及以下的所有子节点
create -e //创建临时节点
create -s //创建顺序节点,自增的
ls -s /app1 //查看节点信息明细
二、javaAPI Curator 实现连接zookeeper服务端
curator是zookeeper的java客户端工具。官网: http://curator.apache.org
1.首先添加依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</
dependency>
2.建立连接:
public class CuratorDemo {
/**
* 测试curator连接zookeeper
*
* @param connectString:服务端ip地址
* @param sessionTimeOUtMs:会话超时时间
* @param connectionTimeOutMs:连接超时时间
* @param retryPolicy:重试策略
*/
public static void testConnect1() {
// 重置策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//第一种方式
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.201:2181", 60 * 1000, 15 * 1000, retryPolicy);
// 开启连接
client.start();
System.out.println("连接成功" + client);
}
/**
* 测试curator连接zookeeper
*
* @param connectString:服务端ip地址
* @param sessionTimeOUtMs:会话超时时间
* @param connectionTimeOutMs:连接超时时间
* @param retryPolicy:重试策略
*/
public static void testConnect2() {
// 重置策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//第二种方式,链式编写
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.1.201:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("zktest") //名称空间
.build();
// 开启连接
client.start();
System.out.println("连接成功" + client);
}
public static void main(String[] args) {
testConnect2();
}
}
2.创建节点:
/**
* 创建节点
*/
public static void createNode() throws Exception{
CuratorFramework client = testConnect2();
String path = client.create().forPath("/app1");
System.out.println(path);
}
2.查询节点:
/**
* 查询节点
*/
public static void getNodeData() throws Exception{
CuratorFramework client = testConnect2();
byte[] data = client.getData().forPath("/app1");
System.out.println(new String(data));
}
打印结果如下:
169.254.31.90 //返回的是节点的ip地址
3.获取子节点数据:
/**
* 获取子节点数据
*/
public static void getChildNodeData() throws Exception{
CuratorFramework client = testConnect2();
List<String> list = client.getChildren().forPath("/");
System.out.println(list);
}
4.获取子节点数据:
/**
* 获取节点的状态等详细信息
* @throws Exception
*/
public static void getNodeStatInfo() throws Exception{
CuratorFramework client = testConnect2();
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath("/app1");
System.out.println(stat);
}
5.修改节点数据:
/**
* 修改节点
*/
public static void setNode() throws Exception{
CuratorFramework client = testConnect2();
client.setData().forPath("/app1","app4".getBytes());
}
5.根据版本号修改节点数据:
/**
* 根据版本号修改节点
* @throws Exception
*/
public static void setNodeByVersion() throws Exception{
CuratorFramework client = testConnect2();
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath("/app1");
int version = stat.getVersion();
System.out.println(version);
client.setData().withVersion(version).forPath("app4","app5".getBytes());
}
6.删除节点:
/**
* 删除节点
* @throws Exception
*/
public static void deleteNode() throws Exception{
CuratorFramework client = testConnect2();
client.delete().forPath("/app1"); //删除节点
client.delete().deletingChildrenIfNeeded().forPath("/app1"); //删除带有子节点的节点
client.delete().guaranteed().forPath("/app1");//必须删除
client.delete().guaranteed().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("回调");
}
}).forPath("/app1"); //删除后回调
}
三、watcher事件监听
zookeeper允许用户在指定的节点上注册一些watcher,并在特定事件触发的时候,zookeeper服务端会将事件通知到订阅的客户端。
zookeeper提供了三种watcher:
1.nodeCache: 只监听某一特定的节点;
2.pathChildrenCache:监听某一Znode的子节点;
3.treeCache:可以监听整个树上的节点,相当于nodeCache 和pathChildrenCache的综合。
/**
* 1.nodeCache监听示例
*/
public static void watcherNodeCache() throws Exception {
// 1.获取客户端对象
CuratorFramework client = testConnect2();
// 2.创建监听对象
NodeCache nodeCache = new NodeCache(client, "/app1");
// 3,注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了。。。。。");
// 获取更新后的节点数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println("获取更新后的节点数据:"+new String(data));
}
});
// 4.开启监听(如果设置为true,则开启监听时加载缓存数据)
nodeCache.start(true);
// 测试
while (true){
}
}
/**
* 2.pathChildrenCache监听示例
*/
public static void watcherPathChildrenCache() throws Exception {
// 1.获取客户端对象
CuratorFramework client = testConnect2();
// 2.创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/app2",true);
// 3,注册监听
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("子节点变化了。。。。。");
System.out.println(pathChildrenCacheEvent);
}
});
// 4.开启监听(如果设置为true,则开启监听时加载缓存数据)
pathChildrenCache.start();
// 测试
while (true){
}
}
/**
* 2.treeCache监听示例
*/
public static void watchertreeCache() throws Exception {
// 1.获取客户端对象
CuratorFramework client = testConnect2();
// 2.创建监听对象
TreeCache treeCache = new TreeCache(client, "/app2");
// 3,注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
System.out.println("节点变化了。。。。");
System.out.println(treeCacheEvent);
}
});
// 4.开启监听(如果设置为true,则开启监听时加载缓存数据)
treeCache.start();
// 测试
while (true){
}
}
四、zookeeper实现分布式锁
在通常的单机应用中,如果有并发场景,我们通常使用sychonized或者lock解决多线程的代码同步问题,而多线程是运行在同一jvm之下,这样是没有任何问题的。
但是在分布式集群环境下,多台机器,属于多个jvm,跨jvm环境是无法通过多线程锁解决线程同步问题的,这个时候就需要引入分布式锁组件,多个jvm机器公用一把锁,来解决跨机器进程间的数据同步问题,这就是分布式锁。
分布式锁的实现方式:
1.基于缓存实现。例如redis (不可靠)
2.zookeeper实现(可靠)
3.数据库层面实现,乐观锁,悲观锁(性能低,不推荐)
zookeeper实现分布式锁的原理:
核心思想:当客户端获取锁时,则创建临时顺序节点,使用完锁后,删除节点。
原理:
客户端获取锁时,会在lock节点下创建临时顺序节点,然后再获取lock下所有的子节点,发现自己创建的节点最小,则获得锁,当不是最小时,则监听比自己小的那个节点,等被删除了则再次触发比较是否最小,若是,则获取锁,若不是,则继续监听。
下面用多线程售票的代码,实现分布式锁应用:
public class Ticket12306 implements Runnable {
private int tickers = 10;// 剩余总票数
private InterProcessMutex lock;
public Ticket12306() {
// 重置策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//第一种方式
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.201:2181", 60 * 1000, 15 * 1000, retryPolicy);
// 开启连接
client.start();
System.out.println("连接成功");
lock = new InterProcessMutex(client, "/lock");
}
@Override
public void run() {
while (true) {
// 获取锁
try {
lock.acquire(3, TimeUnit.SECONDS);
if (tickers > 0) {
System.out.println(Thread.currentThread() + ":" + tickers);
Thread.sleep(1000);
tickers--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Ticket12306 ticket12306 = new Ticket12306();
//创建客户端
Thread t1 = new Thread(ticket12306,"携程");
Thread t2 = new Thread(ticket12306,"飞猪");
t1.start();
t2.start();
}
}
// 等待更新
五、zookeeper集群搭建
首先介绍下zookeeper中leader选举规则和机制:
1.serviceid,服务器id编号,编号越大,权重越大;
2.Zxid,数据id,服务器中存放数据越大,说明越是新的数据,权重越大;
3.在leader选举过程中,如果某台服务器获得了超过半数的选举,则可以成为leader.
下面进行集群搭建:
搭建集群有两种方式,伪集群和真集群,区别在于:
伪集群是在同一集群上安装多个,以端口区分;
真集群是在不同的ip机器上安装,端口一样,ip不一样。
我们以安装真集群为例:
1.首先上传apache-zookeeper-3.6.3-bin.tar 安装包到三台服务器上去;
2.解压: tar -zxvf apache-zookeeper-3.6.3-bin.tar
3.创建集群安装目录分别于三台服务器:
mkdir /usr/local/zookeeper-cluster-1
mkdir /usr/local/zookeeper-cluster-2
mkdir /usr/local/zookeeper-cluster-3
4.将解压的文件复制到集群目录下并修改文件夹名:
cp -r apache-zookeeper-3.6.3-bin /usr/local/zookeeper-cluster-1
mv apache-zookeeper-3.6.3-bin zookeeper-1
cp -r apache-zookeeper-3.6.3-bin /usr/local/zookeeper-cluster-2
mv apache-zookeeper-3.6.3-bin zookeeper-2
cp -r apache-zookeeper-3.6.3-bin /usr/local/zookeeper-cluster-3
mv apache-zookeeper-3.6.3-bin zookeeper-3
5.在zookeeper-1,zookeeper-2,zookeeper-3 目录下创建data目录存放zk的数据
mkdir data
6.修改conf下的zoo_sample.cfg文件名
mv zoo_sample.cfg zoo.cfg
7.分别修改zoo.cfg配置文件中的dataDir参数
dataDir=/usr/local/zookeeper-cluster-1/zookeeper-1/data
dataDir=/usr/local/zookeeper-cluster-2/zookeeper-2/data
dataDir=/usr/local/zookeeper-cluster-3/zookeeper-3/data
到这里三台服务器的zookeeper都安装完毕了,但都是独立的,现需要配置集群:
1.在每个zookeeper/data目录下创建myid文件,内容分别为1,2,3 即服务器的id
echo 1 > /usr/loca/zookeeper-cluster-1/zookeeper-1/data/myid
echo 2 > /usr/loca/zookeeper-cluster-2/zookeeper-2/data/myid
echo 3 > /usr/loca/zookeeper-cluster-3/zookeeper-3/data/myid
2.在每个服务器的zoo.cfg文件配置客户端的访问端口和服务器的ip访问列表:
vim /usr/loca/zookeeper-cluster-1/zookeeper-1/conf/zoo.cfg
server.1=192.168.1.201:2881:3881
server.2=192.168.1.202:2881:3881
server.3=192.168.1.203:2881:3881
解释:
server.服务器id = 服务器ip:服务器之间通信端口:服务器之间投票选举端口
3.启动集群:
/usr/local/zookeeper-cluster-1/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster-2/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster-3/zookeeper-3/bin/zkServer.sh start
启动后分别看zookeeper状态:
./zkServer.sh status
一台leader,两台follower,集群启动成功。