Zookeeper【Curator客户端Java版】从0到1——万字学习笔记

目录

初识Zookeeper

Zookeeper作用

维护配置信息

分布式锁服务

集群管理

生产分布式唯一ID

Zookeeper的设计目标

Zookeeper 工作机制

数据模型

ZooKeeper 命令操作

服务端常用命令

 客户端常用命令

ZooKeeper JavaAPI操作

Curator 介绍

Curator API 常用操作

导入依赖

建立连接

创建节点

查询节点

修改节点

删除节点

Watch事件监听

概念

API演示 

分布式锁实现

分布式锁概述

ZooKeeper分布式锁原理 

​编辑

Curator实现分布式锁

API 

1. 配置

2. 可重入锁InterProcessMutex

3. 不可重入锁InterProcessSemaphoreMutex

4. 可重入读写锁InterProcessReadWriteLock

5. 联锁InterProcessMultiLock

6. 信号量InterProcessSemaphoreV2

7. 共享计数器

7.1. SharedCount

ZooKeeper 集群搭建

选举机制

选举过程

异常情况处理

集群中的角色定位

初识Zookeeper

  Zookeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调存储服务。

  • 维护配置信息

  • 分布式锁服务

  • 集群管理

  • 生成分布式唯一ID

Zookeeper作用

维护配置信息

  java编程经常会遇到配置项,比如数据库的urlschemauserpassword等。通常这些配置项我们会放置在配置文件中,再将配置文件放置在服务器上当需要更改配置项时,需要去服务器上修改对应的配置文件。

        但是随着分布式系统的兴起,由于许多服务都需要使用到该配置文件,因此有必须保证该配置服务的高可用性(highavailability)和各台服务器上配置数据的一致性。

        通常会将配置文件部署在一个集群上,然而一个集群动辄上千台服务器,此时如果再一台台服务器逐个修改配置文件那将是非常繁琐且危险的的操作,因此就需要一种服务能够高效快速且可靠地完成配置项的更改等操作,并能够保证各配置项在每台服务器上的数据一致性。

  zookeeper就可以提供这样一种服务,其使用Zab这种一致性协议来保证一致性。现在有很多开源项目使用zookeeper来维护配置,如在 hbase中,客户端就是连接一个 zookeeper,获得必要的 hbase集群的配置信息,然后才可以进一步操作。还有在开源的消息队列 kafka中,也便用zookeeper来维护 brokers的信息。在 alibaba开源的soa框架dubbo中也广泛的使用zookeeper管理一些配置来实现服务治理。

分布式锁服务

        一个集群是一个分布式系统,由多台服务器组成。为了提高并发度和可靠性,多台服务器上运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器挂掉后,释放锁并 fail over到其他的机器继续执行该服务

集群管理

        一个集群有时会因为各种软硬件故障或者网络故障,出现棊些服务器挂掉而被移除集群,而某些服务器加入到集群中的情况,zookeeper会将这些服务器加入/移出的情况通知给集群中的其他正常工作的服务器,以及时调整存储和计算等任务的分配和执行等。此外zookeeper还会对故障的服务器做出诊断并尝试修复。

生产分布式唯一ID

        在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_ increment属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_ Increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID


Zookeeper的设计目标

zooKeeper致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务

高性能

  • zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其用于以读为主的应用场景

高可用

  • zookeeper一般以集群的方式对外提供服务,一般3~5台机器就可以组成一个可用的 Zookeeper集群了,每台机器都会在内存中维护当前的服务器状态,井且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务

严格顺序访问

  • 对于来自客户端的每个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序


Zookeeper 工作机制

  • Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。也就是说 Zookeeper = 文件系统 + 通知机制。


数据模型


ZooKeeper 命令操作

本节主要基于命令行操作Zookeeper

服务端常用命令

 客户端常用命令


ZooKeeper JavaAPI操作

Curator 介绍

Curator API 常用操作

注意:如果你采用的是Zookeeper3.5及以上的版本,需要采用的是Curator4.0版本来作为其客户端,对于Zookeeper3.5以下的版本,Curator4.0也兼容,所以无脑4.0

导入依赖

<!--curator-->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.0.0</version>
</dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.0.0</version>
</dependency>

建立连接

/*** 建立连接*/@Beforepublic void testConnect() {/*** @param connectString       连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181"* @param sessionTimeoutMs    会话超时时间 单位ms* @param connectionTimeoutMs 连接超时时间 单位ms* @param retryPolicy         重试策略*//* //重试策略RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);//1.第一种方式CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181",60 * 1000, 15 * 1000, retryPolicy);*///重试策略RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
————————————————————————————————————————————————————————————————————————————————//2.第二种方式//CuratorFrameworkFactory.builder();client = CuratorFrameworkFactory.builder().connectString("192.168.149.135:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000).retryPolicy(retryPolicy).namespace("itheima") // 设置命名空间,后续创建的节点默认以此为根节点.build();//开启连接client.start();}

创建节点

题外小知识点:如果我们在Junit单元测试的时候,一个测试方法的需要调用到其他@Test方法来进行初始化 or 资源释放操作,可以通过@Before @After 注解来标注执行该@Test方法前先执行什么方法,后执行什么方法

@Before
public void connect(){建立连接的逻辑
}@Test
public void consum(){建立连接后执行的逻辑
}@After
public void release(){释放连接逻辑
}

上述代码,运行consum方法后,他的加载顺序是 connect() ——> consum() ——> release() 

   /*** 创建节点:create 持久 临时 顺序 数据* 1. 基本创建 :create().forPath("")* 2. 创建节点 带有数据:create().forPath("",data)* 3. 设置节点的类型:create().withMode().forPath("",data)* 4. 创建多级节点  /app1/p1 :create().creatingParentsIfNeeded().forPath("",data)*/@Testpublic void testCreate2() throws Exception {//1. 基本创建//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储String path = client.create().forPath("/app1");  // 这里是在根节点后的节点路径System.out.println(path);}@Testpublic void testCreate() throws Exception {//2. 创建节点 带有数据//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储String path = client.create().forPath("/app2", "hehe".getBytes());System.out.println(path);}@Testpublic void testCreate3() throws Exception {//3. 设置节点的类型//默认类型:持久化String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");System.out.println(path);}@Testpublic void testCreate4() throws Exception {//4. 创建多级节点  /app1/p1//creatingParentsIfNeeded():如果父节点不存在,则创建父节点String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");System.out.println(path);}

查询节点

/*** 查询节点:* 1. 查询数据:get: getData().forPath()* 2. 查询子节点: ls: getChildren().forPath()* 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()*/@Testpublic void testGet1() throws Exception {//1. 查询数据:getbyte[] data = client.getData().forPath("/app1");System.out.println(new String(data));}@Testpublic void testGet2() throws Exception {// 2. 查询子节点: lsList<String> path = client.getChildren().forPath("/"); // 查询整棵树System.out.println(path);}@Testpublic void testGet3() throws Exception {Stat status = new Stat();System.out.println(status);//3. 查询节点状态信息:ls -sclient.getData().storingStatIn(status).forPath("/app1");System.out.println(status);}

修改节点

    /*** 修改数据* 1. 基本修改数据:setData().forPath()* 2. 根据版本修改: setData().withVersion().forPath()* * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。** @throws Exception*/@Testpublic void testSet() throws Exception {client.setData().forPath("/app1", "itcast".getBytes());}@Testpublic void testSetForVersion() throws Exception {Stat status = new Stat();//3. 查询节点状态信息:ls -sclient.getData().storingStatIn(status).forPath("/app1");int version = status.getVersion();//查询出来的 3System.out.println(version);client.setData().withVersion(version).forPath("/app1", "hehe".getBytes());}

删除节点

 /*** 删除节点: delete deleteall* 1. 删除单个节点:delete().forPath("/app1");* 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");* 3. 必须成功的删除:为了防止网络抖动。本质就是重试。  client.delete().guaranteed().forPath("/app2");* 4. 回调:inBackground* @throws Exception*/@Testpublic void testDelete() throws Exception {// 1. 删除单个节点client.delete().forPath("/app1");}@Testpublic void testDelete2() throws Exception {//2. 删除带有子节点的节点client.delete().deletingChildrenIfNeeded().forPath("/app4");}@Testpublic void testDelete3() throws Exception {//3. 必须成功的删除client.delete().guaranteed().forPath("/app2");}@Testpublic void testDelete4() throws Exception {//4. 回调client.delete().guaranteed().inBackground(new BackgroundCallback(){@Overridepublic void processResult(CuratorFramework client, CuratorEvent event) throws Exception {System.out.println("我被删除了~");System.out.println(event);}}).forPath("/app1");}@Afterpublic void close() {if (client != null) {client.close();}}

Watch事件监听

概念

        Zookeeper的Watch事件监听是一种机制,用于在Zookeeper集群中监视观察节点状态的变化。当某个节点发生变化时(例如数据内容的更改、节点的创建或删除等),Zookeeper会通知与该节点相关联的应用程序,以便应用程序能够及时作出相应的处理。 Watch事件监听可以帮助应用程序实时感知和处理节点状态的变化,并在节点状态发生变化时触发相应的回调函数或事件处理机制。这种机制使得应用程序能够根据事件的发生而采取适当的行动,从而实现分布式系统中各个节点之间的协调和同步。

Watch事件监听可以类比一下 volatile 关键字修饰的变量的保证多线程可见性的操作

API演示 
 /*** 建立连接*/@Beforepublic void testConnect() {/*** @param connectString       连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181"* @param sessionTimeoutMs    会话超时时间 单位ms* @param connectionTimeoutMs 连接超时时间 单位ms* @param retryPolicy         重试策略*//* //重试策略RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);//1.第一种方式CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181",60 * 1000, 15 * 1000, retryPolicy);*///重试策略RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);//2.第二种方式//CuratorFrameworkFactory.builder();client = CuratorFrameworkFactory.builder().connectString("192.168.149.135:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000).retryPolicy(retryPolicy).namespace("itheima").build();//开启连接client.start();}@Afterpublic void close() {if (client != null) {client.close();}}/*** 演示 NodeCache:给指定一个节点注册监听器*/@Testpublic void testNodeCache() throws Exception {//1. 创建NodeCache对象final NodeCache nodeCache = new NodeCache(client,"/app1");//2. 注册监听nodeCache.getListenable().addListener(new NodeCacheListener() {@Overridepublic void nodeChanged() throws Exception {System.out.println("节点变化了~");//获取修改节点后的数据byte[] data = nodeCache.getCurrentData().getData();System.out.println(new String(data));}});//3. 开启监听.如果设置为true,则开启监听是,加载缓冲数据nodeCache.start(true);}/*** 演示 PathChildrenCache:监听某个节点的所有子节点们*/@Testpublic void testPathChildrenCache() throws Exception {//1.创建监听对象PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);//2. 绑定监听器pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {@Overridepublic void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {System.out.println("子节点变化了~");System.out.println(event);//监听子节点的数据变更,并且拿到变更后的数据//1.获取类型PathChildrenCacheEvent.Type type = event.getType();//2.判断类型是否是updateif(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){System.out.println("数据变了!!!");byte[] data = event.getData().getData();System.out.println(new String(data));}}});//3. 开启pathChildrenCache.start();}/*** 演示 TreeCache:监听某个节点自己和所有子节点们*/@Testpublic void testTreeCache() throws Exception {//1. 创建监听器TreeCache treeCache = new TreeCache(client,"/app2");//2. 注册监听treeCache.getListenable().addListener(new TreeCacheListener() {@Overridepublic void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {System.out.println("节点变化了");System.out.println(event);}});//3. 开启treeCache.start();}

分布式锁实现

分布式锁概述


ZooKeeper分布式锁原理 

基于节点顺序依次获取锁可见,Zookeeper实现的锁是公平锁


Curator实现分布式锁

API 

在Curator中有五种锁方案:

  • InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
  • InterProcessMutex:分布式可重入排它锁
  • InterProcessReadWriteLock:分布式读写锁
  • InterProcessMultiLock:将多个锁作为单个实体管理的容器
  • InterProcessSemaphoreV2:共享信号量

1. 配置

添加curator客户端配置:

@Configuration
public class CuratorConfig {@Beanpublic CuratorFramework curatorFramework(){// 重试策略,这里使用的是指数补偿重试策略,重试3次,初始重试间隔1000ms,每次重试之后重试间隔递增。RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);// 初始化Curator客户端:指定链接信息 及 重试策略CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.111:2181", retry);client.start(); // 开始链接,如果不调用该方法,很多方法无法工作return client;}
}

2. 可重入锁InterProcessMutex

ReentrantJDKReentrantLock类似, 意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。它是由类InterProcessMutex来实现。

// 常用构造方法
public InterProcessMutex(CuratorFramework client, String path)
// 获取锁
public void acquire();
// 带超时时间的可重入锁
public boolean acquire(long time, TimeUnit unit);
// 释放锁
public void release();

注意:如想重入,则需要使用同一个InterProcessMutex对象。

3. 不可重入锁InterProcessSemaphoreMutex

具体实现:InterProcessSemaphoreMutexInterProcessMutex调用方法类似,区别在于该锁是不可重入的,在同一个线程中不可重入。

public InterProcessSemaphoreMutex(CuratorFramework client, String path);
public void acquire();
public boolean acquire(long time, TimeUnit unit);
public void release();

4. 可重入读写锁InterProcessReadWriteLock

类似JDKReentrantReadWriteLock。一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁。这也意味着写锁可以降级成读锁。从读锁升级成写锁是不成的。主要实现类InterProcessReadWriteLock

// 构造方法
public InterProcessReadWriteLock(CuratorFramework client, String basePath);
// 获取读锁对象
InterProcessMutex readLock();
// 获取写锁对象
InterProcessMutex writeLock();

注意:写锁在释放之前会一直阻塞请求线程,而读锁不会

5. 联锁InterProcessMultiLock

Multi Shared Lock是一个锁的容器。当调用acquire, 所有的锁都会被acquire,如果请求失败,所有的锁都会被release。同样调用release时所有的锁都被release(失败被忽略)。基本上,它就是组锁的代表,在它上面的请求释放操作都会传递给它包含的所有的锁。实现类InterProcessMultiLock

// 构造函数需要包含的锁的集合,或者一组ZooKeeper的path
public InterProcessMultiLock(List<InterProcessLock> locks);
public InterProcessMultiLock(CuratorFramework client, List<String> paths);// 获取锁
public void acquire();
public boolean acquire(long time, TimeUnit unit);// 释放锁
public synchronized void release();

6. 信号量InterProcessSemaphoreV2

一个计数的信号量类似JDKSemaphoreJDKSemaphore维护的一组许可(permits),而Cubator中称之为租约(Lease)。注意,所有的实例必须使用相同的numberOfLeases值。调用acquire会返回一个租约对象。客户端必须在finallyclose这些租约对象,否则这些租约会丢失掉。但是,如果客户端session由于某种原因比如crash丢掉, 那么这些客户端持有的租约会自动close, 这样其它客户端可以继续使用这些租约。主要实现类InterProcessSemaphoreV2

// 构造方法
public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases);// 注意一次你可以请求多个租约,如果Semaphore当前的租约不够,则请求线程会被阻塞。
// 同时还提供了超时的重载方法
public Lease acquire();
public Collection<Lease> acquire(int qty);
public Lease acquire(long time, TimeUnit unit);
public Collection<Lease> acquire(int qty, long time, TimeUnit unit)// 租约还可以通过下面的方式返还
public void returnAll(Collection<Lease> leases);
public void returnLease(Lease lease);

7. 共享计数器

利用ZooKeeper可以实现一个集群共享的计数器。只要使用相同的path就可以得到最新的计数器值, 这是由ZooKeeper的一致性保证的。Curator有两个计数器, 一个是用int来计数,一个用long来计数。

7.1. SharedCount

共享计数器SharedCount相关方法如下:

// 构造方法
public SharedCount(CuratorFramework client, String path, int seedValue);
// 获取共享计数的值
public int getCount();
// 设置共享计数的值
public void setCount(int newCount) throws Exception;
// 当版本号没有变化时,才会更新共享变量的值
public boolean  trySetCount(VersionedValue<Integer> previous, int newCount);
// 通过监听器监听共享计数的变化
public void addListener(SharedCountListener listener);
public void addListener(final SharedCountListener listener, Executor executor);
// 共享计数在使用之前必须开启
public void start() throws Exception;
// 关闭共享计数
public void close() throws IOException;

ZooKeeper 集群搭建

选举机制

        ZooKeeper的选举机制是指在ZooKeeper集群中选择一个节点作为Leader节点的过程。选举机制确保在集群中只有一个节点充当Leader,而其他节点作为Followers来同步数据。

        全局唯一标识:每个节点在加入ZooKeeper集群时,会被分配一个全局唯一标识(Zxid),用于标识节点的唯一性和顺序性。这个标识由Leader节点负责分配。

  • Server,选举状态
  • LOOKING,竞选状态。
  • FOLLOWING,随从状态,同步leader状态,参与投票。
  • LEADING,领导者状态。

选举过程

数据同步

服务器1

服务器1启动,发起投票,投票格式为(Zxid,ServerID),投出的票为(0,1),此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING。

服务器2

服务器2启动,发起投票,投出的票为(0,2),服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ServerID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING。

服务器3

服务器3启动,发起投票,投出的票为(0,3),此时服务器1和服务器2发现服务器3的ServerID比自己目前投票推举的(服务器2)大,更改选票为推举服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING。

服务器4

服务器4启动,发起投票。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING。

服务器5

服务器5启动,同4一样当小弟。

注:Zookeeper集群的中每个服务器的数据都是一致的,除非网络波动极大,才会导致Zxid不一致,故而每个服务器的Zxid一致。如果真的遇到Zxid不一致,那么最大的Zxid的服务器会自动当选leader,如果相当则按照上述规则进行选举。

四、总结

本文介绍了Zookeeper的选举机制,需要注意一下几点:

  • 同一集群中Zxid是一致的,除非网络很差。

  • Zookeeper选举成功遵循半数机制,即选票成功超过50%就行。

  • leader故障后会重新按照规则进行选举,优先选择Zxid大的。

异常情况处理

在选举过程中,可能会发生以下异常情况:

  • Leader节点失去连接或崩溃:当Leader节点失去连接或崩溃时,其他节点无法接收到Leader的心跳消息,会开始新一轮的选举,选举出新的Leader。
  • 拥有最新数据的节点脱离集群:如果一个拥有最新数据的节点脱离了集群,其他节点会将其标记为不可达,并继续选举新的Leader。

集群中的角色定位

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

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

相关文章

PTE-精听学习(一)

目录 SST SST每一题都是单独计时 MMA 切换题目的时候&#xff0c;总是会迷茫 deduct 出现关键词之后&#xff0c;才开始精听 没有人管你 &#xff0c;绝对是要为后方留出更多的时间 &#xff0c;选多一个错的&#xff0c;要倒扣分 特征 1.paraphrase 2.循序出现 …

JDK 21的新特性总结和分析

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Windows服务器安装php+mysql环境的经验分享

php mysql环境 下载IIS Php Mysql环境集成包,集成包下载地址: 1、Windows Server 2008 一键安装Web环境包 x64 适用64位操作系统服务器:下载地址:链接: https://pan.baidu.com/s/1MMOOLGll4D7Eb5tBrdTQZw 提取码: btnx 2、Windows Server 2008 一键安装Web环境包 32 适…

视频推拉流/直播点播平台EasyDSS分享的链接提示“无信号”,该如何解决?

视频直播点播平台EasyDSS可支持用户自行上传视频文件&#xff0c;也可将上传的点播文件作为虚拟直播进行播放。平台能支持多屏播放&#xff0c;可兼容Windows、Android、iOS、Mac等操作系统&#xff0c;还能支持CDN转推&#xff0c;具备较强的可拓展性与灵活性。 为给用户提供更…

AcWing算法提高课-5.6.2青蛙的约会

宣传一下 算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 两只青蛙在网上相识了&#xff0c;它们聊得很开心&#xff0c;于是觉得很有必要见一面。 它们很高兴地发现它们住在同一条纬度线上&#xff0c;于是它们约定各自朝西跳&#xff0c;直到…

数据库设计与前端框架

数据库设计与前端框架 学习目标&#xff1a; 理解多租户的数据库设计方案 熟练使用PowerDesigner构建数据库模型理解前端工程的基本架构和执行流程 完成前端工程企业模块开发 多租户SaaS平台的数据库方案 多租户是什么 多租户技术&#xff08;Multi-TenancyTechnology&a…

PCI设备与UIO驱动

随着网络的高速发展,对网络的性能要求也越来越高,DPDK框架是目前的一种加速网络IO的解决方案之一,也是最为流行的一套方案。DPDK通过bypass内核协议栈与内核驱动,将驱动的工作从内核态移至用户态,并利用polling mode的线程工作模式加速网络I/O使得网络IO性能出现大幅度的增…

xml文件报错 ORA-00907: 缺失右括号

原来的sql 更改之后 加一个select * from &#xff08;&#xff09;

一文理解登录鉴权(Cookie、Session、Jwt、CAS、SSO)

1 前言 登录鉴权是任何一个网站都无法绕开的部分&#xff0c;当系统要正式上线前都会要求接入统一登陆系统&#xff0c;一方面能够让网站只允许合法的用户访问&#xff0c;另一方面&#xff0c;当用户在网站上进行操作时也需要识别操作的用户&#xff0c;用作后期的操作审计。…

嵌入式开发学习之STM32F407点亮LED及J-Link下载(二)

嵌入式开发学习之STM32F407点亮LED及J-Link下载&#xff08;二&#xff09; 开发涉及工具控制端口配置端口的设定与确认端口配置方法实现点亮LED程序下载与仿真 有工程实例&#xff0c;链接在最底部。 开发涉及工具 开发环境&#xff08;IDE&#xff09;&#xff1a;IAR-ARM8…

力扣每日一题46:全排列

题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; …

Tuxera NTFS2024最新永久版下载和安装

要使用Tuxera NTFS for Mac&#xff0c;你需要先下载和安装Tuxera NTFS for Mac驱动器&#xff0c;然后按照以下步骤操作&#xff1a; 1、下载和安装Tuxera NTFS for Mac 免费下载Tuxera NTFS for Mac驱动器的最新版本。下载完成后&#xff0c;双击DMG文件并按照提示安装即可…

云计算:shell脚本

shell脚本&#xff0c;会极大减少重复性工作&#xff0c;缩短很大时间。 脚本每个人都可以不一样&#xff0c;只要实现就可以。 注意&#xff1a;要多思考&#xff0c;把思路锻炼好。以后就可以写各种程序。 shell语言 学完shell之后&#xff0c;对Linux理解更深刻&#xff…

IDEA 修改插件安装位置

不说假话&#xff0c;一定要看到最后&#xff0c;不然你以为我为什么要自己总结&#xff01;&#xff01;&#xff01; IDEA 修改插件安装位置 前言步骤 前言 IDEA 默认的配置文件均安装在C盘&#xff0c;使用时间长会生成很多文件&#xff0c;这些文件会占用挤兑C盘空间&…

如何使用前端模块化开发?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

vue中引入jquery解决跨域问题

1、vue 工程文件 package.json 中 引入 “dependencies”: { “jquery”:“^2.2.4” }, 2、控制台执行命令&#xff0c;当前工程文件夹下 cnpm install 3、修改的vue文件中 加入 import $ from ‘jquery’ 4、调用 ajax请求 $.ajax({url:http://192.168.0.10:9099/strutsJspA…

Vue Router - 路由的使用、两种切换方式、两种传参方式、嵌套方式

目录 一、Vue Router 1.1、下载 1.2、基本使用 a&#xff09;引入 vue-router.js&#xff08;注意&#xff1a;要在 Vue.js 之后引入&#xff09;. b&#xff09;创建好路由规则 c&#xff09;注册到 Vue 实例中 d&#xff09;展示路由组件 1.3、切换路由的两种方式 1.…

会议OA小程序首页布局

目录 一. Flex布局介绍 1.1 什么是Flex布局 1.2 基本概念 1.3 Flex属性 二. 会议OA首页轮播图的实现 配置 Mock工具 swiper 效果展示 三. 会议OA首页会议信息布局 index.js index.wxml index.wxss 首页整体效果展示 一. Flex布局介绍 布局的传统解决方案&#x…

简单的对称加密

异或 异或算法的好处便是数A和数B异或后&#xff0c;把结果再和数A异或便可得到B&#xff0c;或者和数B异或可重新得到数据A。利用异或的这个特性可简单实现数据的加密和解密算法。 恺撒密码 恺撒密码的替换方法是通过排列明文和密文字母表&#xff0c;密文字母表示通过将明…

MATLAB | 对随机信号进行统计分析,绘制频次直方图、频率分布图,与理论概率密度进行比较

一、问题描述 对于一个随机信号&#xff0c;我们可以通过统计手段&#xff0c;得到其的频次分布图&#xff08;直方图&#xff09;&#xff0c;并由此计算出它的频率分布图。当观察次数区域无穷大时&#xff0c;频率分布图近似于概率密度函数。 下面我们以稳定分布的随机变量为…