【面试】Zookeeper
Zookeeper
- 1、ZooKeeper 介绍
- 2、znode 节点里面的存储
- 3、znode 节点上监听机制
- 4、ZooKeeper 集群部署
- 5、ZooKeeper 选举机制
- 6、何为集群脑裂
- 7、如何保证数据一致性
- 8、讲一下 zk 分布式锁实现原理吧
- 9、Eureka 与 Zk 有什么区别
1、ZooKeeper 介绍
- ZooKeeper 的核心特性
- 高可用性:ZooKeeper 通过多节点集群实现高可用性,即使部分节点故障,服务仍然可用。
- 一致性:ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议保证数据的一致性。
- 顺序性:ZooKeeper 保证客户端的操作顺序与请求顺序一致。
- 高性能:ZooKeeper 的设计目标是高吞吐量和低延迟。
- ZNode 的类型
-
ZooKeeper 的数据模型类似于文件系统的树形结构,每个节点称为 ZNode。ZNode 可以存储数据,并且可以有子节点。
- 持久(PERSISTENT)节点 :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
- 临时(EPHEMERAL)节点 :临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失 。并且,临时节点只能做叶子节点 ,不能创建子节点。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001 、/node1/app0000000002 。
- 临时顺序(EPHEMERAL_SEQUENTIAL)节点 :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
- ZooKeeper 的架构
-
ZooKeeper 集群通常由多个节点组成,其中一个节点是 Leader,其他节点是 Follower。
- Leader:负责处理写请求,并将写操作广播给 Follower。
- Follower:处理读请求,并参与 Leader 选举和写操作的投票。
- ZooKeeper 的核心功能
- 配置管理
- ZooKeeper 可以用于存储和管理分布式系统的配置信息。
- 示例:HBase 使用 ZooKeeper 存储 RegionServer 的配置。
- 命名服务
- ZooKeeper 可以用于实现分布式系统中的命名服务。
- 示例:Kafka 使用 ZooKeeper 管理 Broker 的命名和元数据。
- 分布式锁
- ZooKeeper 可以用于实现分布式锁,确保多个节点之间的互斥访问。
- 示例:使用临时顺序节点实现分布式锁。
- 领导者选举
- ZooKeeper 可以用于实现分布式系统中的领导者选举。
- 示例:HBase 使用 ZooKeeper 选举 Master 节点。
- 服务发现
- ZooKeeper 可以用于实现服务发现,动态管理服务的注册和发现。
- 示例:Dubbo 使用 ZooKeeper 实现服务注册和发现。
2、znode 节点里面的存储
- stat :状态信息。
Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等,如下:
- data : znode 存储业务数据信息。
- acl : 记录客户端对 znode 节点访问权限,如 IP 等。
- ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- CREATE : 能创建子节点
- READ :能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- DELETE : 能删除子节点
- ADMIN : 能设置节点 ACL 的权限
- 其中尤其需要注意的是,CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。对于身份认证,提供了以下几种方式:
- world : 默认方式,所有用户都可无条件访问。
- auth :不使用任何 id,代表任何已认证的用户。
- digest :用户名:密码认证方式: username:password 。
- ip : 对指定 ip 进行限制
- ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- child : 当前节点子节点引用。
3、znode 节点上监听机制
Watcher 为事件监听器,是 zk 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端注册指定的 watcher ,当服务端符合了 watcher 的某些事件或要求则会向客户端发送事件通知 ,客户端收到通知后找到自己定义的 Watcher 然后执行相应的回调方法 。
可以把 Watcher 理解成客户端注册在某个 Znode 上触发器,当这个 Znode 节点发生变化时(增删改查),就会触发 Znode 对应注册事件,注册客户端就会收到异步通知,然后做出业务改变。
zookeeper 监听原理
zookeeper的监听事件有四种
- nodedatachanged 节点数据改变
- nodecreate 节点创建事件
- nodedelete 节点删除事件
- nodechildrenchanged 子节点改变事件
ZooKeeper Watcher 机制主要包括客户端线程、客户端WatcherManager、Zookeeper 服务器三部分。
- 客户端向 ZooKeeper 服务器注册 Watcher 同时,会将 Watcher 对象存储在客户端 WatchManager 中。
- 当 zookeeper 服务器触发 watcher 事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应Watcher 对象来执行回调逻辑。
4、ZooKeeper 集群部署
为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议来保持数据的一致性。
最典型集群模式: Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
ZooKeeper 集群角色
ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
5、ZooKeeper 选举机制
ZooKeeper 的选举机制 是保证其高可用性和一致性的核心部分。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议 来实现领导者选举和数据同步。
选举的触发条件
集群启动:当 ZooKeeper 集群启动时,所有节点会参与选举。
Leader 失效:当 Leader 节点失效时,Follower 节点会重新选举 Leader。
① 选举初始化
- 当 ZooKeeper 集群启动或 Leader 节点失效时,集群中的节点会进入选举状态。每个节点会尝试选举自己为 Leader。
② 投票
- 每个节点会投票给自己,并将投票信息(包括节点的 ID 和事务 ID)广播给其他节点。节点收到其他节点的投票后,会比较投票信息:
- 如果收到的投票的事务 ID 更大,则更新自己的投票。
- 如果事务 ID 相同,则比较节点 ID,选择更大的节点 ID。
③ 确定 Leader
- 当某个节点收到超过半数的投票时,该节点被选举为 Leader。Leader 会向其他节点发送确认消息,其他节点确认后成为 Follower。
④ 数据同步
- Leader 会将自己的数据状态同步给 Follower,确保集群中的数据一致性。同步完成后,集群进入正常工作状态。
6、何为集群脑裂
对于一个集群,想要提高这个集群的可用性,通常会采用多机房部署,比如现在有一个由6台zkServer所组成的一个集群,部署在了两个机房:
正常情况下,此集群只会有一个Leader,那么如果机房之间的网络断了之后,两个机房内的zkServer还是可以相互通信的,如果不考虑过半机制,那么就会出现每个机房内部都将选出一个Leader。
这就相当于原本一个集群,被分成了两个集群,出现了两个“大脑”,这就是脑裂。
对于这种情况,我们也可以看出来,原本应该是统一的一个集群对外提供服务的,现在变成了两个集群同时对外提供服务,如果过了一会,断了的网络突然联通了,那么此时就会出现问题了,两个集群刚刚都对外提供服务了,数据该怎么合并,数据冲突怎么解决等等问题。
刚刚在说明脑裂场景时,有一个前提条件就是没有考虑过半机制,所以实际上Zookeeper集群中是不会出现脑裂问题的,而不会出现的原因就跟过半机制有关。
过半机制
举个简单的例子: 如果现在集群中有6台zkServer,也就是说至少要4台zkServer才能选出来一个Leader,才会符合过半机制,才能选出来一个Leader。
所以对于机房1来说它不能选出一个Leader,同样机房2也不能选出一个Leader,这种情况下整个集群当机房间的网络断掉后,整个集群将没有Leader。
如果假设我们现在只有5台机器,也部署在两个机房:
也就是至少要3台服务器才能选出一个Leader,此时机房件的网络断开了,对于机房1来说是没有影响的,Leader依然还是Leader,对于机房2来说是选不出来Leader的,此时整个集群中只有一个Leader。
所以,我们可以总结得出,有了过半机制,对于一个Zookeeper集群,要么没有Leader,要没只有1个Leader,这样就避免了脑裂问题。
过半机制是如何防止脑裂现象产生的
ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
ZooKeeper 集群为啥最好奇数台
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。 假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。
综上,何必增加那一个不必要的 ZooKeeper 呢?
7、如何保证数据一致性
在 ZooKeeper 集群中,所有客户端的请求都是写入到 Leader 进程中的,然后,由 Leader 同步到其他节点,称为 Follower。在集群数据同步的过程中,如果出现 Follower 节点崩溃或者 Leader 进程崩溃时,都会通过 Zab 协议来保证数据一致性。
- ZAB 协议的核心思想
ZAB 协议是一种原子广播协议,用于在分布式系统中实现数据的一致性和可靠性。它的核心思想包括:
- 原子广播:确保所有写操作以相同的顺序被所有节点接收和执行。
- 领导者选举:在集群启动或 Leader 失效时,选举新的 Leader 来协调写操作。
- 数据同步:确保 Leader 和 Follower 之间的数据一致性。
- ZAB 协议的工作流程
ZAB 协议的工作流程分为两个阶段:
- 选举阶段:
- 当集群启动或 Leader 失效时,ZooKeeper 会进入选举阶段。
- 通过投票机制选举新的 Leader,确保集群中只有一个 Leader。
- 广播阶段:
- Leader 负责接收客户端的写请求,并将写操作广播给所有 Follower。
- Follower 接收到写操作后,将其应用到本地状态,并向 Leader 发送确认。
- 当 Leader 收到多数 Follower 的确认后,提交写操作并通知客户端。
消息广播阶段,ZooKeeper中的一个节点被选为leader节点,它接收来自客户端的事务提交请求,并将这些请求作为proposal广播给其他follower节点。每个follower节点收到proposal后会进行反馈,leader节点根据收集到的反馈决定是否执行commit操作。为了保证数据一致性,ZooKeeper使用了quorum选举机制来决定大多数节点上的commit结果。
client端发起请求,读请求由follower和observer直接返回,写请求由它们转发给leader。Leader 首先为这个事务分配一个全局单调递增的唯一事务ID (即 ZXID )。然后发起proposal给follower,Leader 会为每一个 Follower 都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,并且根据 FIFO策略进行消息发送。每一个 Follower 在接收到这个事务 Proposal 之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给 Leader 服务器一个 Ack 响应。当 Leader 服务器接收到超过半数 Follower 的 Ack 响应后,就会广播一个Commit 消息给所有的 Follower 服务器以通知其进行事务提交,同时Leader 自身也会完成对事务的提交。
8、讲一下 zk 分布式锁实现原理吧
实现分布式锁要借助临时顺序节点和watch,首先我们要有一个持久节点,客户端获取锁就是在持久节点下创建临时顺序节点。客户端创建的临时顺序节点创建成功后会判断节点是不是最小节点,如果是最小节点那么获取锁成功,否则回去锁失败。如果获取锁失败,则说明有其他客户端已成功获得锁,这时候也不需要循环尝试去加锁,而是给前一个节点注册一个事件监听器,这个监听器作用就是当前一个节点释放后,也就是节点删除后通知自己让自己获得锁,这样的好处是不会通知到所有的节点去争夺锁(避免无效自旋)。所以使用Zookeeper实现的分布式锁是公平锁。
为什么要用临时顺序节点
临时节点相比持久节点,最主要的是对会话失效的情况处理不一样,临时节点会话消失则对应的节点消失。这样的话,如果客户端发生异常导致没来得及释放锁也没关系,会话失效节点自动被删除,不会发生死锁的问题。
使用 Redis 实现分布式锁的时候,我们是通过过期时间来避免锁无法被释放导致死锁问题的,而 ZooKeeper 直接利用临时节点的特性即可。
假设不适用顺序节点的话,所有尝试获取锁的客户端都会对持有锁的子节点加监听器。当该锁被释放之后,势必会造成所有尝试获取锁的客户端来争夺锁,这样对性能不友好。使用顺序节点之后,只需要监听前一个节点就好了,对性能更友好。
为什么要设置对前一个节点的监听
同一时间段内,可能会有很多客户端同时获取锁,但只有一个可以获取成功。如果获取锁失败,则说明有其他的客户端已经成功获取锁。获取锁失败的客户端并不会不停地循环去尝试加锁,而是在前一个节点注册一个事件监听器。
这个事件监听器的作用是: 当前一个节点对应的客户端释放锁之后(也就是前一个节点被删除之后,监听的是删除事件),通知获取锁失败的客户端(唤醒等待的线程,Java 中的 wait/notifyAll ),让它尝试去获取锁,然后就成功获取锁了。
原生API 分布式锁
package com.example.test.other.zk;import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class ZookepeerLock1 {private final String connectionString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";private final int sessionTimeout = 2000;private final ZooKeeper zk;private CountDownLatch countDownLatch = new CountDownLatch(1);private CountDownLatch waitLatch = new CountDownLatch(1);private String waitPath;private String currentMode;public ZookepeerLock1() throws IOException, InterruptedException, KeeperException {// 获取连接zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {// connectLatch 如果连接上zk 可以释放if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {countDownLatch.countDown();}// waitLatch 需要释放if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {waitLatch.countDown();}}});// 等待zk正常连接后,往下走程序countDownLatch.await();// 判断根节点/locks是否存在Stat stat = zk.exists("/lockZookeeper", false);if (stat == null) {// 创建根节点,这是⼀个完全开放的ACL,持久节点zk.create("/lockZookeeper", "lockZookeeper".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}// 对zk加锁public void zkLock() {try {// 创建对应的临时顺序节点currentMode =zk.create("/lockZookeeper/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);// 判断创建的节点是否是序号最小的节点,如果是获取到锁,如果不是,监听他序号前一个节点List<String> children = zk.getChildren("/lockZookeeper", false);if (children.size() == 1) {return;} else {//[seq-0000000016, seq-0000000017]Collections.sort(children);// 获取节点名称 /locks/seq-0000000017 -> seq-0000000017String thisNode = currentMode.substring("/lockZookeeper/".length());// 通过seq-0000000017获取该节点在children集合的位置int index = children.indexOf(thisNode);// 判断if (index == -1) {System.out.println("数据异常");} else if (index == 0) {// 就一个节点,可以获取锁了return;} else {// 需要监听 他前一个结点的变化waitPath = "/lockZookeeper/" + children.get(index - 1);zk.getData(waitPath, true, null);// 等待监听waitLatch.await();}}} catch (KeeperException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}// 解锁public void unZkLock() {// 删除节点try {zk.delete(currentMode, -1);} catch (InterruptedException e) {e.printStackTrace();} catch (KeeperException e) {e.printStackTrace();}}
}
Curator 分布式锁
package com.example.test.other.zk;import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.KeeperException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.io.IOException;
/*** Description: 1、@Bean单独注解方法时,每次调用方法都是执行方法内的逻辑并返回新创建的对象bean,而且SpringIOC并没有该bean的存在。* 2、@Bean + @Configuration ,在调用@Bean注解的方法时返回的实例bean是从IOC容器获取的,已经注入的,且是单例的,而不是新创建的。* 3、@Bean + @Component,虽然@Bean注解的方法返回的实例已经注入到SpringIOC容器中,但是每次调用@Bean注解的方法时,都会创建新的对象实例bean返回,并不会从IOC容器中获取。* Author: yangjj_tc* Date: 2023/5/18 13:25*/
@Configuration
public class ZookeperLock1Test {private final String ZOOKEEPER_ADDRESS = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";/*** Description: Author:* yangjj_tc* 1、会话连接是异步的,需要自己去处理。比如使用CountDownLatch* 2、Watch需要重复注册,不然就不能生效* 3、开发的复杂性还是比较高的* 4、不支持多节点删除和创建。需要自己去递归* Date: 2023/5/18 12:48*/@Beanpublic CuratorFramework getCuratorFramework(){// 重试策略,重试间隔时间为1秒,重试次数为3次。curator管理了zookeeper的连接,在操作zookeeper的过程中出现连接问题会自动重试。RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);// 初始化客户端,通过工厂创建连接// zk地址 会话超时时间,默认60秒 连接超时时间,默认15秒 重试策略CuratorFramework zkClient = CuratorFrameworkFactory.newClient(ZOOKEEPER_ADDRESS, 5000, 15000, retryPolicy);// 开始连接zkClient.start();return zkClient;}public static void main(String[] args) throws InterruptedException, IOException, KeeperException {// 获得两个客户端CuratorFramework client1 = new ZookeperLock1Test().getCuratorFramework();CuratorFramework client2 = new ZookeperLock1Test().getCuratorFramework();// 可重入锁, 意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。如想重入,则需要使用同一个InterProcessMutex对象。final InterProcessLock lock1 = new InterProcessMutex(client1, "/lockCurator");final InterProcessLock lock2 = new InterProcessMutex(client2, "/lockCurator");// 不可重入锁,区别在于该锁是不可重入的,在同一个线程中不可重入final InterProcessSemaphoreMutex lock3 = new InterProcessSemaphoreMutex(client1, "/lockCurator");final InterProcessSemaphoreMutex lock4 = new InterProcessSemaphoreMutex(client2, "/lockCurator");// 模拟两个线程new Thread(() -> {try {// 线程加锁lock3.acquire();lock3.acquire();System.out.println("线程1获取锁");// 线程沉睡Thread.sleep(5 * 1000);// 线程解锁lock1.release();System.out.println("线程1释放了锁");} catch (Exception e) {e.printStackTrace();}}).start();// 线程2new Thread(() -> {// 线程加锁try {lock2.acquire();System.out.println("线程2获取到锁");// 线程沉睡Thread.sleep(5 * 1000);lock2.release();System.out.println("线程2释放锁");} catch (Exception e) {e.printStackTrace();}}).start();}
}
9、Eureka 与 Zk 有什么区别
- Eureka采用 AP 模型,优先保证可用性,允许短暂的数据不一致。通过心跳机制和客户端缓存实现最终一致性。ZooKeeper采用 CP 模型,在分区故障时可能牺牲可用性。使用 ZAB 协议保证强一致性。
- Eureka是对等架构:Eureka 节点之间是对等的,每个节点都可以接收注册和查询请求。客户端缓存,客户端会缓存服务注册表,即使 Eureka 服务器不可用,客户端仍然可以获取服务信息。ZooKeeper主从架构:ZooKeeper 集群中有 Leader 和 Follower 节点,写操作由 Leader 处理。强一致性:所有节点数据保持一致,客户端总是读取到最新数据。
相关文章:

【面试】Zookeeper
Zookeeper 1、ZooKeeper 介绍2、znode 节点里面的存储3、znode 节点上监听机制4、ZooKeeper 集群部署5、ZooKeeper 选举机制6、何为集群脑裂7、如何保证数据一致性8、讲一下 zk 分布式锁实现原理吧9、Eureka 与 Zk 有什么区别 1、ZooKeeper 介绍 ZooKeeper 的核心特性 高可用…...

电力系统中各参数的详细解释【智能电表】
一、核心电力参数 电压 (Voltage) 单位:伏特(V) 含义:电势差,推动电流流动的动力 类型:线电压(三相系统)、相电压,如220V(家用)或380Vÿ…...

前端系统测试(单元、集成、数据|性能|回归)
有关前端测试的面试题 系统测试 首先,功能测试部分。根据资料,单元测试是验证最小可测试单元的正确性,比如函数或组件。都提到了单元测试的重要性,强调其在开发早期发现问题,并通过自动化提高效率。需要整合我搜索到的资料中的观点,比如单元测试的方法(接口测试、路径覆…...

软件开发过程总揽
开发模型 传统开发模型 瀑布模型 #mermaid-svg-yDNBSwh3gDYETWou {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-yDNBSwh3gDYETWou .error-icon{fill:#552222;}#mermaid-svg-yDNBSwh3gDYETWou .error-text{fill:#…...

VBA第二十期 VBA最简单复制整张表格Cells的用法
前面讲过复制整张表格的方法,使用语句Workbooks("实例.xlsm").Sheets("表格1").Copy Workbooks(wjm).Sheets(1)实现,这里用我们熟悉的Cells属性也可以实现整表复制。实例如下: Sheets("全部").Activate Cells…...

Redis为什么要自定义序列化?如何实现自定义序列化器?
在 Redis中,通常会使用自定义序列化器,那么,Redis为什么需要自定义序列化器,该如何实现它? 1、为什么需要自定义序列化器? 整体来说,Redis需要自定义序列化器,主要有以下几个原因&…...

Matlab:矩阵运算篇——矩阵数学运算
目录 1.矩阵的加法运算 实例——验证加法法则 实例——矩阵求和 实例——矩阵求差 2.矩阵的乘法运算 1.数乘运算 2.乘运算 3.点乘运算 实例——矩阵乘法运算 3.矩阵的除法运算 1.左除运算 实例——验证矩阵的除法 2.右除运算 实例——矩阵的除法 ヾ( ̄…...

手写一个Tomcat
Tomcat 是一个广泛使用的开源 Java Servlet 容器,用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂,但通过手写一个简易版的 Tomcat,我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat,并深…...

开发ai模型最佳的系统是Ubuntu还是linux?
在 AI/ML 开发中,Ubuntu 是更优选的 Linux 发行版,原因如下: 1. 开箱即用的 AI 工具链支持 Ubuntu 预装了主流的 AI 框架(如 TensorFlow、PyTorch)和依赖库,且通过 apt 包管理器可快速部署开发环境。 提…...

Scala 中生成一个RDD的方法
在 Scala 中,生成 RDD(弹性分布式数据集)的主要方法是通过 SparkContext(或 SparkSession)提供的 API。以下是生成 RDD 的常见方法: 1. 从本地集合创建 RDD 使用 parallelize 方法将本地集合(如…...

【redis】慢查询分析与优化
慢查询指在Redis中执行时间超过预设阈值的命令,其日志记录是排查性能瓶颈的核心工具。Redis采用单线程模型,任何耗时操作都可能阻塞后续请求,导致整体性能下降。 命令的执行流程 根据Redis的核心机制,命令执行流程可分为以下步骤…...

P8925 「GMOI R1-T2」Light 题解
P8925 「GMOI R1-T2」Light 让我们好好观察样例解释的这一张图: 左边第 1 1 1 个像到 O O O 点的距离 : L 2 2 L L\times22L L22L 右边第 1 1 1 个像到 O O O 点的距离 : R 2 2 R R\times22R R22R 左边第 2 2 2 个像到 O O O 点…...

Spring Boot + MyBatis + MySQL:快速搭建CRUD应用
一、引言 1. 项目背景与目标 在现代Web开发中,CRUD(创建、读取、更新、删除)操作是几乎所有应用程序的核心功能。本项目旨在通过Spring Boot、MyBatis和MySQL技术栈,快速搭建一个高效、简洁的CRUD应用。我们将从零开始ÿ…...

python中os库的常用举例
os 库是Python中用于与操作系统进行交互的标准库,以下是一些 os 库的常用示例: 获取当前工作目录 python import os current_dir os.getcwd() print(current_dir) os.getcwd() 函数用于获取当前工作目录的路径。 列出目录内容 python import os …...

Unity 通用UI界面逻辑总结
概述 在游戏开发中,常常会遇到一些通用的界面逻辑,它不论在什么类型的游戏中都会出现。为了避免重复造轮子,本文总结并提供了一些常用UI界面的实现逻辑。希望可以帮助大家快速开发通用界面模块,也可以在次基础上进行扩展修改&…...

Python3 与 VSCode:深度对比分析
Python3 与 VSCode:深度对比分析 引言 Python3 和 Visual Studio Code(VSCode)在软件开发领域扮演着举足轻重的角色。Python3 作为一门强大的编程语言,拥有丰富的库和框架,广泛应用于数据科学、人工智能、网络开发等多个领域。而 VSCode 作为一款轻量级且功能强大的代码…...

第五课:Express框架与RESTful API设计:技术实践与探索
在使用Node.js进行企业应用开发,常用的开发框架Express,其中的中间件、路由配置与参数解析、RESTful API核心技术尤为重要,本文将深入探讨它们在应用开发中的具体使用方法,最后通过Postman来对开发的接口进行测试。 一、Express中…...

Linux 内核自定义协议族开发:从 “No buffer space available“ 错误到解决方案
引言 在 Linux 内核网络协议栈开发中,自定义协议族(Address Family, AF)是实现新型通信协议或扩展内核功能的关键步骤。然而,开发者常因对内核地址族管理机制理解不足,遇到如 insmod: No buffer space available 的错误。本文将以实际案例为基础,深入分析错误根源,并提…...

html-列表标签和表单标签
一、列表标签 表格是用来显示数据的,那么列表就是用来布局的 列表最大的特点就是整齐、整洁、有序,它作为布局会更加自由和方便。 根据使用情景不同,列表可以分为三大类:无序列表、有序列表和自定义列表。 1.无序列表(重…...

HTML-网页介绍
一、网页 1.什么是网页: 网站是指在因特网上根据一定的规则,使用 HTML 等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”,通常是 HTML 格式的文件,它要通过浏览器来阅读。 网页是构成网站的基本元素…...

动态ip和静态ip适用于哪个场景?有何区别
在数字化浪潮席卷全球的今天,IP地址作为网络世界的“门牌号”,其重要性不言而喻。然而,面对动态IP与静态IP这两种截然不同的IP分配方式,许多用户往往感到困惑:它们究竟有何区别?又分别适用于哪些场景呢&…...

android13打基础: 保存用户免得下次重新登录逻辑
使用SP来做 创建LoginUser.kt // 登录用户需要Email data class LoginUser(val email: String,val password: String, )创建假数据FakeLoginUser.kt object FakeLoginUser {val fake_login_user_items arrayListOf(LoginUser(email "1690544550qq.com",password …...

Linux 4.4 内核源码的目录结构及其主要内容的介绍
以下是 Linux 4.4 内核源码的目录结构及其主要内容的介绍,适用于理解内核模块和驱动开发的基本框架: Linux 4.4 内核源码目录结构 目录作用与内容arch/平台架构相关代码每个子目录对应一种 CPU 架构(如 x86/、arm/、arm64/),包含硬件相关的启动逻辑、中断处理、内存管理等…...

手脑革命:拆解Manus AI如何用“执行智能体”重构生产力——中国团队突破硅谷未竟的技术深水区
第一章:Manus AI 的技术演进与行业背景 1.1 从工具到智能体:AI 技术的范式跃迁 人工智能的发展经历了从规则驱动(Rule-based)到统计学习(Statistical Learning),再到深度学习(Deep…...

Android 调用c++报错 exception of type std::bad_alloc: std::bad_alloc
一、报错信息 terminating with uncaught exception of type std::bad_alloc: std::bad_alloc 查了那部分报错c++代码 szGridSize因为文件太大,初始化溢出了 pEGM->pData = new float[szGridSize]; 解决办法 直接抛出异常,文件太大就失败吧 最后还增加一个日志输出,给…...

匿名GitHub链接使用教程(Anonymous GitHub)2025
Anonymous GitHub 1. 引言2. 准备3. 进入Anonymous GitHub官网4. 用GitHub登录匿名GitHub并授权5. 进入个人中心,然后点击• Anonymize Repo实例化6. 输入你的GitHub链接7. 填写匿名链接的基础信息8. 提交9. 实例化对应匿名GitHub链接10. 进入个人中心管理项目11. 查…...

【0基础跟AI学软考高项】成本管理
💰「成本管理」是什么? 一句话解释:像家庭装修控制预算,既要买得起好材料,又要避免超支吃泡面——成本管理就是精准算钱、合理花钱、动态盯钱,保证项目不破产! 🌋 真实案例…...

模型的原始输出为什么叫 logits
模型的原始输出为什么叫 logits flyfish 一、Logarithm(对数 log) 定义:对数是指数运算的逆运算,表示某个数在某个底数下的指数。 公式:若 b x a b^x a bxa,则 log b ( a ) x \log_b(a) x logb…...

[SAP MM] 查看物料主数据的物料类型
创建物料主数据时,必须为物料分配物料类型,如原材料或半成品 在标准系统中,物料类型ROH(原材料)的所有物料都要从外部采购,而类型为NLAG(非库存物料)的物料则可从外部采购也可在内部生产 ① 特殊物料类型:NLAG 该物料…...

风控模型算法面试题集结
特征处理 1. 特征工程的一般步骤什么?什么是特征迭代 特征工程一般包含: 数据获取,分析数据的可用性(覆盖率,准确率,获取容易程度)数据探索,分析数据业务含义,对特征有一个大致了解,同时进行数据质量校验,包含缺失值、异常值和一致性等;特征处理,包含数据处理和…...