SpringBoot集成Zookeeper_springboot与zookeeper集成demo-程序员宅基地

技术标签: ZooKeeper  spring-boot  

Zookeeper 伪集群安装部署操作
Java操作Zookeeper实现分布式锁和队列

分布式事物

  • 分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式事物中会涉及到对多个数据源或业务系统的操作。
  • 典型的分布式事务场景:跨银行转操作就涉及调用两个异地银行服务

CAP理论

  • CAP理论:一个分布式系统不可能同时满足一致性,可用性和分区容错性这个三个基本需求,最多只能同时满足其中两项
  • 一致性:数据在多个副本之间是否能够保持一致的特性。
  • 可用性:是指系统提供的服务必须一致处于可用状态,对于每一个用户的请求总是在有限的时间内返回结果,超过时间就认为系统是不可用的
  • 分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障。

CAP定理的应用

  • 放弃P:如果希望能够避免系统出现分区容错性问题,一种较为简单的做法就是将所有的数据(或者是与事物先相关的数据)都放在一个分布式节点上,这样索然无法保证100%系统不会出错,但至少不会碰到由于网络分区带来的负面影响
  • 放弃A:其做法是一旦系统遇到网络分区或其他故障时,那马受到影响的服务需要等待一定的时间,英雌等待期间系统无法对外提供正常的服务,即不可用
  • 放弃C:这里说的放弃一致性,并不是完全不需要数据一致性,是指放弃数据的强一致性,保留数据的最终一致性。

BASE理论

  • BASE是基本可用,软状态,最终一致性。是对CAP中一致性和可用性权限的结果,是基于CAP定理演化而来的,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特定,采用适当的方式来使系统达到最终一致性

2PC提交

  • 二阶段提交协议是将食物的提交过程分成提交事务请求和执行事务提交两个阶段进行处理。

阶段1:提交事物请求

  • 事务询问:协调者向所有的参与者发送事物内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应
  • 执行事务:各参与者节点执行事物操作,并将Undo和Redo信息记入事务日志中
  • 如果参与者成功执事务操作,就反馈给协调者Yes响应,表示事物可以执行,如果没有成功执行事务,就反馈给协调者No响应,表示事务不可以执行
  • 二阶段提交一些的阶段一夜被称为投票阶段,即各参与者投票票表明是否可以继续执行接下去的事物提交操作

阶段二:执行事物提交

  • 假如协调者从所有的参与者或得反馈都是Yes响应,那马就会执行事务提交。
  • 发送提交请求:协调者向所有参与者节点发出Commit请求
  • 事务提交:参与者接受到Commit请求后,会正式执行事物提交操作,并在完成提交之后放弃整个事务执行期间占用的事务资源
  • 反馈事务提交结果:参与者在完成事物提交之后,向协调者发送ACK消息
  • 完成事务:协调者接收到所有参与者反馈的ACK消息后,完成事物

中断事务

  • 假如任何一个参与者向协调者反馈了No响应,或者在等待超市之后,协调者尚无法接收到所有参与者的反馈响应,那么就中断事物。
  • 发送回滚请求:协调者向搜优参与者节点发出Rollback请求
  • 事物回滚:参与者接收到Rollback请求后,会利用其在阶段一种记录的Undo信息执行事物回滚操作,并在完成回滚之后释放事务执行期间占用的资源。
  • 反馈事务回滚结果:参与则在完成事务回滚之后,向协调者发送ACK消息
  • 中断事务:协调者接收到所有参与者反馈的ACk消息后,完成事务中断、

优缺点

  • 原理简单,实现方便
  • 缺点是同步阻塞,单点问题,脑裂,保守

3PC提交

  • 三阶段提,也叫三阶段提交协议,是二阶段提交(2PC)的改进版本。
  • 与两阶段提交不同的是,三阶段提交有两个改动点。引入超时机制。同时在协调者和参与者中都引入超时机制。在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
  • 阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

zookeeper集群的角色

  • zk中共有三种角色:leader,follower,observer。
  • leader可以为客户端提供读写服务。follower和observer只能提供读服务,并且observer不参与leader选择过程,也不参与’过半写成功‘的策略。

ZAB协议的核心思想

ZAB协议的核心是定义了对于那些会改变Zookeeper服务器数据状态的事务请求的处理方式即:

  • 所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为leader服务器,而余下的其它服务则称为follower服务。leader服务器负责将客户端事务请求转换成一个事务提议,并将提议分发给集群中所有的ollower服务,之后leader服务器需要等待所有follower服务反馈,一旦超过半数的follower服务进行了正确的反馈后,那么leader服务器机会再次向所有的follower服务分发commit消息,要求将前一个的提议进行提交。

ZAB协议两种模式

  • ZAB协议包括两种基本模式:崩溃恢复和原子广播。
  • 当整个服务框架子啊启动过程中,或是当leader服务器出现网络故障,崩溃退出与重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的Leader服务器。
  • 当选择产生了新的leader服务器,同时集群中已经有过半的机器与该leader服务器完成了同步状态之后,ZAB协议就会退出恢复模式。所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。

ZAB协议消息广播流程图

ZAB协议消息广播

  • ZAB协议和二阶段提交有所不同,移除了中断逻辑,所有Follower服务的正常反馈Leader的提议,要吗就丢弃
  • 同时过半Follower服务反馈ACK后就可以开始提交事务的提议Proposal,而不需要等待集群中所有的Follower都反馈ACK
  • 但是这种模式无法解决Leader服务器崩溃退出而带来的数据不一致性问题。
  • 在消息广播的过程中,Leader会为每一个事务Proposal分配一个全局递增唯一的ID。
  • 每一个Follower在接收到这个事务的Proposal之后,会将其以事务日志的形式写入到磁盘中去,并向Leader反馈ACK。
  • 当Leader收到半数节点的ACK时,会广播一个commit消息通知其他节点进行事务提交,同时自己也完成事务提交

ZAB协议奔溃恢复

  • 当leader服务出现故障或过半的follower与leader服务失去联系就会进入崩溃恢复模式
  • Leader选举算法应该保证:已经在Leader上提交的事务最终也被其他节点都提交,即使出现了Leader挂掉,Commit消息没发出去这种情况。
  • 确保丢弃只在Leader上被提出的事务。Leader提出一个事务后挂了,集群中别的节点都没收到,当挂掉的节点恢复后,要确保丢弃那个事务。
  • 让Leader选举算法能够保证新选举出来的Leader拥有最大的事务ID的Proposal。

SpringBoot集成Zookeeper实战

pom.xml引入zk依赖

 <dependency>
     <groupId>org.apache.zookeeper</groupId>
     <artifactId>zookeeper</artifactId>
     <version>3.5.5</version>
 </dependency>

application.yml配置

zookeeper:
  address: 127.0.0.1:2181
  timeout: 4000

ZookeeperConfig配置类


@Configuration
public class ZookeeperConfig {
    
    private static final Logger logger = LoggerFactory.getLogger(ZookeeperConfig.class);

    @Value("${zookeeper.address}")
    private    String connectString;

    @Value("${zookeeper.timeout}")
    private  int timeout;


    @Bean(name = "zkClient")
    public ZooKeeper zkClient(){
    
        ZooKeeper zooKeeper=null;
        try {
    
            final CountDownLatch countDownLatch = new CountDownLatch(1);
            //连接成功后,会回调watcher监听,此连接操作是异步的,执行完new语句后,直接调用后续代码
            //  可指定多台服务地址 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
             zooKeeper = new ZooKeeper(connectString, timeout, new Watcher() {
    
                @Override
                public void process(WatchedEvent event) {
    
                    if(Event.KeeperState.SyncConnected==event.getState()){
    
                        //如果收到了服务端的响应事件,连接成功
                        countDownLatch.countDown();
                    }
                }
            });
            countDownLatch.await();
            logger.info("【初始化ZooKeeper连接状态....】={}",zooKeeper.getState());

        }catch (Exception e){
    
            logger.error("初始化ZooKeeper连接异常....】={}",e);
        }
         return  zooKeeper;
    }


}

zk客户端zpi封装工具类

@Component
public class ZkApi {
    

    private static final Logger logger = LoggerFactory.getLogger(ZkApi.class);

    @Autowired
    private ZooKeeper zkClient;


    /**
     * 判断指定节点是否存在
     * @param path
     * @param needWatch  指定是否复用zookeeper中默认的Watcher
     * @return
     */
    public Stat exists(String path, boolean needWatch){
    
        try {
    
            return zkClient.exists(path,needWatch);
        } catch (Exception e) {
    
            logger.error("【断指定节点是否存在异常】{},{}",path,e);
            return null;
        }
    }

    /**
     *  检测结点是否存在 并设置监听事件
     *      三种监听类型: 创建,删除,更新
     *
     * @param path
     * @param watcher  传入指定的监听类
     * @return
     */
    public Stat exists(String path,Watcher watcher ){
    
        try {
    
            return zkClient.exists(path,watcher);
        } catch (Exception e) {
    
            logger.error("【断指定节点是否存在异常】{},{}",path,e);
            return null;
        }
    }

    /**
     * 创建持久化节点
     * @param path
     * @param data
     */
    public boolean createNode(String path, String data){
    
        try {
    
            zkClient.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            return true;
        } catch (Exception e) {
    
            logger.error("【创建持久化节点异常】{},{},{}",path,data,e);
            return false;
        }
    }


    /**
     * 修改持久化节点
     * @param path
     * @param data
     */
    public boolean updateNode(String path, String data){
    
        try {
    
            //zk的数据版本是从0开始计数的。如果客户端传入的是-1,则表示zk服务器需要基于最新的数据进行更新。如果对zk的数据节点的更新操作没有原子性要求则可以使用-1.
            //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.setData(path,data.getBytes(),-1);
            return true;
        } catch (Exception e) {
    
            logger.error("【修改持久化节点异常】{},{},{}",path,data,e);
            return false;
        }
    }

    /**
     * 删除持久化节点
     * @param path
     */
    public boolean deleteNode(String path){
    
        try {
    
            //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.delete(path,-1);
            return true;
        } catch (Exception e) {
    
            logger.error("【删除持久化节点异常】{},{}",path,e);
            return false;
        }
    }

    /**
      * 获取当前节点的子节点(不包含孙子节点)
      * @param path 父节点path
      */
    public List<String> getChildren(String path) throws KeeperException, InterruptedException{
    
        List<String> list = zkClient.getChildren(path, false);
        return list;
    }

    /**
     * 获取指定节点的值
     * @param path
     * @return
     */
    public  String getData(String path,Watcher watcher){
    
        try {
    
            Stat stat=new Stat();
            byte[] bytes=zkClient.getData(path,watcher,stat);
            return  new String(bytes);
        }catch (Exception e){
    
            e.printStackTrace();
            return  null;
        }
    }


    /**
     * 测试方法  初始化
     */
    @PostConstruct
    public  void init(){
    
        String path="/zk-watcher-2";
        logger.info("【执行初始化测试方法。。。。。。。。。。。。】");
        createNode(path,"测试");
        String value=getData(path,new WatcherApi());
        logger.info("【执行初始化测试方法getData返回值。。。。。。。。。。。。】={}",value);

        // 删除节点出发 监听事件
        deleteNode(path);

    }

}

实现Watcher监听

public class WatcherApi implements Watcher {
    

    private static final Logger logger = LoggerFactory.getLogger(WatcherApi.class);
    @Override
    public void process(WatchedEvent event) {
    
        logger.info("【Watcher监听事件】={}",event.getState());
        logger.info("【监听路径为】={}",event.getPath());
        logger.info("【监听的类型为】={}",event.getType()); //  三种监听类型: 创建,删除,更新
    }
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010391342/article/details/100404588

智能推荐

js sort排序_sort a<b-程序员宅基地

文章浏览阅读103次。/* 排序 >号 从小到大排序 <从大到小排序 */ list.sort(function(a, b) { return a.date < b.date ? 1 : -1; })如果是简单的list就直接 return a < b ? 1 : -1;即可,如果是list里面套的map,可让list按map里面的指定字段进行排揎。..._sort a

前端设置条件限制form表单提交到后端解决方案_jsp前端页面将表单是否提交成功作为限制条件-程序员宅基地

文章浏览阅读375次。<script src="js/jquery-1.8.3.min.js" type="text/javascript"></script> <script type="text/javascript"> function checkName() { var name = document.getElementB..._jsp前端页面将表单是否提交成功作为限制条件

计算机网络sequence number,TCP协议中SequenceNumber和Ack Numbe-程序员宅基地

文章浏览阅读1k次。Sequence Numberlzyws7393074532892018-04-25Number Sequenceqq_391789932452017-09-21理解TCP序列号(Sequence Number)和确认号(Acknowledgment Number)hebbely9822017-01-14Number Sequence(规律)l25336363712902017-07-18Numb..._ack num

计算机系统启动项设置密码,电脑开机第一道密码怎么设置 - 卡饭网-程序员宅基地

文章浏览阅读5.9k次。笔记本电脑怎么进CMOS密码巧设置笔记本电脑怎么进CMOS密码巧设置 笔记本电脑为了保护用户的数据安全,往往采用加密的方式,最常见的还是CMOS密码加密技术。为了让你的重要数据更加安全,你可能需要设置不同的密码,这也就要求你记住许多密码。对于笔记本电脑用户来说,真的需要设置一道道密码关卡吗?非也非也! 一、认识与设置笔记本电脑的CMOS密码 笔记本电脑的CMOS密码大致分为超级密码(Supervi..._电脑第一道密码修改

VulnHub靶机-Jangow: 1.0.1_jangow01-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏5次。迟到的文章,就当库存发出来吧~_jangow01

spark实战之RDD的cache或persist操作不会触发transformation计算_spark cache和persist不生效-程序员宅基地

文章浏览阅读1.7w次,点赞2次,收藏5次。默认情况下RDD的transformation是lazy形式,实际计算只有在ation时才会进行,而且rdd的计算结果默认都是临时的,用过即丢弃,每个action都会触发整个DAG的从头开始计算,因此在迭代计算时都会想到用cache或persist进结果进行缓存。敝人看到很多资料或书籍有的说是persist或cache会触发transformation真正执行计算,有的说是不会!敝人亲自实验了一把..._spark cache和persist不生效

随便推点

用mac的chrome浏览器调试Android手机的网页-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏2次。一、参考链接read chrome remote debugging documentation调出开发者选项二、设置android在安卓4.2及更新的版本中,默认情况下,【开发者选项】是隐藏的。要启用【开发者选项】,设置 -> 关于手机 -> 版本号,对着版本号点击7次。设置 -> 开发者选项 -> USB调试三、连接手机和电脑..._小米13链接mac chrome inspect

树莓派GPIO简单操作_树莓派怎么读取gpio口上的信息-程序员宅基地

文章浏览阅读637次。树莓派的GPIO操作被抽象为文件读写,下面以一个例子来说明GPIO操作。_树莓派怎么读取gpio口上的信息

【汽车电子】浅谈车载系统QNX_车机qnx虚拟化软件系统架构-程序员宅基地

文章浏览阅读1.7k次。QNX是一种商用的遵从POSIX规范的类Unix实时操作系统,目标市场主要是面向嵌入式系统。它可能是最成功的微内核操作系统之一。QNX是一种商用的类Unix实时操作系统,遵从POSⅨ规范,目标市场主要是嵌入式系统[1]。QNX成立于1980年,是加拿大一家知名的嵌入式系统开发商。QNX的应用范围极广,包含了:控制保时捷跑车的音乐和媒体功能、核电站和美国陆军无人驾驶Crusher坦克的控制系统[2],还有RIM公司的BlackBerry PlayBook平板电脑。_车机qnx虚拟化软件系统架构

信号发生器设计VHDL代码Quartus仿真_vhdl正弦波信号发生器-程序员宅基地

文章浏览阅读1k次,点赞20次,收藏22次。代码功能:信号发生器设计信号发生器由波形选择开关控制波形的输出,分别能输出正弦波、方汉和三角波三种波形,波形的周期为2秒(由40M有源晶振分频控制)。考虑程序的容量,每种波形在一个周期内均取16个取样点,每个样点数据是8位(数值范围:00000000~1111111)要求将D/A变换前的8位二进数据(以十进制方式)输出到数码管动态演示出来。_vhdl正弦波信号发生器

笔记-Java线程概述_java 线程概述-程序员宅基地

文章浏览阅读629次。Java Concurrency in Practice中对线程安全的定义:当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的 。过多的同步会产生死锁的问题,死锁属于程序运行的时_java 线程概述

MATLAB从文件读取数据_matlab读取数据-程序员宅基地

文章浏览阅读1.2w次,点赞10次,收藏61次。读取表单Sheet2中部分信息。_matlab读取数据

推荐文章

热门文章

相关标签