Zookeeper断网重连事件回调源码分析

“不积跬步,无以至千里。”

背景

  • 确定使用Curator作为zk客户端的情况下,断网[发生SUSPENDED | LOST事件]重连后每次都会回调org.apache.curator.framework.state.ConnectionStateListener#stateChanged方法,且事件类型为org.apache.curator.framework.state.ConnectionState#RECONNECTED
  • 部署zookeeper的版本为最新稳定版3.8.3,curator-recipes相关依赖的版本为5.5.0

源码分析过程

  • 首先需要构建一个CuratorFramework对象,并基于这个CuratorFramework对象创建一个用于实现Leader选举功能的LeaderSelector,并将它启动

    public static final String leaderSelectorPath = "/source-code-analyse/reconnect";
    public static void main(String[] args) throws InterruptedException {CuratorFramework curatorFramework = newCuratorFramework();LeaderSelectorListener leaderSelectorListener = new LeaderSelectorListener() {@Overridepublic void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {System.out.println("Thread " + Thread.currentThread().getName() + " Connection state changed : " + connectionState);}@Overridepublic void takeLeadership(CuratorFramework curatorFramework) throws Exception {System.out.println("Thread " + Thread.currentThread().getName() + " get the leader.");TimeUnit.SECONDS.sleep(20);}};LeaderSelector leaderSelector = new LeaderSelector(curatorFramework, leaderSelectorPath, leaderSelectorListener);leaderSelector.start();TimeUnit.SECONDS.sleep(100);leaderSelector.close();curatorFramework.close();System.out.println("Test completed.");}
    
      public static CuratorFramework newCuratorFramework() {CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.0.104:2181").sessionTimeoutMs(30000).connectionTimeoutMs(6000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).threadFactory(ThreadUtils.newThreadFactory("ReconnectionTestThread")).build();curatorFramework.start();return curatorFramework;}
    
  • 由于LeaderSelector的功能实现需要基于CuratorFramework,于是应该先看看CuratorFramework的start方法,直接看实现类CuratorFrameworkImpl

    @Override
    public void start()
    {log.info("Starting");if ( !state.compareAndSet(CuratorFrameworkState.LATENT, CuratorFrameworkState.STARTED) ){throw new IllegalStateException("Cannot be started more than once");}try{connectionStateManager.start();//省略代码
    
  • 发现CuratorFrameworkImpl内部维护了一个与连接状态管理器,start方法中会启动它

  • ConnectionStateManager的start方法中,会向线程池提交一个任务,去调用processEvents方法

    public void start()
    {Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTED), "Cannot be started more than once");service.submit(new Callable<Object>(){@Overridepublic Object call() throws Exception{processEvents();return null;}});
    }
    
  • processEvents方法里面核心的内容就是,从eventQueue的一个阻塞队列中不断调用poll方法获取ConnectionState对象,因为处在一个while循环中,只要当前连接状态正常,就会一直去poll

    private void processEvents(){while ( state.get() == State.STARTED ){try{int useSessionTimeoutMs = getUseSessionTimeoutMs();long elapsedMs = startOfSuspendedEpoch == 0 ? useSessionTimeoutMs / 2 : System.currentTimeMillis() - startOfSuspendedEpoch;long pollMaxMs = useSessionTimeoutMs - elapsedMs;final ConnectionState newState = eventQueue.poll(pollMaxMs, TimeUnit.MILLISECONDS);if ( newState != null ){if ( listeners.isEmpty() ){log.warn("There are no ConnectionStateListeners registered.");}listeners.forEach(listener -> listener.stateChanged(client, newState));}else if ( sessionExpirationPercent > 0 ){synchronized(this){checkSessionExpiration();}}synchronized(this){if ( (currentConnectionState == ConnectionState.LOST) && client.getZookeeperClient().isConnected() ){// CURATOR-525 - there is a race whereby LOST is sometimes set after the connection has been repaired// this "hack" fixes it by forcing the state to RECONNECTEDlog.warn("ConnectionState is LOST but isConnected() is true. Forcing RECONNECTED.");addStateChange(ConnectionState.RECONNECTED);}}}catch ( InterruptedException e ){// swallow the interrupt as it's only possible from either a background// operation and, thus, doesn't apply to this loop or the instance// is being closed in which case the while test will get it}}}
    
  • 随后遍历所有的ConnectionStateListener,回调stateChanged方法,LeaderSelector有一个静态内部类叫做WrappedListener实现了LeaderSelectorListener,则这个WrappedListener的stateChanged方法会被回调

    @Override
    public void stateChanged(CuratorFramework client, ConnectionState newState){try{listener.stateChanged(client, newState);}catch ( CancelLeadershipException dummy ){// If we cancel only leadership but not whole election, then we could hand over// dated leadership to client with no further cancellation. Dated leadership is// possible due to separated steps in leadership acquire: server data(e.g. election sequence)// change and client flag(e.g. hasLeadership) set.leaderSelector.cancelElection();}}
    
  • 而上面的listener.stateChanged(client, newState)中listener变量就是构造LeaderSelector时传入的第三个构造参数:LeaderSelectorListener,就是我们自己实现的LeaderSelectorListener

    所以最终会回调到我们自定义的LeaderSelectorListener#stateChanged()方法

  • 那么现在需要搞清楚ConnectionStateManager中的eventQueue是在哪里被放进去的

  • 追溯一下方法调用,发现eventQueue中的元素,是在ConnectionStateManager#postState方法中offer进去的

    private void postState(ConnectionState state)
    {log.info("State change: " + state);notifyAll();//如果队列满了,offer失败,会先poll,之后继续offerwhile ( !eventQueue.offer(state) ){eventQueue.poll();log.warn("ConnectionStateManager queue full - dropping events to make room");}
    }
    
  • 继续追溯来到org.apache.curator.framework.state.ConnectionStateManager#addStateChange方法

    public synchronized boolean addStateChange(ConnectionState newConnectionState)
    {//如果client不是启动状态直接返回falseif ( state.get() != State.STARTED ){return false;}ConnectionState previousState = currentConnectionState;//如果新的连接状态和前一个一样,说明连接状态没有发生变化,不产生事件,直接返回了if ( previousState == newConnectionState ){return false;}currentConnectionState = newConnectionState;ConnectionState localState = newConnectionState;boolean isNegativeMessage = ((newConnectionState == ConnectionState.LOST) || (newConnectionState == ConnectionState.SUSPENDED) || (newConnectionState == ConnectionState.READ_ONLY));//如果是第一次连接,设置状态为CONNECTEDif ( !isNegativeMessage && initialConnectMessageSent.compareAndSet(false, true) ){localState = ConnectionState.CONNECTED;}postState(localState);return true;
    }
    
  • 继续看addStateChange方法被org.apache.curator.framework.imps.CuratorFrameworkImpl#validateConnection调用

    void validateConnection(Watcher.Event.KeeperState state)
    {//state为Disconnected的时候产生SUSPENDED事件if ( state == Watcher.Event.KeeperState.Disconnected ){suspendConnection();}//state为Expired的时候产生LOST事件else if ( state == Watcher.Event.KeeperState.Expired ){connectionStateManager.addStateChange(ConnectionState.LOST);}//state为SyncConnected的时候产生RECONNECTED事件else if ( state == Watcher.Event.KeeperState.SyncConnected ){connectionStateManager.addStateChange(ConnectionState.RECONNECTED);}//state为ConnectedReadOnly的时候产生READ_ONLY事件else if ( state == Watcher.Event.KeeperState.ConnectedReadOnly ){connectionStateManager.addStateChange(ConnectionState.READ_ONLY);}
    }
    
  • 继续追溯validateConnection()的调用方是org.apache.curator.framework.imps.CuratorFrameworkImpl#processEvent

    private void processEvent(final CuratorEvent curatorEvent)
    {//只有事件类型是WATCHED时候,会调用这个validateConnection方法,连接状态的变更事件就是WARCHEDif ( curatorEvent.getType() == CuratorEventType.WATCHED ){validateConnection(curatorEvent.getWatchedEvent().getState());}//省略代码
    }
    
  • 这个processEvent方法在连接状态发生变化时,会被CuratorFrameworkImplCuratorZookeeperClient传入的一个匿名内部类Watcher给调用

    public CuratorFrameworkImpl(CuratorFrameworkFactory.Builder builder)
    {//这个ZookeeperFactory就是Curator创建Zookeeper的一个工厂ZookeeperFactory localZookeeperFactory = makeZookeeperFactory(builder.getZookeeperFactory(), builder.getZkClientConfig());this.client = new CuratorZookeeperClient(localZookeeperFactory,builder.getEnsembleProvider(),builder.getSessionTimeoutMs(),builder.getConnectionTimeoutMs(),builder.getWaitForShutdownTimeoutMs(),new Watcher(){@Overridepublic void process(WatchedEvent watchedEvent){CuratorEvent event = new CuratorEventImpl(CuratorFrameworkImpl.this, CuratorEventType.WATCHED, watchedEvent.getState().getIntValue(), unfixForNamespace(watchedEvent.getPath()), null, null, null, null, null, watchedEvent, null, null);processEvent(event);}},builder.getRetryPolicy(),builder.canBeReadOnly());//省略代码
    }
    
  • 并且在CuratorZookeeperClient构造函数中,创建了一个ConnectionState对象,用来管理客户端与zk的连接事件,同时把刚才的Watcher作为构造参数传给了ConnectionState,放到一个parentWatchers的队列中

      public CuratorZookeeperClient(ZookeeperFactory zookeeperFactory, EnsembleProvider ensembleProvider,int sessionTimeoutMs, int connectionTimeoutMs, int waitForShutdownTimeoutMs, Watcher watcher,RetryPolicy retryPolicy, boolean canBeReadOnly){if ( sessionTimeoutMs < connectionTimeoutMs ){log.warn(String.format("session timeout [%d] is less than connection timeout [%d]", sessionTimeoutMs, connectionTimeoutMs));}retryPolicy = Preconditions.checkNotNull(retryPolicy, "retryPolicy cannot be null");ensembleProvider = Preconditions.checkNotNull(ensembleProvider, "ensembleProvider cannot be null");this.connectionTimeoutMs = connectionTimeoutMs;this.waitForShutdownTimeoutMs = waitForShutdownTimeoutMs;//创建了一个ConnectionState对象,管理客户端与zk的连接状态state = new ConnectionState(zookeeperFactory, ensembleProvider, sessionTimeoutMs, watcher, tracer, canBeReadOnly);setRetryPolicy(retryPolicy);}
    
     ConnectionState(ZookeeperFactory zookeeperFactory, EnsembleProvider ensembleProvider, int sessionTimeoutMs, Watcher parentWatcher, AtomicReference<TracerDriver> tracer, boolean canBeReadOnly){this.ensembleProvider = ensembleProvider;this.tracer = tracer;if ( parentWatcher != null ){//把匿名内部类的Watcher对象传进来,放到parentWatchers中parentWatchers.offer(parentWatcher);}handleHolder = new HandleHolder(zookeeperFactory, this, ensembleProvider, sessionTimeoutMs, canBeReadOnly);}
    
  • 然后在ConnectionState对象中看看哪些地方使用了这个parentWatchers对象,发现是一个process()方法

     @Overridepublic void process(WatchedEvent event){if ( LOG_EVENTS ){log.debug("ConnectState watcher: " + event);}if ( event.getType() == Watcher.Event.EventType.None ){boolean wasConnected = isConnected.get();boolean newIsConnected = checkState(event.getState(), wasConnected);if ( newIsConnected != wasConnected ){isConnected.set(newIsConnected);connectionStartMs = System.currentTimeMillis();if ( newIsConnected ){lastNegotiatedSessionTimeoutMs.set(handleHolder.getNegotiatedSessionTimeoutMs());log.debug("Negotiated session timeout: " + lastNegotiatedSessionTimeoutMs.get());}}}for ( Watcher parentWatcher : parentWatchers ){OperationTrace trace = new OperationTrace("connection-state-parent-process", tracer.get(), getSessionId());//遍历Watcher,调用process方法,目前已知是在CuratorFrameworkImpl构造器中new的一个匿名Watcher,会回到我们自定义的ConnectionStateListenerparentWatcher.process(event);trace.commit();}}
    
  • 那么ConnectionState#process方法又是在哪里被调用的呢?这个找的有点深了,最终经过断点发现是在org.apache.zookeeper.ClientCnxn.EventThread#processEvent中被调用

    private void processEvent(Object event) {try {if (event instanceof WatcherSetEventPair) {// each watcher will process the eventWatcherSetEventPair pair = (WatcherSetEventPair) event;for (Watcher watcher : pair.watchers) {try {watcher.process(pair.event);} catch (Throwable t) {LOG.error("Error while calling watcher ", t);}}//省略代码
    
  • 这个ClientCnxn已经不是Curator的源码了,属于Zookeeper原生API,是最底层用来管理客户端和zookeeper连接的一个组件,在new Zookeeper的时候被初始化,这个Zookeeper之前提了一下,会被Curator框架封装在ConnectionState中

    ConnectionState(ZookeeperFactory zookeeperFactory, EnsembleProvider ensembleProvider, int sessionTimeoutMs, Watcher parentWatcher, AtomicReference<TracerDriver> tracer, boolean canBeReadOnly)
    {this.ensembleProvider = ensembleProvider;this.tracer = tracer;if ( parentWatcher != null ){parentWatchers.offer(parentWatcher);}//这个zookeeperFactory里面封装了获取Zookepper的方法handleHolder = new HandleHolder(zookeeperFactory, this, ensembleProvider, sessionTimeoutMs, canBeReadOnly);
    }
    
  • org.apache.zookeeper.ClientCnxn.EventThread#processEvent方法又是在org.apache.zookeeper.ClientCnxn.EventThread#run中调用,因为EventThread这个内部类继承了Thread类,所以在创建Zookeeper的时候就调用start()将线程启动了,同时启动的还有SendThread

    public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,boolean canBeReadOnly)throws IOException//省略代码... ...cnxn = createConnection(connectStringParser.getChrootPath(),hostProvider,sessionTimeout,this.clientConfig,watcher,getClientCnxnSocket(),canBeReadOnly);cnxn.start();
    }
    
    public void start() {sendThread.start();eventThread.start();
    }
    
  • 跟踪EventThread源码,可以看到,这个线程的run方法中也是采用while循环的方式不断从一个叫做waitingEvents的阻塞队列中take事件

    private final LinkedBlockingQueue<Object> waitingEvents =new LinkedBlockingQueue<Object>();
    @Override
    @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER")
    public void run() {try {isRunning = true;while (true) {Object event = waitingEvents.take();//如果不是一个new Object对象,交给processEvent方法处理if (event == eventOfDeath) {wasKilled = true;} else {processEvent(event);}//省略无关代码... ...
    }
    
  • 那么重点就是这个waitingEvents的元素是在哪里add的?

  • 在ClientCnxn中拿这个变量搜索一下,发现有两个地方会add,一个是queueEvent方法,一个是queuePacket方法,显然根据名字来看,第二个应该是添加和ZK进行交互的具体数据的(而后通过打断点的方式也确实验证了这一点),而queueEvent()才是用来添加事件数据的

    public void queueEvent(WatchedEvent event) {queueEvent(event, null);}private void queueEvent(WatchedEvent event, Set<Watcher> materializedWatchers) {if (event.getType() == EventType.None && sessionState == event.getState()) {return;}sessionState = event.getState();final Set<Watcher> watchers;if (materializedWatchers == null) {// materialize the watchers based on the eventwatchers = watchManager.materialize(event.getState(), event.getType(), event.getPath());} else {watchers = new HashSet<>(materializedWatchers);}WatcherSetEventPair pair = new WatcherSetEventPair(watchers, event);// queue the pair (watch set & event) for later processingwaitingEvents.add(pair);
    }
    
  • 也就是说,当客户端和ZK server连接状态变更时(如重连)一定会在某个地方调用这个queueEvent方法,把变更状态放到阻塞队列中,等待消费

  • 这块代码比较复杂,有兴趣可以自主阅读org.apache.zookeeper.ClientCnxn.SendThread源码

  • 简单的说,这块的处理流程是这样的:Zookeerper被创建的时候,会创建ClientCnxn,启动两个线程,一个是eventThread,另一个就是sendThread

  • 这个SendThread主要作用就是用来跟zk通信的,而且还会搞一个心跳机制,定期去和zk ping一下,确定连接是正常的

  • 在SendThread的run方法里有一个while循环,会检查如果你是断网状态,会不停的通过ClientCnxnSocket重新建立连接,连不上会重复进行此步骤

    //如果不是连接状态,会一直尝试建立连接,有兴趣的可以去startConnect方法看看,如果失败,会被外层的Catch块捕获,然后继续来到while循环,重新尝试建立连接
    if (!clientCnxnSocket.isConnected()) {// don't re-establish connection if we are closingif (closing) {break;}if (rwServerAddress != null) {serverAddress = rwServerAddress;rwServerAddress = null;} else {serverAddress = hostProvider.next(1000);}onConnecting(serverAddress);//这个方法中,最后会通过clientCnxnSocket组件连接zk,clientCnxnSocket.connect(addr);startConnect(serverAddress);// Update now to start the connection timer right after we make a connection attemptclientCnxnSocket.updateNow();clientCnxnSocket.updateLastSendAndHeard();
    }
    
  • 一旦重新重新建立,会在org.apache.zookeeper.ClientCnxn.SendThread#run方法中调用clientCnxnSocket.doTransport,开始和zk收发数据包

    //pengingQueue是已经发送并正在等待响应的数据包
    clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this);
    
  • doTransport方法里面是NIO的代码,有兴趣可以自己研究下

  • 最终会在org.apache.zookeeper.ClientCnxnSocket#readConnectResult读取zk响应的数据包,调用org.apache.zookeeper.ClientCnxn.SendThread#onConnected方法,将数据放入waitingEvents阻塞队列中

    void readConnectResult() throws IOException {//省略无关代码sendThread.onConnected(conRsp.getTimeOut(), this.sessionId, conRsp.getPasswd(), isRO);
    }
    

    因为我们和ZK建立的不是一个只读连接,所以事件类型会是SyncConnected

    void onConnected(int _negotiatedSessionTimeout,long _sessionId,byte[] _sessionPasswd,boolean isRO) throws IOException {//省略无关代码KeeperState eventState = (isRO) ? KeeperState.ConnectedReadOnly : KeeperState.SyncConnected;eventThread.queueEvent(new WatchedEvent(Watcher.Event.EventType.None, eventState, null));
    }
    

    前面已经看到代码,在validate的时候,如果KeeperState是KeeperState.SyncConnected,会触发RECONNECTED事件,最终回调到我们自定义的ConnectionStateListener#stateChanged方法中

  • 有兴趣的可以根据我的思路进行断点调试验证,不过有一些事异步的,注意打断点的时机

验证结果

  • 使用CuratorFramework作为zookeeper客户端连接工具时,当发生断网重连时在自定义的ConnectionStateListener的stateChanged方法中确定会产生RECONNECTED事件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106321.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

苍穹外卖(八) 使用WebSocket协议完成来单提醒及客户催单功能

WebSocket介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信(双向传输)——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c; 并进行双向数据传输。 HTTP协议和WebSocket协议对比&#xff1a; HTTP…

ChatGPT角色扮演教程,Prompt词分享

使用指南 1、可直复制使用 2、可以前往已经添加好Prompt预设的AI系统测试使用 https://ai.idcyli.comhttps://ai.idcyli.com 雅思写作考官 我希望你假定自己是雅思写作考官&#xff0c;根据雅思评判标准&#xff0c;按我给你的雅思考题和对应答案给我评分&#xff0c;并且按…

LeetCode每日一题:1488. 避免洪水泛滥(2023.10.13 C++)

目录 1488. 避免洪水泛滥 实现代码与解析&#xff1a; 贪心 原理思路&#xff1a; 1488. 避免洪水泛滥 题目描述&#xff1a; 你的国家有无数个湖泊&#xff0c;所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的&#xff0c;那么它就会装满水。如果第 n 个湖泊下雨前是…

使用 Secrets OPerationS 管理 Kubernetes 密钥

Kubernetes非常受欢迎&#xff0c;很大程度上要归功于它的灵活性。由于其模块化&#xff0c;它还可以快速部署。然而&#xff0c;为了保持这种模块化&#xff0c;您需要以流畅且可定制的方式构建云环境&#xff1b;这意味着确保 ConfigMap 和 Secret 的设计与基础设施无关。 您…

DirectX绘制流水线

使用DirectX可以让在Windows平台上运行的游戏或多媒体程序获得更高的执行效率&#xff0c;掌握DirectX的基本概念和技术是虚拟现实技术、计算机仿真和3D游戏程序开发的基础。 DirectX概述 DirectX是微软的一个多媒体应用编程接口(API)工具包&#xff0c;用于为Windows操作系统…

数组——螺旋矩阵II

文章目录 一、题目二、题解 题目顺序&#xff1a;代码随想录算法公开课&#xff0c;b站上有相应视频讲解 一、题目 59. Spiral Matrix II Given a positive integer n, generate an n x n matrix filled with elements from 1 to n2 in spiral order. Example 1: Input: n …

十五届蓝桥杯软件和信息技术大赛

目录 &#x1f388;大赛奖项 &#xff08;一&#xff09;高校类 1. 软件赛 2. 电子赛 3. 视觉艺术设计赛 4. 数字科技创新赛 &#x1f388;奖项设置 &#x1f388;时间安排&#xff08;预计&#xff09; &#x1f388;规则简介 &#x1f388;如何准备&#xff1f; &a…

EfficientDet: Scalable and Efficient Object Detection

CVPR2020 V7 Mon, 27 Jul 2020 引用量&#xff1a;243 机构&#xff1a;Google 贡献&#xff1a;1>提出了多尺度融合网络BiFPN 2>对backbone、feature network、box/class prediction network and resolution进行复合放缩&#xff0c;有着不同的…

在 VSCode 中使用 PlantUML

最近&#xff0c;因为工作需要绘制一些逻辑图&#xff0c;我自己现在使用的是 PlantUML 或者 mermaid&#xff0c;相比之下前者更加强大。不过它的环境也麻烦一些&#xff0c;mermaid 在一些软件上已经内置了。但是 PlantUML 一般需要自己本地安装或者使用远程服务器&#xff0…

Unity中Shader不同灯光类型的支持与区分

文章目录 前言一、在开始之前做一些准备1、在上一篇文章的场景基础上&#xff0c;增加一个Unity默认的球体作为对照组2、创建一个点光源&#xff0c;用来看点光源的影响 对 Unity默认的Shader效果 和 我们实现的Shader效果 之间的不同 二、点光源的适配把上一篇文章中 ForwardB…

R语言的计量经济学实践技术应用

计量经济学通常使用较小样本&#xff0c;但这种区别日渐模糊&#xff0c;机器学习在经济学领域、特别是经济学与其它学科的交叉领域表现日益突出&#xff0c;R语言是用于统计建模的主流计算机语言&#xff0c;在本次培训中&#xff0c;我们将从实际应用出发&#xff0c;重点从数…

【arm实验2】按键中断事件控制实验

设置按键中断&#xff0c;按键1按下&#xff0c;LED亮&#xff0c;再次按下&#xff0c;灭 按键2按下&#xff0c;蜂鸣器叫&#xff0c;再次按下&#xff0c;停 按键3按下&#xff0c;风扇转&#xff0c;再次按下&#xff0c;停 主函数&#xff1a; linuxlinux:~/study/08-c$…

指定显卡运行python脚本

指定显卡运行python脚本 指定显卡运行python脚本 指定显卡运行python脚本 CUDA_VISIBLE_DEVICES0 python infer.py

electron打包后主进程下载文件崩溃

electronvue3写了一个小项目&#xff0c;实现了一个文件下载功能 存在的问题 打包后&#xff0c;应用下载文件崩溃代码 // 渲染进程window.electron.ipcRenderer.invoke(save-file, {path: r.filePath,fileurl: previewUrl,}).then(response > {console.log(response ----…

八皇后问题的解析与实现

问题描述 八皇后问题是一个古老而又著名的问题。 时间退回到1848年,国际西洋棋棋手马克斯贝瑟尔提出了这样的一个问题: 在88格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问一共有多少种摆法。 如何找到这所有的…

【基础篇】四、本地部署Flink

文章目录 1、本地独立部署会话模式的Flink2、本地独立部署会话模式的Flink集群3、向Flink集群提交作业4、Standalone方式部署单作业模式5、Standalone方式部署应用模式的Flink Flink的常见三种部署方式&#xff1a; 独立部署&#xff08;Standalone部署&#xff09;基于K8S部署…

使用Tortoisegit界面拉起master主分支以副分支以及命令行操作

文章目录 1、Gui操作界面2、命令行操作3、合并分支到master分支上面 1、Gui操作界面 "小乌龟"通常指的是Git的图形用户界面&#xff08;GUI&#xff09;工具&#xff0c;其中比较常见的是TortoiseGit。下面是使用TortoiseGit来拉取&#xff08;checkout&#xff09;一…

数据结构(一)—— 数据结构简介

文章目录 一、基本概念和术语&#xff1f;1.1、数据1.2、数据元素1.3、数据项&#xff08;属性、字段&#xff09;1.4、数据对象1.5、数据结构 二、逻辑结构和物理结构&#xff08;存储结构&#xff09;2.1、逻辑结构1、定义2、分类&#xff08;线性结构和非线性结构&#xff0…

Netty 入门 — 亘古不变的Hello World

这篇文章我们正式开始学习 Netty&#xff0c;在入门之前我们还是需要了解什么是 Netty。 什么是 Netty 为什么很多人都推崇 Java boy 去研究 Netty&#xff1f;Netty 这么高大上&#xff0c;它到底是何方神圣&#xff1f; 用官方的话说&#xff1a;Netty 是一款异步的、基于事…

四、K8S之Deployment

一、概述 在K8S中&#xff0c;Deployment是一种更高层级的控制器&#xff0c;用于管理应用程序的部署和更新。为 Pod和 ReplicaSet提供声明式的更新能力。比如&#xff1a; 部署ReplicaSet&#xff08;副本集&#xff09; 清理不再需要的旧版RS 扩展/缩小RS里的Pod数量 动态…