第四节 zookeeper集群与分布式锁

目录

1. Zookeeper集群操作

1.1 客户端操作zk集群

1.2 模拟集群异常操作

1.3 curate客户端连接zookeeper集群

2. Zookeeper实战案例

2.1 创建项目引入依赖

2.2 获取zk客户端对象

2.3 常用API

2.4 客户端向服务端写入数据流程

2.5 服务器动态上下线、客户端动态监听

2.6 测试

3.Zookeeper分布式锁

3.1 什么是分布式锁

3.2 Zookeeper分布式锁分析

3.3 分布式锁实现


1. Zookeeper集群操作

1.1 客户端操作zk集群

1) 第一步启动集群,启动后查看Zookeeper进程。

image.png

jps命令 作用是显示当前所有java 进程的pid 的命令,QuorumPeerMain是zookeeper集群的启动入口类

2) 客户端连接

  • 连接集群所有客户端

[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183                  

image.png

连接集群单个客户端

# 连接2181
[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2181 
​
# 连接2182
[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2182
​
# 在2181中创建节点
[zk: 192.168.58.200:2181(CONNECTED) 0] create /test2
​
# 在2182中查询,发现数据已同步
[zk: 192.168.58.200:2182(CONNECTED) 0] ls /
[test1, test2, zookeeper]

以上两种方式的区别在于:

  • 如果只连接单个客户端,如果当前连接的服务器挂掉,当前客户端连接也会挂掉,连接失败。

  • 如果是连接所有客户端的形式,则允许集群中半数以下的服务挂掉!当半数以上服务挂掉才会停止服务,可用性更高一点!

3)集群节点信息查看

集群中的节点信息被存放在每一个节点/zookeeper/config/目录下

image.png

1.2 模拟集群异常操作

Leader选举

  • Serverid : 服务器ID

    • 三台服务 编号分别是 1 2 3

    • 编号越大在选择算法中权重越大

  • Zxid: 数据ID

    • 服务器中存放的最大的数据ID, 值越大数据越新

  • 在Leader选举的过程中 如果某台Zookeeper获得了超过半数的选票,就可以当选Leader

(1)首先我们先测试如果是从服务器挂掉,会怎么样

把3号服务器停掉,观察1号和2号,发现状态并没有变化

/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh stop
​
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status

image.png

由此得出结论,3个节点的集群,从服务器挂掉,集群正常

(2)我们再把1号服务器(从服务器)也停掉,查看2号(主服务器)的状态,发现已经停止运行了。

/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh stop
​
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status

image.png

由此得出结论,3个节点的集群,2个从服务器都挂掉,主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。

(3)我们再次把1号服务器启动起来,发现2号服务器又开始正常工作了。而且依然是领导者。

image.png

(4)我们把3号服务器也启动起来,把2号服务器停掉,停掉后观察1号和3号的状态。

/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh stop
​
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status

image.png

发现新的leader产生了~

由此我们得出结论,当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader 。

(5)我们再次测试,当我们把2号服务器重新启动起来启动后,会发生什么?2号服务器会再次成为新的领导吗?我们看结果

/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
​
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status

我们会发现,2号服务器启动后依然是跟随者(从服务器),3号服务器依然是领导者(主服务器),没有撼动3号服务器的领导地位。

由此我们得出结论,当领导者产生后,再次有新服务器加入集群,不会影响到现任领导者。

image.png

1.3 curate客户端连接zookeeper集群

public class CuratorCluster {
​//zookeeper连接private final static String CLUSTER_CONNECT = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";
​//session超时时间private static final int sessionTimeoutMs = 60 * 1000;
​//连接超时时间private static final int connectionTimeoutMs = 5000;
​private static CuratorFramework client;
​public static String getClusterConnect() {return CLUSTER_CONNECT;}
​@Beforepublic void init(){
​// 重试策略RetryPolicy retryPolicy =new ExponentialBackoffRetry(3000,10);
​// zookeeper连接client = CuratorFrameworkFactory.builder().connectString(getClusterConnect()).sessionTimeoutMs(60*1000).connectionTimeoutMs(15*1000).retryPolicy(retryPolicy).namespace("mashibing")  //当前程序创建目录的根目录.build();
​// 添加监听器client.getConnectionStateListenable().addListener(new ConnectionStateListener() {@Overridepublic void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {System.out.println("连接成功!");}});
​client.start();}
​//创建节点public void createIfNeed(String path) throws Exception {Stat stat = client.checkExists().forPath(path);if(stat == null){String s = client.create().forPath(path);System.out.println("创建节点: " + s);}}
​
​//从集群中获取数据@Testpublic void testCluster() throws Exception {createIfNeed("/test");
​//每隔一段时间 获取一次数据while(true){byte[] data = client.getData().forPath("/test");System.out.println(new String(data));
​TimeUnit.SECONDS.sleep(5);}}
}

在集群中的任意服务器节点,为test设置数据

[zk: 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183(CONNECTED) 2] set /mashibing/test 12345

image.png

2. Zookeeper实战案例

2.1 创建项目引入依赖

<dependency><groupId>com.101tec</groupId><artifactId>zkclient</artifactId><version>0.10</version>
</dependency>

2.2 获取zk客户端对象

public class ZkClientTest {private String connectString = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";private int sessionTimeout = 2000;private ZkClient zkClient;/*** 获取zk客户端连接*/@Beforepublic void Before(){/*** 参数1:服务器的IP和端口* 参数2:会话的超时时间* 参数3:会话的连接时间* 参数4:序列化方式*/zkClient = new ZkClient(connectString, sessionTimeout, 1000 * 15, new SerializableSerializer());}@Afterpublic void after(){zkClient.close();}
}

2.3 常用API

  • 创建节点

    /*** 创建节点*/@Testpublic void testCreateNode(){//创建方式 返回创建节点名称String nodeName = zkClient.create("/node1", "lisi", CreateMode.PERSISTENT);System.out.println("路径名称为:" + nodeName);zkClient.create("/node2","wangwu",CreateMode.PERSISTENT_SEQUENTIAL);zkClient.create("/node3","hehe",CreateMode.EPHEMERAL);zkClient.create("/node4","haha",CreateMode.EPHEMERAL_SEQUENTIAL);while(true){}}
  • 删除节点

    /*** 删除节点*/@Testpublic void testDeleteNode(){// 删除没有子节点的节点
//        boolean b1 = zkClient.delete("/node2");
//        System.out.println("删除成功: " + b1);// 递归删除节点信息boolean b2 = zkClient.deleteRecursive("/node2");System.out.println("删除成功: " + b2);}
  • 查看节点的子节点

    /*** 查询节点的子节点*/@Testpublic void testFindNodes(){//返回指定路径的节点信息List<String> ch = zkClient.getChildren("/");for (String c1 : ch) {System.out.println(c1);}}
  • 查看当前节点的数据

    • 注意:如果出现:org.I0Itec.zkclient.exception.ZkMarshallingError: java.io.StreamCorruptedException: invalid stream header: 61616161 异常的原因是: 在shell中的数据序列化方式 和 java代码中使用的序列化方式不一致导致 因此要解决这个问题只需要保证序列化一致即可 都使用相同端操作即可

    /*** 获取节点数据*/@Testpublic void testFindNodeData(){String nodeName = zkClient.create("/node3", "taotao", CreateMode.PERSISTENT);Object data = zkClient.readData("/node3");System.out.println(data);}
  • 查看当前节点的数据并获取状态信息

    /*** 获取数据以及当前节点状态信息*/@Testpublic void testFindNodeDataAndStat(){Stat stat = new Stat();Object data = zkClient.readData("/node20000000004", stat);System.out.println(data);System.out.println(stat);}
  • 修改节点数据

    /*** 修改节点*/@Testpublic void testUpdateNodeData(){zkClient.writeData("/node3","123456");}
  • 监听节点数据的变化

    /*** 监听节点数据*/@Testpublic void testNodeChange(){zkClient.subscribeDataChanges("/node3", new IZkDataListener() {// 当节点的值在修改时,会自动调用这个方法@Overridepublic void handleDataChange(String nodeName, Object result) throws Exception {System.out.println("节点名称: " + nodeName);System.out.println("节点数据: " + result);}// 当节点被删除时,会调用该方法@Overridepublic void handleDataDeleted(String nodeName) throws Exception {System.out.println("节点名称: " + nodeName);}});while(true){}}
  • 监听节点目录的变化

    /*** 监听节点目录的变化*/@Testpublic void testNodesChange(){zkClient.subscribeChildChanges("/node3", new IZkChildListener() {@Overridepublic void handleChildChange(String nodeName, List<String> list) throws Exception {System.out.println("父节点名称: " + nodeName);System.out.println("发生变更后,所有子节点名称: ");for (String name : list) {System.out.println(name);}}});while(true){}}
  • 判断某一个节点是否存在

    //判断节点是否存在@Testpublic void exist(){boolean exists = zkClient.exists("/node3");System.out.println(exists == true ? "节点存在" : "节点不存在");}

2.4 客户端向服务端写入数据流程

  • 写流程之写入请求,直接发送给Leader

image.png

  • 写流程之写入请求,发送给follower节点

image.png

2.5 服务器动态上下线、客户端动态监听

某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。

image.png

1)根节点下,创建servers节点

[zk: 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183(CONNECTED) 0] 
create /servers "servers"
Created /servers

2)服务端代码

完成服务端向zookeeper注册、动态上下线的代码。

/*** 服务端*/
public class DistributeServer {private ZooKeeper client;// 连接信息private String connectString = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";// 超时时间private int sessionTimeOut = 30000;public static void main(String[] args) throws Exception {DistributeServer server = new DistributeServer();//1.获取zk连接server.getConnect();//2.将服务器注册到zk集群,args参数通过启动 main方法时传入即可server.register(args[0]);//3.启动业务逻辑(线程睡眠)Thread.sleep(Long.MAX_VALUE);}/*** 注册操作* @param hostName 将服务器注册到zk集群时,所需的服务名称*/private void register(String hostName) throws Exception {/*** ZooDefs.Ids.OPEN_ACL_UNSAFE: 此权限表示允许所有人访问该节点(服务器)* CreateMode.EPHEMERAL_SEQUENTIAL: 由于服务器是动态上下线的,上线后存在,下线后不存在,所以是临时节点* 而服务器一般都是有序号的,所以是临时、有序的节点.*/String node = client.create("/servers/" + hostName,hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);System.out.println("已成功创建" + node + "节点");System.out.println(hostName + " 已经上线");}/*** 获取连接*/private void getConnect() throws IOException {client = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {}});}
}

2)客户端代码

服务端代码写好之后,再来完成客户端动态监听zk服务端各个节点的代码。

/*** 客户端*/
public class DistributeClient {private ZooKeeper zk;// 连接信息private String connectString = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";// 超时时间private int sessionTimeOut = 30000;public static void main(String[] args) throws Exception {DistributeClient client = new DistributeClient();//1.获取zk连接client.getConnection();//2.监听 /servers下面所有的子节点变化client.getServerList();//3.业务逻辑Thread.sleep(Long.MAX_VALUE);}/*** 获取连接*/private void getConnection() throws Exception {zk = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {//监听服务器地址的上下线try {getServerList();} catch (Exception e) {e.printStackTrace();}}});}/*** 监听 /servers路径下的所有子节点变化,true表示启动监听器*/private void getServerList() throws Exception {List<String> zkChildren = zk.getChildren("/servers", true);List<String> servers = new ArrayList<>();zkChildren.forEach(node -> {//拼接服务完整信息try {byte[] data = zk.getData("/servers/" + node, false, null);servers.add(new String(data));} catch (Exception e) {e.printStackTrace();}});System.out.println(servers);System.out.println("=======================");}
}

2.6 测试

1)Zookeeper命令行,完成测试

[zk: localhost:2181(CONNECTED) 1] create -e -s /servers/zk01 "192.168.58.200:2181"
Created /servers/zk010000000000

image.png

[zk: localhost:2181(CONNECTED) 2] create -e -s /servers/zk02 "192.168.58.200:2182"
Created /servers/zk020000000001

image.png

[zk: localhost:2181(CONNECTED) 3] create -e -s /servers/zk03 "192.168.58.200:2183"
Created /servers/zk030000000002

image.png

上面的执行结果可以看到,在servers下面依次创建子结点,客户端代码都可以成功监听到。

下面我们删除节点,看看客户端能不能做到动态监听功能(也即删除的节点不会再被监听到)。

[zk: localhost:2181(CONNECTED) 5] delete /servers/zk010000000000[zk: localhost:2181(CONNECTED) 7] delete /servers/zk020000000001[zk: localhost:2181(CONNECTED) 8] delete /servers/zk030000000002 

image.png

使用Java代码来测试

  • 先启动客户端代码

  • 再启动服务端代码

只是在服务端代码中,我们的 register 方法中传参用到了 args ,所以启动之前要传入这个参数。

image.png

传入 192.168.58.200 之后,可以看到服务端代码已经能够实现动态上线了。

image.png

这里我们的 192.168.58.200 动态上线之后,可以看到客户端也正常的监听到它了。

image.png

转到zk命令行中,也可以看到这台服务器的节点信息。

image.png

由于我们之前的服务端代码还启动着,此时我们再传入新的参数 192.168.58.200:2182,那么之前的2181服务器肯定会被挤掉(这里模拟就是main方法,同一个类肯定只能同时启动一次main方法了),那么我们看看客户端能不能动态监听到2181下线、2182上线。

image.png

服务端自然可以正常实现2182这台服务器的动态上线。在客户端代码中可以看到List集合中已经没有2181了(即2181已经下线了),而2182正常上线

image.png

3.Zookeeper分布式锁

3.1 什么是分布式锁

传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制,但是原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效。提出分布式锁的概念,是为了解决跨机器的互斥机制来控制共享资源的访问。

image.png

3.2 Zookeeper分布式锁分析

客户端(对zookeeper集群而言)向zookeeper集群进行上线注册,并在一个永久节点下创建有序的临时子节点后,根据编号顺序,最小顺序的子节点获取到锁,其他子节点由小到大监听前一个节点。

image.png

当拿到锁的节点处理完事务后,释放锁,后一个节点监听到前一个节点释放锁后,立刻申请获得锁,以此类推

image.png

3.3 分布式锁实现

1)创建 Distributedlock类, 获取与zookeeper的连接

  • 构造方法中获取连接

  • 添加 CountDownLatch

    CountDownLatch是具有synchronized机制的一个工具,目的是让一个或者多个线程等待,直到其他线程的一系列操作完成。

    CountDownLatch初始化的时候,需要提供一个整形数字,数字代表着线程需要调用countDown()方法的次数,当计数为0时,线程才会继续执行await()方法后的其他内容。

/*** 分布式锁*/
public class DistributedLock {private ZooKeeper client;// 连接信息private String connectString = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";// 超时时间private int sessionTimeOut = 30000;private CountDownLatch countDownLatch = new CountDownLatch(1);//1. 在构造方法中获取连接public DistributedLock() throws Exception {client = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {}});//等待Zookeeper连接成功,连接完成继续往下走countDownLatch.await();//2. 判断节点是否存在}//3.对ZK加锁public void zkLock(){//创建 临时带序号节点//判断 创建的节点是否是最小序号节点,如果是 就获取到锁;如果不是就监听前一个节点}//4.解锁public void unZkLock(){//删除节点}
}

2)对zk加锁

/*** 分布式锁*/
public class DistributedLock {private ZooKeeper client;// 连接信息private String connectString = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";// 超时时间private int sessionTimeOut = 30000;// 等待zk连接成功private CountDownLatch countDownLatch = new CountDownLatch(1);// 等待节点变化private CountDownLatch waitLatch = new CountDownLatch(1);//当前节点private String currentNode;//前一个节点路径private String waitPath;//1. 在构造方法中获取连接public DistributedLock() throws Exception {client = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {//countDownLatch 连上ZK,可以释放if(watchedEvent.getState() == Event.KeeperState.SyncConnected){countDownLatch.countDown();}//waitLatch 需要释放 (节点被删除并且删除的是前一个节点)if(watchedEvent.getType() == Event.EventType.NodeDeleted &&watchedEvent.getPath().equals(waitPath)){waitLatch.countDown();}}});//等待Zookeeper连接成功,连接完成继续往下走countDownLatch.await();//2. 判断节点是否存在Stat stat = client.exists("/locks", false);if(stat == null){//创建一下根节点client.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}//3.对ZK加锁public void zkLock(){//创建 临时带序号节点try {currentNode = client.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);//判断 创建的节点是否是最小序号节点,如果是 就获取到锁;如果不是就监听前一个节点List<String> children = client.getChildren("/locks", false);//如果创建的节点只有一个值,就直接获取到锁,如果不是,监听它前一个节点if(children.size() == 1){return;}else{//先排序Collections.sort(children);//获取节点名称String nodeName = currentNode.substring("/locks/".length());//通过名称获取该节点在集合的位置int index = children.indexOf(nodeName);//判断if(index == -1){System.out.println("数据异常");}else if(index == 0){//就一个节点,可以获取锁return;}else{//需要监听前一个节点变化waitPath = "/locks/" + children.get(index-1);client.getData(waitPath,true,null);//等待监听执行waitLatch.await();return;}}} catch (KeeperException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}
}

3)zk删除锁

    //4.解锁public void unZkLock() throws KeeperException, InterruptedException {//删除节点client.delete(currentNode,-1);}

4)测试

public class DistributedLockTest {
​public static void main(String[] args) throws Exception {
​final DistributedLock lock1 = new DistributedLock();final DistributedLock lock2 = new DistributedLock();
​new Thread(new Runnable() {@Overridepublic void run() {
​try {lock1.zkLock();System.out.println("线程1 启动 获取到锁");
​Thread.sleep(5 * 1000);lock1.unZkLock();System.out.println("线程1 释放锁");} catch (InterruptedException | KeeperException e) {e.printStackTrace();}}}).start();
​new Thread(new Runnable() {@Overridepublic void run() {
​try {lock2.zkLock();System.out.println("线程2 启动 获取到锁");
​Thread.sleep(5 * 1000);lock2.unZkLock();System.out.println("线程2 释放锁");} catch (InterruptedException | KeeperException e) {e.printStackTrace();}}}).start();}
}

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

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

相关文章

寒假 day10

1、请使用递归实现n! #include<stdio.h> #include<string.h> #include<stdlib.h>int fun(int m) {if(m0)return 1;else{return m*fun(m-1);} } int main(int argc, const char *argv[]) {int m;printf("please enter m:");scanf("%d",…

linux 08 文件查找

02. 第一. alias&#xff1a;起别名(可以输入别名就可以执行对应的命令)&#xff0c;语法&#xff1a;alias 别名‘ls -l’ 第二. locate&#xff1a; locate 找不到最近的文件 更新locate 后 find命令&#xff1a; find&#xff1a; find 路径 选项 文件名&#x…

ZigBee学习——在官方例程实现组网

✨Z-Stack版本&#xff1a;3.0.2 ✨IAR版本&#xff1a;10.10.1 ✨这篇博客是在善学坊BDB组网实验的基础上进行完善&#xff0c;并指出实现的过程中会出现的各种各样的问题&#xff01; 善学坊教程地址&#xff1a; ZigBee3.0 BDB组网实验 文章目录 一、基础工程选择二、可能遇…

C++入门学习(二十七)跳转语句—continue语句

当在循环中遇到continue语句时&#xff0c;它会跳过当前迭代剩余的代码块&#xff0c;并立即开始下一次迭代。这意味着continue语句用于跳过循环中特定的执行步骤&#xff0c;而不是完全终止循环。 直接看一下下面的代码更清晰&#xff1a; 与上一节的break语句可以做一下对比…

CentOS 7.9安装Tesla M4驱动、CUDA和cuDNN

正文共&#xff1a;1333 字 21 图&#xff0c;预估阅读时间&#xff1a;2 分钟 上次我们在Windows上尝试用Tesla M4配置深度学习环境&#xff08;TensorFlow识别GPU难道就这么难吗&#xff1f;还是我的GPU有问题&#xff1f;&#xff09;&#xff0c;但是失败了。考虑到Windows…

python+vue+django体育场地器材预约管理系统dyn9h

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm .体育馆管理系统有管理员和用户两个角色。用户功能有场地…

算法沉淀——位运算(leetcode真题剖析)

算法沉淀——位运算 常用位运算总结1.基础位运算2.确定一个数中第x位是0还是13.将一个数的第x位改成14.将一个数的第x位改成05.位图6.提取一个数最右边的17.删掉一个数最右边的18.异或运算9.基础例题 力扣题目讲解01.面试题 01.01. 判定字符是否唯一02.丢失的数字03.两整数之和…

给定具体日期 返回给定日期是星期几 calendar.weekday(year,month,day)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 给定具体日期 返回给定日期是星期几 calendar.weekday(year,month,day) [太阳]选择题 如果2024年2月12日是星期一&#xff0c;请问最后一个print语句的运行结果是&#xff1f; import calenda…

【Linux】信号保存与信号捕捉处理

信号保存与信号捕捉 一、信号保存1. 信号的发送2. 理解信号保存&#xff08;1&#xff09;信号保存原因&#xff08;2&#xff09;信号保存概念 3. 信号保存系统接口&#xff08;1&#xff09;sigset_t&#xff08;2&#xff09;sigprocmask()&#xff08;3&#xff09;sigpend…

第三百一十六回

[tod] 我们在上一章回中介绍了"如何在输入框中处理光标"相关的内容&#xff0c;本章回中将介绍如何添加输入框默认值.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 在项目中经常使用输入框获取用户输入的内容&#xff0c;有时候在输入框中反复输入相…

C/C++模板初阶

目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.1 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int&…

无人机飞行原理,多旋翼无人机飞行原理详解

多旋翼无人机升空飞行的首要条件是动力&#xff0c;有了动力才能驱动旋粪旋转&#xff0c;才能产生克服重力所必需的升力。使旋翼产生升力&#xff0c;进而推动多旋翼无人机升空飞行的一套设备装置称为动力装置&#xff0c;包括多旋翼无人机的发动机以及保证发动机正常工作所必…