zookeeper节点启动的主要逻辑

1.前言

QuorumPeer是一个线程对象,里面比较核心的方法是run方法,但QuorumPeer的run方法比较复杂,里面包含着针对QuorumPeer的各种状态的判断,里面的代码比较长,zk节点的looking状态下的操作,下面这块代码是针对QuorumPeer是Looking状态下的话,进行执行的代码逻辑,会有两个分支,根据判断节点是否配置readonlymode.enabled参数,然后有两个分支逻辑,这两个分支逻辑都会走同一个代码逻辑。readonlymode.enabled参数为true的时候会进行开启一个异步线程执行ReadOnlyZooKeeperServer的startup方法。

2.LOOKING状态下QuorumPeer的执行逻辑

下面这个是LOOKING状态下的代码逻辑

LOG.info("LOOKING");
ServerMetrics.getMetrics().LOOKING_COUNT.add(1);
//判断节点是否是一个只读节点的配置if (Boolean.getBoolean("readonlymode.enabled")) {LOG.info("Attempting to start ReadOnlyZooKeeperServer");// Create read-only server but don't start it immediatelyfinal ReadOnlyZooKeeperServer roZk = new ReadOnlyZooKeeperServer(logFactory, this, this.zkDb);// Instead of starting roZk immediately, wait some grace// period before we decide we're partitioned.//// Thread is used here because otherwise it would require// changes in each of election strategy classes which is// unnecessary code coupling.Thread roZkMgr = new Thread() {public void run() {try {// lower-bound grace period to 2 secssleep(Math.max(2000, tickTime));if (ServerState.LOOKING.equals(getPeerState())) {roZk.startup();}} catch (InterruptedException e) {LOG.info("Interrupted while attempting to start ReadOnlyZooKeeperServer, not started");} catch (Exception e) {LOG.error("FAILED to start ReadOnlyZooKeeperServer", e);}}};try {roZkMgr.start();reconfigFlagClear();if (shuttingDownLE) {shuttingDownLE = false;startLeaderElection();}setCurrentVote(makeLEStrategy().lookForLeader());checkSuspended();} catch (Exception e) {LOG.warn("Unexpected exception", e);setPeerState(ServerState.LOOKING);} finally {// If the thread is in the the grace period, interrupt// to come out of waiting.roZkMgr.interrupt();roZk.shutdown();}} else {try {reconfigFlagClear();if (shuttingDownLE) {shuttingDownLE = false;startLeaderElection();}setCurrentVote(makeLEStrategy().lookForLeader());} catch (Exception e) {LOG.warn("Unexpected exception", e);setPeerState(ServerState.LOOKING);}}

走同一块的代码逻辑

//将reconfigFlag这个字段的值设置为false
//不是很清楚这个 reconfigFlag字段的作用
reconfigFlagClear();if (shuttingDownLE) {shuttingDownLE = false;//QuorumPeer#start方法中已经进行了startLeaderElection方法的调用//这块看了下shuttingDownLE这个属性默认值为false 感觉一般情况下不会调用这个方法//开启选举算法 这块开始选择startLeaderElection();}//设置当前的选票 //makeLEStrategy().lookForLeader() 这个逻辑看上去是进行选举leader节点操作setCurrentVote(makeLEStrategy().lookForLeader());checkSuspended();

3.创建选举算法

虽然startLeaderElection这方法,在QuorumPeer的start方法中,已经被进行调用了,此处在looking状态下很有可能是不会被调用的,我们可以简单的看下startLeaderElection这个方法,我们这边看下的是zookeeper-3.9.1版本的代码

 public synchronized void startLeaderElection() {try {//判断当前QuorumPeer的状态如果是LOOKING的状态 会进行构建一个选票信息if (getPeerState() == ServerState.LOOKING) {currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());}} catch (IOException e) {RuntimeException re = new RuntimeException(e.getMessage());re.setStackTrace(e.getStackTrace());throw re;}//根据选举的类型进行创建一个选举的算法逻辑this.electionAlg = createElectionAlgorithm(electionType);}
/***
* 以前版本还支持 多个选举类型 会有不同的选举算法来进行对应
* 现在支持1种选举算法 FastLeaderElection 
**/
protected Election createElectionAlgorithm(int electionAlgorithm) {Election le = null;switch (electionAlgorithm) {case 1:throw new UnsupportedOperationException("Election Algorithm 1 is not supported.");case 2:throw new UnsupportedOperationException("Election Algorithm 2 is not supported.");case 3://进行构建一个网络通信的managerQuorumCnxManager qcm = createCnxnManager();QuorumCnxManager oldQcm = qcmRef.getAndSet(qcm);if (oldQcm != null) {LOG.warn("Clobbering already-set QuorumCnxManager (restarting leader election?)");oldQcm.halt();}//获取网络通信的组件的一个ListenerQuorumCnxManager.Listener listener = qcm.listener;if (listener != null) {//启动Listener 这个Listener主要是用来接收别的节点的信息listener.start();//构建FastLeaderElection对象FastLeaderElection fle = new FastLeaderElection(this, qcm);//启动选举算法 这个选举算法应该也是一个线程fle.start();le = fle;} else {LOG.error("Null listener when initializing cnx manager");}break;default:assert false;}return le;}

FastLeaderElection方法的构造方法,在FastLeaderElection的构造方法中,主要进行发送队列和接收队列的初始化,并对QuorumPeer的网络通信组件进行封装,用于后期进行网络通信。

//构造方法  
public FastLeaderElection(QuorumPeer self, QuorumCnxManager manager) {this.stop = false;this.manager = manager;starter(self, manager);}private void starter(QuorumPeer self, QuorumCnxManager manager) {this.self = self;proposedLeader = -1;proposedZxid = -1;//初始化一个发送队列sendqueue = new LinkedBlockingQueue<>();//初始化的一个接收队列recvqueue = new LinkedBlockingQueue<>();this.messenger = new Messenger(manager);}
//FastLeaderElection的start方法public void start() {this.messenger.start();}
//Messenger的start方法 此时会进行启动两个线程
void start() {//发送线程的启动 this.wsThread.start();//接收线程的启动this.wrThread.start();
}

4.lookForLeader

从前端的代码逻辑中分析得出,org.apache.zookeeper.server.quorum.FastLeaderElection#lookForLeader,这个方法开启leader节点的选举操作,当QuorumPeer的状态为LOOKING状态的时候,会进行调用此方法。

 public Vote lookForLeader() throws InterruptedException {//.....省略了部分代码try {//当前选票存放的集合Map<Long, Vote> recvset = new HashMap<>();Map<Long, Vote> outofelection = new HashMap<>();int notTimeout = minNotificationInterval;synchronized (this) {logicalclock.incrementAndGet();updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());}LOG.info("New election. My id = {}, proposed zxid=0x{}",self.getMyId(),Long.toHexString(proposedZxid));//发送选票信息sendNotifications();SyncedLearnerTracker voteSet = null;//循环交换选票信息 直达选出leader节点while ((self.getPeerState() == ServerState.LOOKING) && (!stop)) {// 从接收队列中进行获取选票信息Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);//如果选票信息为null 当zkServer节点第一次启动的时候肯定是nullif (n == null) {//manager.haveDelivered() 这个方法主要进行判断是否有已经连接的机器信息if (manager.haveDelivered()) {//如果已经有连接的机器信息的话 就进行给所有的节点发送选票信息sendNotifications();} else {//zkServer节点第一次启动的时候 连接机器的信息列表肯定为空//所以第一次的时候肯定会进行连接其他机器信息manager.connectAll();}/** Exponential backoff*/notTimeout = Math.min(notTimeout << 1, maxNotificationInterval);/** When a leader failure happens on a master, the backup will be supposed to receive the honour from* Oracle and become a leader, but the honour is likely to be delay. We do a re-check once timeout happens** The leader election algorithm does not provide the ability of electing a leader from a single instance* which is in a configuration of 2 instances.* */if (self.getQuorumVerifier() instanceof QuorumOracleMaj&& self.getQuorumVerifier().revalidateVoteset(voteSet, notTimeout != minNotificationInterval)) {setPeerState(proposedLeader, voteSet);Vote endVote = new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch);leaveInstance(endVote);return endVote;}LOG.info("Notification time out: {} ms", notTimeout);} else if (validVoter(n.sid) && validVoter(n.leader)) {//....省略很多代码} else {//....省略很多代码}}return null;} finally {try {if (self.jmxLeaderElectionBean != null) {MBeanRegistry.getInstance().unregister(self.jmxLeaderElectionBean);}} catch (Exception e) {LOG.warn("Failed to unregister with JMX", e);}self.jmxLeaderElectionBean = null;LOG.debug("Number of connection processing threads: {}", manager.getConnectionThreadCount());}}

5.集群中机器互联

zk节点最开始启动的时候,会进行leader节点的选举,在选举的过程中,要进行选票的统计,但要进行选票的统计的时候,需要接收zk集群中所有节点的数据。

//连接集群中的其他机器 
public void connectAll() {long sid;//循环遍历进行拦截机器信息for (Enumeration<Long> en = queueSendMap.keys(); en.hasMoreElements(); ) {sid = en.nextElement();connectOne(sid);}}

通过代码的一步一步的进去查看,我们找到了最终进行连接的操作, 这块zookeeper是进行启动一个异步线程进行连接操作,

 public boolean initiateConnectionAsync(final MultipleAddresses electionAddr, final Long sid) {if (!inprogressConnections.add(sid)) {LOG.debug("Connection request to server id: {} is already in progress, so skipping this request", sid);return true;}try {//线程池启动一个异步线程进行连接别的zk节点信息connectionExecutor.execute(new QuorumConnectionReqThread(electionAddr, sid));connectionThreadCnt.incrementAndGet();} catch (Throwable e) {inprogressConnections.remove(sid);LOG.error("Exception while submitting quorum connection request", e);return false;}return true;}

//异步连接请求线程private class QuorumConnectionReqThread extends ZooKeeperThread {final MultipleAddresses electionAddr;final Long sid;QuorumConnectionReqThread(final MultipleAddresses electionAddr, final Long sid) {super("QuorumConnectionReqThread-" + sid);this.electionAddr = electionAddr;this.sid = sid;}@Overridepublic void run() {try {//连接请求initiateConnection(electionAddr, sid);} finally {inprogressConnections.remove(sid);}}}

initiateConnection方法是初始化连接请求的数据,

//初始化连接请求
public void initiateConnection(final MultipleAddresses electionAddr, final Long sid) {Socket sock = null;try {LOG.debug("Opening channel to server {}", sid);//根据是否是SSL的类型进行创建不同的socketif (self.isSslQuorum()) {sock = self.getX509Util().createSSLSocket();} else {sock = SOCKET_FACTORY.get();}//设置socket的一些属性//tcpNoDelay soTimeout keepAlive等参数信息setSockOpts(sock);//开始正儿八经的连接操作sock.connect(electionAddr.getReachableOrOne(), cnxTO);if (sock instanceof SSLSocket) {SSLSocket sslSock = (SSLSocket) sock;sslSock.startHandshake();LOG.info("SSL handshake complete with {} - {} - {}",sslSock.getRemoteSocketAddress(),sslSock.getSession().getProtocol(),sslSock.getSession().getCipherSuite());}LOG.debug("Connected to server {} using election address: {}:{}",sid, sock.getInetAddress(), sock.getPort());} catch (X509Exception e) {LOG.warn("Cannot open secure channel to {} at election address {}", sid, electionAddr, e);closeSocket(sock);return;} catch (UnresolvedAddressException | IOException e) {LOG.warn("Cannot open channel to {} at election address {}", sid, electionAddr, e);closeSocket(sock);return;}try {//连接完成之后的一些处理,//包含设置一些输入,输出流,读写线程的启动等startConnection(sock, sid);} catch (IOException e) {LOG.error("Exception while connecting, id: {}, addr: {}, closing learner connection",sid,sock.getRemoteSocketAddress(),e);closeSocket(sock);}}

机器连接完成的一些操作,当机器连接完成之后,会进行调用startConnection方法

private boolean startConnection(Socket sock, Long sid) throws IOException {//data数据的输出流 从socket中进行获取并进行封装DataOutputStream dout = null;//data数据的输入流,从socket中获取并进行封装DataInputStream din = null;LOG.debug("startConnection (myId:{} --> sid:{})", self.getMyId(), sid);try {BufferedOutputStream buf = new BufferedOutputStream(sock.getOutputStream());dout = new DataOutputStream(buf);//连接完成之后 向连接的zk节点输出自己的节点idlong protocolVersion = self.isMultiAddressEnabled() ? PROTOCOL_VERSION_V2 : PROTOCOL_VERSION_V1;dout.writeLong(protocolVersion);dout.writeLong(self.getMyId());Collection<InetSocketAddress> addressesToSend = protocolVersion == PROTOCOL_VERSION_V2? self.getElectionAddress().getAllAddresses(): Arrays.asList(self.getElectionAddress().getOne());String addr = addressesToSend.stream().map(NetUtils::formatInetAddr).collect(Collectors.joining("|"));byte[] addr_bytes = addr.getBytes();dout.writeInt(addr_bytes.length);dout.write(addr_bytes);dout.flush();din = new DataInputStream(new BufferedInputStream(sock.getInputStream()));} catch (IOException e) {LOG.warn("Ignoring exception reading or writing challenge: ", e);closeSocket(sock);return false;}QuorumPeer.QuorumServer qps = self.getVotingView().get(sid);if (qps != null) {authLearner.authenticate(sock, qps.hostname);}//这块有一个逻辑 我觉得可以变更一下 这块是判断sid 如果小于自身的sid的时候//会进行关闭连接 那这块我觉得是不是可以在连接的时候 只连接比自己大的sid就可以了//而且这块的sid也是自己的sid 根本就不会大于 这个if里的操作就不会执行if (sid > self.getMyId()) {LOG.info("Have smaller server identifier, so dropping the connection: (myId:{} --> sid:{})", self.getMyId(), sid);closeSocket(sock);} else {LOG.debug("Have larger server identifier, so keeping the connection: (myId:{} --> sid:{})", self.getMyId(), sid);// 开始两个线程个 一个读的线程 一个写的线程 并进行启动 读写线程SendWorker sw = new SendWorker(sock, sid);RecvWorker rw = new RecvWorker(sock, din, sid, sw);sw.setRecv(rw);SendWorker vsw = senderWorkerMap.get(sid);if (vsw != null) {vsw.finish();}senderWorkerMap.put(sid, sw);queueSendMap.putIfAbsent(sid, new CircularBlockingQueue<>(SEND_CAPACITY));//启动读写线程sw.start();rw.start();return true;}return false;}

有连接就会有被连接的,比方说sid=1的节点进行发起连接的时候,别的zk节点是如何进行接收他的连接请求呢。这块代码逻辑是在哪里,还记得我们在创建选举算法的时候,会进行创建网络连接器,在网络连接器中有一个QuorumCnxManager.Listener,这个Listener会根据集群的数量启动一定的数量的ListenerHandler来进行监听连接。

 public void run() {if (!shutdown) {LOG.debug("Listener thread started, myId: {}", self.getMyId());//获取所有的连接地址的大小Set<InetSocketAddress> addresses;if (self.getQuorumListenOnAllIPs()) {addresses = self.getElectionAddress().getWildcardAddresses();} else {addresses = self.getElectionAddress().getAllAddresses();}//启动一个CountDownLatch 大小为连接地址集合的大小CountDownLatch latch = new CountDownLatch(addresses.size());listenerHandlers = addresses.stream().map(address ->new ListenerHandler(address, self.shouldUsePortUnification(), self.isSslQuorum(), latch)).collect(Collectors.toList());// 异步线程提交 listenerHandlersfinal ExecutorService executor = Executors.newFixedThreadPool(addresses.size());try {listenerHandlers.forEach(executor::submit);} finally {executor.shutdown();}try {//在此进行等待,等待所有的节点都连接成功latch.await();} catch (InterruptedException ie) {LOG.error("Interrupted while sleeping. Ignoring exception", ie);} finally {//解释所有的ListenerHandler监听for (ListenerHandler handler : listenerHandlers) {try {handler.close();} catch (IOException ie) {LOG.debug("Error closing server socket", ie);}}}}LOG.info("Leaving listener");if (!shutdown) {LOG.error("As I'm leaving the listener thread, I won't be able to participate in leader election any longer: {}",self.getElectionAddress().getAllAddresses().stream().map(NetUtils::formatInetAddr).collect(Collectors.joining("|")));if (socketException.get()) {// After leaving listener thread, the host cannot join the quorum anymore,// this is a severe error that we cannot recover from, so we need to exitsocketBindErrorHandler.run();}}}

ListenerHandler的主要逻辑 :进行创建ServerSocket这个东西,然后调用serverSocket.accept(),进行接受别的scoket的连接,接收到别的连接之后也会进行封装输入流和输出流,然后启动读写线程,用来进行接收后续的消息,但是这块有一个不同的地方。在接收完连接之后,会有一个handleConnection方法,在这个方法中会进行读取连接请求发送过来的sid,当sid小于当前sid的时候会尽心关闭连接,然后自己在主动发起一个连接请求。

 public void run() {try {Thread.currentThread().setName("ListenerHandler-" + address);//接收请求参数信息acceptConnections();try {close();} catch (IOException e) {LOG.warn("Exception when shutting down listener: ", e);}} catch (Exception e) {// Output of unexpected exception, should never happenLOG.error("Unexpected error ", e);} finally {//lietener中传入的countDownLatch 进行减一的操作latch.countDown();}}//acceptConnections方法private void acceptConnections() {int numRetries = 0;Socket client = null;//如果机器没有宕机并且重试次数还没达到最大次数的时候 会在这里进行循环等待连接while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) {try {serverSocket = createNewServerSocket();LOG.info("{} is accepting connections now, my election bind port: {}", QuorumCnxManager.this.mySid, address.toString());while (!shutdown) {try {client = serverSocket.accept();setSockOpts(client);LOG.info("Received connection request from {}", client.getRemoteSocketAddress());if (quorumSaslAuthEnabled) {//异步接收连接请求receiveConnectionAsync(client);} else {//同步接收连接请求receiveConnection(client);}numRetries = 0;} catch (SocketTimeoutException e) {LOG.warn("The socket is listening for the election accepted "+ "and it timed out unexpectedly, but will retry."+ "see ZOOKEEPER-2836");}}} catch (IOException e) {if (shutdown) {break;}LOG.error("Exception while listening to address {}", address, e);if (e instanceof SocketException) {socketException.set(true);}numRetries++;try {close();Thread.sleep(1000);} catch (IOException ie) {LOG.error("Error closing server socket", ie);} catch (InterruptedException ie) {LOG.error("Interrupted while sleeping. Ignoring exception", ie);}closeSocket(client);}}if (!shutdown) {LOG.error( "Leaving listener thread for address {} after {} errors. Use {} property to increase retry count.",formatInetAddr(address),numRetries,ELECTION_PORT_BIND_RETRY);}
}

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

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

相关文章

三大网络简介

一、三大网是哪三大网 三大网络为电话网、电视广播网、互联网&#xff0c;如果这三大网络使用都是“ip分组交换”技术的话&#xff0c;他们将会被融合成为一个网络&#xff0c; 但是由于历史原因&#xff0c;他们使用了不同的通信技术&#xff0c;三大网各自拥有相当的独立性&a…

2024-06-08 Unity 编辑器开发之编辑器拓展9 —— EditorUtility

文章目录 1 准备工作2 提示窗口2.1 双键窗口2.2 三键窗口2.3 进度条窗口 3 文件面板3.1 存储文件3.2 选择文件夹3.3 打开文件3.4 打开文件夹 4 其他内容4.1 压缩纹理4.2 查找对象依赖项 1 准备工作 ​ 创建脚本 “Lesson38Window.cs” 脚本&#xff0c;并将其放在 Editor 文件…

MySQL Show命令集

MySQL SHOW 命令 1、mysql shell 查看帮助show (rootlocalhost) [(none)]> \help show Name: SHOW Description: SHOW has many forms that provide information about databases, tables, columns, or status information about the server. This section describes thos…

Vue3【十二】09Computed计算属性

Vue3【十二】09Computed计算属性 计算属性 获取全名 这种方式是只读的不能修改 这样定义fullName是一个计算属性&#xff0c;可读可写 案例截图 目录结构 代码 Person.vue <template><div class"person"><h1>我是 Person 组件</h1>姓&…

基于OpenVINO实现无监督异常检测

异常检测(AD) 在欺诈检测、网络安全和医疗诊断等关键任务应用中至关重要。由于数据的高维性和底层模式的复杂性&#xff0c;图像、视频和卫星图像等视觉数据中的异常检测尤其具有挑战性。然而&#xff0c;视觉异常检测对于检测制造中的缺陷、识别监控录像中的可疑活动以及检测医…

三维重建 虚拟内窥镜(VE)是什么?怎么实现 使用场景

1.虚拟内窥镜&#xff1a; 就是利用计算机图形学、虚拟现实、图像处理和科学可视化等信息处理技术仿真光学内窥镜对病人进行诊断的一种技术。 VE(Virtual Endoscopy)&#xff0c;虚拟内镜技术。这种CT重建图像可以模拟各种内镜检查的效果&#xff0c;它是假设视线位于所要观察…

探索微软新VLM Phi-3 Vision模型:详细分析与代码示例

引言 在最近的微软Build大会上&#xff0c;微软宣布了许多新内容&#xff0c;其中包括新款Copilot PC和围绕Copilot生态系统的一系列功能。其中最引人注目的是发布了一些新的Phi模型&#xff0c;特别是Phi-3 Vision模型。本文将详细探讨Phi-3 Vision模型的特性&#xff0c;并提…

vue 路由(二)-- 进阶

vue 路由&#xff08;二&#xff09;-- 进阶 路由向组件传递参数传参方式: 在路径传递参数params name 传递参数query 参数 props 接收路由的 params完整的例子 HTML5 History 模式meta 元信息导航守卫全局守卫 过渡效果参考 路由可向路由匹配的组件传递参数&#xff0c; 不同…

高能来袭|联想拯救者携手《黑神话:悟空》玩转东方神话世界

从2020年首次发布实机演示视频以来&#xff0c;《黑神话&#xff1a;悟空》便在全球范围内获得了广泛关注&#xff0c;成为国产3A游戏的现象级爆款。6月&#xff0c;联想拯救者正式宣布成为《黑神话&#xff1a;悟空》全球官方合作伙伴&#xff0c;致力于共同革新国产游戏体验&…

第十二届蓝桥杯C++青少年组中/高级组选拔赛2020年11月22日真题解析

一、编程题 第1题&#xff1a;求和 【题目描述】 输入一个正整数 N(N < 100)&#xff0c;输出 1 到 N(包含 1 和 N)之间所有奇数的和。 【输入描述】 输入一个正整数 N(N < 100) 【输出描述】 输出 1 到 N 之间的所有奇数的和 【输入样例】 3【输出样例】 4答案&…

LIP模型动力学方程例子

线性倒立摆(Linear Inverted Pendulum, LIP)模型是用于描述和控制人形机器人步态的重要工具。LIP模型假设质心沿着一条固定的直线运动,并且所有质量集中在质心上。这简化了计算,使得模型更容易用于控制和稳定分析。 LIP模型动力学方程 LIP模型的基本假设是: 机器人的质心…

【病理数据】svs格式数据解读

Openslide 病理图像通常以.svs格式存储在数据库中。要想使用python处理svs格式的图像&#xff0c;我们通常使用Openslide模块。 关于Openslide模块的安装详见这个博客&#xff1a; 【解决Error】ModuleNotFoundError: No module named ‘openslide‘ 病理图像数据结构 病理图…

Latex中表格(3)

Latex中的表格 一、多行或多列单元格 这篇主要说Latex中表格出现多行或者多列单元格的形式. 一、多行或多列单元格 可能用到的宏包 \usepackage{booktabs}\usepackage{multirow} 代码&#xff1a; \begin{table}[h!] \centering \caption{Your caption here} \begin{tabul…

【iOS】UI——关于UIAlertController类(警告对话框)

目录 前言关于UIAlertController具体操作及代码实现总结 前言 在UI的警告对话框的学习中&#xff0c;我们发现UIAlertView在iOS 9中已经被废弃&#xff0c;我们找到UIAlertController来代替UIAlertView实现弹出框的功能&#xff0c;从而有了这篇关于UIAlertController的学习笔记…

Nextjs学习教程

一.手动创建项目 建议看这个中文网站文档,这个里面的案例配置都是手动的,也可以往下看我这个博客一步步操作 1.在目录下执行下面命令,初始化package.json文件 npm init -y2.安装react相关包以及next包 yarn add next react react-dom // 或者 npm install --save next react…

k8s常见故障--yaml文件检查没有问题 pod起不来(一直处于创建中)

故障信息 pod一直处于创建中 查看pod详细信息显示 kubectl describe pod 容器id文字 Events: Type Reason Age From Message Normal Scheduled 5m30s default-scheduler Successfully assigned default/nginx-server2-f97c6b9d5-d6dsp to worker02 Warning FailedCreatePod…

C语言之字符函数总结(全部!),一篇记住所有的字符函数

前言 还在担心关于字符的库函数记不住吗&#xff1f;不用担心&#xff0c;这篇文章将为你全面整理所有的字符函数的用法。不用记忆&#xff0c;一次看完&#xff0c;随查随用。用多了自然就记住了 字符分类函数和字符转换函数 C语言中有一系列的函数是专门做字符分类和字符转换…

Python | 刷题笔记

继承 class Father:__secret"you are your own kid"stroy"iam a handsome boy..."def tellstory(self):print("我的故事:",self.stroy)def __tellstory(self):print("我的秘密:",Father.__secret) class Son(Father):def tell(self…

XML解析库tinyxml2库使用详解

XML语法规则介绍及总结-CSDN博客 TinyXML-2 是一个简单轻量级的 C XML 解析库,它提供了一种快速、高效地解析 XML 文档的方式。 1. 下载地址 Gitee 极速下载/tinyxml2 2. 基本用法 下面将详细介绍 TinyXML-2 的主要使用方法: 2.1. 引入头文件和命名空间 #i…

Acrobat Pro DC 2023 for Mac/Win:全平台PDF编辑器的终极解决方案

对于需要处理PDF文档的个人和企业用户来说&#xff0c;Adobe Acrobat Pro DC 2023是一款不可或缺的工具。作为全球领先的PDF编辑器&#xff0c;Acrobat Pro DC 2023在Mac和Windows平台上提供了丰富的功能和令人印象深刻的性能&#xff0c;使其成为用户编辑、转换和管理PDF文档的…