(自用)Zookeeper复习-2021-10-20

一. 安装:

(1) docker-compose.yml 
version: "3.1"
services:
  zk:
   image: daocloud.io/daocloud/zookeeper:latest
   restart: always
   container_name: zk
   ports:
     - 2181:2181

(2) 进入zk容器内部,进入bin目录执行下面的zk客户端命令
    docker ps -> docker exec -it 容器id bash
    ls -> cd bin/ -> ls -> ./zkCli.sh

二.  架构: 节点Znode -> 存数据 -> 节点不能重复

三. Znode类型:

1.节点类型:
- 持久节点:永久的保存在你的Zookeeper
- 持久有序节点:永久的保存在你的Zookeeper,他会给节点添加一个有序的序号, /xx -> /xx0000001
- 临时节点:当存储的客户端和Zookeeper服务断开连接时,这个临时节点会自动删除
- 临时有序节点:当存储的客户端和Zookeeper服务断开连接时,这个临时节点会自动删除,他会给节点添加一个有序的序号,/xx -> /xx0000001

2. 监听通知机制     

四. 监听机制: 

public class Test03 {
    CuratorFramework cf = ZkUtil.cf();
    @Test
    public void listen() throws Exception {
        //1. 创建NodeCache对象,指定要监听的znode
        final NodeCache nodeCache = new NodeCache(cf,"/qf");
        nodeCache.start();
        //2. 添加一个监听器
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                byte[] data = nodeCache.getCurrentData().getData();
                Stat stat = nodeCache.getCurrentData().getStat();
                String path = nodeCache.getCurrentData().getPath();
                System.out.println("监听的节点是:" + path);
                System.out.println("节点现在的数据是:" + new String(data,"UTF-8"));
                System.out.println("节点状态是:" + stat);
            }
        });
        System.out.println("开始监听!!");
        //3. System.in.read();//让监听不退出
        System.in.read();
    }
}

五. zkCli.sh常用命令:

# 查询当前节点下的全部子节点
ls 节点名称
# 例子 ls /

# 查询当前节点下的数据
get 节点名称
# 例子 get /zookeeper

# 创建节点
create [-s] [-e] znode名称 znode数据
# -s:sequence,有序节点
# -e:ephemeral,临时节点

# 修改节点值
set znode名称 新数据

# 删除节点
delete znode名称    # 删除没有子节点的znode
rmr znode名称      # 删除当前节点和全部的子节点(如果rmr不行就用deleteall)

六. 集群:

1. 主从之分
2. 主节点: master -> 执行读写
3. 从节点: slave -> 执行读
4. 角色:
    (1). Leader: 主节点
    (2). Follower: 选举新的leader
    (3). Observer: 不参与投票
    (4). Looking: 正在寻找leader节点
5. 投票策略:
    (1) Zxix 数字越大, 数据越新
    (2) myid: 每个zookeeper服务分配一个全局Uid
    (3) zookeeper写数据时, 每个节点的FIFO队列, 顺序不会乱, 数据越新zxid越大
    (4) zxid相同节点中, 选择myid最大的节点

        5. 集群搭建-Linux服务器:

(1) yml文件
version: "3.1"
services:
  zk1:
    image: zookeeper
    restart: always
    container_name: zk1
    ports:
      - 2181:2181
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=zk1:2888:3888;2181

(2) # exit退出容器↓
    root@b5e3889b3e91:/opt/zookeeper/bin# exit

(3) # 避免端口冲突,停止并删除单机版zk然后修改yml文件为集群配置,然后重启容器
    docker-compose down
    docker-compose up -d

(4) # 分别进入各自容器内部的bin目录,执行./zkServer.sh status查看状态信息,看看谁是leader
    ./zkServer.sh status

七. Java操作Zookeeper:

(1) 导入依赖: 
<dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.6.0</version>
    </dependency>
    //CuratorFrameworkFactory工具类的依赖
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.0.1</version>
    </dependency>

(2) 连接Zookeeper集群工具类
public class ZkUtil {
    public static CuratorFramework cf(){
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);//需重试策略,每隔3秒,重试2次        
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                .connectString("10.20.159.25:2181,10.20.159.25:2182,10.20.159.25:2183")
                .retryPolicy(retryPolicy)
                .build();

        cf.start();
        return cf;
    }
}

(3) 调用工具类,运行单元测试,如果没有报错,说明连接zk成功
public class Test01 {
    @Test
    public void testConnectToZk() {
        CuratorFramework cf = ZkUtil.cf();
        System.out.println(cf);
    }
}

2. Java操作节点:

    @Test
    public void create0() throws Exception {
        cf.create().withMode(CreateMode.PERSISTENT).forPath("/qf","qfsj".getBytes());
    }
    //获取数据,指定路径,哪个节点的↓
    @Test
    public void getData() throws Exception {
        byte[] bytes = cf.getData().forPath("/qf");
        System.out.println(new String(bytes,"UTF-8"));//qfsj
    }
    //创建节点并伴随创建节点的类型,指定路径包装节点和数据↓
    @Test
    public void create() throws Exception {
        cf.create().withMode(CreateMode.PERSISTENT).forPath("/qf2","uuuu".getBytes());
    }
    //设置数据,指定路径,哪个节点,和修改数据↓
    @Test
    public void update() throws Exception {
        cf.setData().forPath("/qf2","oooo".getBytes());
    }
    //删除节点,如果有子孩子也随便删除,指定路径,哪个节点↓
    @Test
    public void delete() throws Exception {
        cf.delete().deletingChildrenIfNeeded().forPath("/qf2");
    }
    //检查存在节点状态,指定路径,哪个节点↓
    @Test
    public void stat() throws Exception {
        Stat stat = cf.checkExists().forPath("/qf");
        System.out.println(stat);
    }
}

八. 过半数存活原则:

1. 存活机器数量超过总数量一半时, 集群才能正常工作, 否则拒绝

2. 2N+1: 奇数台

3. 作用: 解决脑裂问题-> 集群监听不到leader心跳-> 集群判断leader出错-> 集群分裂-> 各自选举各自leader节点-> 集群中出现多个leader-> 无法保证数据一致性;

九. 应用场景: 

1. 配置文件管理-> 配置中心-> 保存到配置到目录节点-> 应用监听节点-> 配置发生变化->Zookeeper通知应用-> 程序更新配置信息

2. 集群管理: 所有机器约定在父目录GroupMembers创建目录节点-> 监听父目录子节点-> 机器挂掉-> 机器与zookeeper连接断开-> 机器的临时目录节点被删除-> 其他机器收到通知

3. 分布式锁: zookeeper的节点作为锁-> 所有客户端创建/distribute-lock节点-> 节点创建成功的客户端获得锁-> 用户删除创建的节点释放锁

(1) 分布式锁: 临时有序节点-> 客户端在指定目录下创建临时有序节点-> 序号最小的客户端获得锁-> 如当前节点序号不是最小的-> 监听比自己小一号的节点-> 监听的节点被删除后-> 当前节点再次判断自己是否是最小节点-> 获取锁资源

(2) 并发工具测试: ab压力测试工具

(3) 依赖: zookeeper / curator-framework

(4) ZKUtil.java配置类
@Configuration
public class ZkConfig {
    @Bean
    public CuratorFramework cf(){
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);//需要指定重试间隔和重试次数
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()              .connectString("10.20.159.25:2181,10.20.159.25:2182,10.20.159.25:2183")
                .retryPolicy(retryPolicy)
                .build();
        curatorFramework.start();
        return curatorFramework;
    }
}

(3) 业务代码加锁:
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//加锁
lock.acquire();
lock.acquire(1,TimeUnit.SECONDS); //指定排队多久放弃获取锁资源
//----------------业务逻辑代码------------------------
//释放锁
lock.release();

(4) 原理图解:
    请求一个个来-> Znode临时有序节点-> 最小序号的节点来获取锁

4. 命名服务(Dubbo监控中心): 服务A访问服务B-> B服务未完成-> 服务B部署到Zookeeper-> 添加服务B的节点和数据-> 服务A监控zookeeper的B节点-> 发现服务B有数据后访问B服务

5. 发现服务: 注册持久节点Service/Business-> 每个子节点(临时挂掉自动移除)都是可用服务-> 保存服务的端口-> 其他服务调用zookeeper的节点信息

十. 拓展:使用Redis实现分布式锁 -> 比较轻量级(zk重量级锁, 创建很多节点)

1. 原理: 多线程请求一个个来-> 存储键值对到redis-> 键作为标记-> 键不存在则获取锁-> 存在则不获取-> Redis单线程setnxex-> 处理完删除键释放锁

2. 死锁: 设置键的过期时间 expire(zookeeper自动断开)

3. 代码实现:

(1) 依赖: spring-boot-starter-data-redis
(2) yml配置: spring: redis: host: localhost/ port: 6379
(3) RedisLockUtil.java工具类
@Component
public class RedisLockUtil {
    @Autowired
    private StringRedisTemplate redisTemplate;
    public boolean lock(String key,String value,int second){
        return redisTemplate.opsForValue().setIfAbsent(key,value,second, TimeUnit.SECONDS);
    }
    public void unlock(String key){
        redisTemplate.delete(key);
    }
}

(4) 加锁-> 业务逻辑放入固定代码
@Autowired
private RedisLockUtil lock;
@GetMapping("/redis/kill")
public String redisKill(String item) throws Exception {
    //加锁
    if(lock.lock(item,System.currentTimeMillis() + "",1)){
        //业务代码...
        //释放锁
        lock.unlock(item);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值