第四节 zookeeper集群与分布式锁

news2025/2/24 23:13:41

目录

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;
    }
​
    @Before
    public 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() {
            @Override
            public 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);
        }
    }
​
​
    //从集群中获取数据
    @Test
    public 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客户端连接
     */
    @Before
    public void Before(){

        /**
         * 参数1:服务器的IP和端口
         * 参数2:会话的超时时间
         * 参数3:会话的连接时间
         * 参数4:序列化方式
         */
        zkClient = new ZkClient(connectString, sessionTimeout, 1000 * 15, new SerializableSerializer());

    }

    @After
    public void after(){
        zkClient.close();
    }
}

2.3 常用API

  • 创建节点

    /**
     * 创建节点
     */
    @Test
    public 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){}
    }
  • 删除节点

    /**
     * 删除节点
     */
    @Test
    public void testDeleteNode(){
        // 删除没有子节点的节点
//        boolean b1 = zkClient.delete("/node2");
//        System.out.println("删除成功: " + b1);

        // 递归删除节点信息
        boolean b2 = zkClient.deleteRecursive("/node2");
        System.out.println("删除成功: " + b2);
    }
  • 查看节点的子节点

    /**
     * 查询节点的子节点
     */
    @Test
    public 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代码中使用的序列化方式不一致导致 因此要解决这个问题只需要保证序列化一致即可 都使用相同端操作即可

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

    /**
     * 获取数据以及当前节点状态信息
     */
    @Test
    public void testFindNodeDataAndStat(){
        Stat stat = new Stat();
        Object data = zkClient.readData("/node20000000004", stat);

        System.out.println(data);
        System.out.println(stat);
    }
  • 修改节点数据

    /**
     * 修改节点
     */
    @Test
    public void testUpdateNodeData(){

        zkClient.writeData("/node3","123456");
    }
  • 监听节点数据的变化

    /**
     * 监听节点数据
     */
    @Test
    public void testNodeChange(){

        zkClient.subscribeDataChanges("/node3", new IZkDataListener() {

            // 当节点的值在修改时,会自动调用这个方法
            @Override
            public void handleDataChange(String nodeName, Object result) throws Exception {
                System.out.println("节点名称: " + nodeName);
                System.out.println("节点数据: " + result);
            }

            // 当节点被删除时,会调用该方法
            @Override
            public void handleDataDeleted(String nodeName) throws Exception {
                System.out.println("节点名称: " + nodeName);
            }
        });

        while(true){}
    }
  • 监听节点目录的变化

    /**
     * 监听节点目录的变化
     */
    @Test
    public void testNodesChange(){

        zkClient.subscribeChildChanges("/node3", new IZkChildListener() {

            @Override
            public 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){}
    }
  • 判断某一个节点是否存在

    //判断节点是否存在
    @Test
    public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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.coloradmin.cn/o/1446210.html

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

相关文章

基于大语言模型的AI Agents

代理&#xff08;Agent&#xff09;指能自主感知环境并采取行动实现目标的智能体。基于大语言模型&#xff08;LLM&#xff09;的 AI Agent 利用 LLM 进行记忆检索、决策推理和行动顺序选择等&#xff0c;把Agent的智能程度提升到了新的高度。LLM驱动的Agent具体是怎么做的呢&a…

探索Redis特殊数据结构:Geospatial(地理位置)在实际中的应用

一、概述 Redis官方提供了多种数据类型&#xff0c;除了常见的String、Hash、List、Set、zSet之外&#xff0c;还包括Stream、Geospatial、Bitmaps、Bitfields、Probabilistic&#xff08;HyperLogLog、Bloom filter、Cuckoo filter、t-digest、Top-K、Count-min sketch、Confi…

「Linux」用户操作

root用户 su&#xff1a;切换账户 语法&#xff1a;su [–] [用户名] -&#xff1a;可选&#xff0c;表示是否在切换用户后加载环境变量&#xff0c;建议带上用户名&#xff1a;表示要切换的用户&#xff0c;省略时表示切换到root切换用户后&#xff0c;通过exit命令退回上一个…

HTTP基本概念-HTTP 是什么?

资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP 是什么? HTTP 是超文本传输协议&#xff0c;也就是HyperText Transfer Protocol。 能否详细解释「超文本传输协议」? HTTP 的名字「超文本协议传输」&#xff0c;它可以拆成三个部分: 超文本传输…

vue安装使用less,解决与webpack的冲突

第077个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使用&#xff0c;computed&a…

HeidiSQL安装配置(基于小皮面板(phpstudy))连接MySQL

下载资源 对于这款图形化工具&#xff0c;博主建议通过小皮面板&#xff08;phpstudy&#xff09;来下载即可&#xff0c;也是防止你下载到钓鱼软件&#xff0c;小皮面板&#xff08;phpstudy&#xff09;如果你不懂是什么&#xff0c;请看下面链接这篇博客 第二篇&#xff1a;…

EMC学习笔记(二十六)降低EMI的PCB设计指南(六)

降低EMI的PCB设计指南&#xff08;六&#xff09; 1.PCB布局1.1 带键盘和显示器的前置面板PCB在汽车和消费类应用中的应用1.2 敏感元器件的布局1.3 自动布线器 2.屏蔽2.1 工作原理2.2 屏蔽接地2.3 电缆屏蔽至旁路2.4 缝隙天线&#xff1a;冷却槽和缝隙 tips&#xff1a;资料主要…

蓝牙BLE学习-概述

1. 简介 1.1 蓝牙发展历程 蓝牙&#xff0c;直接来自于一位国王的名字--King Harald ‘Bluetooth Gromsson。这位国王因两件事留名于史&#xff0c;其一是在公园958年统一了丹麦和挪威&#xff0c;其二是在其死后&#xff0c;其牙齿呈现出暗蓝色的颜色&#xff0c;因而得名蓝牙…

OpenCV-36 多边形逼近与凸包

目录 一、多边形的逼近 二、凸包 一、多边形的逼近 findContours后的轮廓信息countours可能过于复杂不平滑&#xff0c;可以用approxPolyDP函数对该多边形曲线做适当近似&#xff0c;这就是轮廓的多边形逼近。 apporxPolyDP就是以多边形去逼近轮廓&#xff0c;采用的是Doug…

《Linux 简易速速上手小册》第3章: 文件系统与权限(2024 最新版)

文章目录 3.1 Linux 文件系统结构3.1.1 重点基础知识3.1.2 重点案例&#xff1a;设置一个 Web 服务器3.1.3 拓展案例 1&#xff1a;日志文件分析3.1.3 拓展案例 2&#xff1a;备份用户数据 3.2 理解文件权限3.2.1 重点基础知识3.2.2 重点案例&#xff1a;共享项目文件夹3.2.3 拓…

网络编程项目:电子辞典

项目要求&#xff1a; 登录注册功能&#xff0c;不能重复登录&#xff0c;重复注册。用户信息也存储在数据库中。单词查询功能历史记录功能&#xff0c;存储单词&#xff0c;意思&#xff0c;以及查询时间&#xff0c;存储在数据库基于TCP&#xff0c;支持多客户端连接&#x…

python安装cv2失败

问题:安装cv2包失败 解决方法&#xff1a; pip install opencv-python或在Anaconda中conda install opencv-python

二、DataX安装

DataX安装 一、简介二、系统要求三、部署 一、简介 官方地址&#xff1a;https://github.com/alibaba/DataX/blob/master/userGuid.md 二、系统要求 LinuxJDK(1.8以上&#xff0c;推荐1.8) Centos7.9的java1.8安装命令&#xff1a;yum install java-1.8.0-openjdk.x86_64 Py…

【Java程序设计】【C00254】基于Springboot的java学习平台(有论文)

基于Springboot的java学习平台&#xff08;有论文&#xff09;&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的学习平台 本系统分为系统功能模块、管理员功能模块、教师功能模块以及学生功能模块。 系统功能模块&#xff1a;在平台…

【国产MCU】-CH32V307-基本定时器(BCTM)

基本定时器(BCTM) 文章目录 基本定时器(BCTM)1、基本定时器(BCTM)介绍2、基本定时器驱动API介绍3、基本定时器使用实例CH32V307的基本定时器模块包含一个16 位可自动重装的定时器(TIM6和TIM7),用于计数和在更新新事件产生中断或DMA 请求。 本文将详细介绍如何使用CH32…

大话设计模式——1.模板方法模式(Template Method Pattern)

定义&#xff1a;定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 例子&#xff1a;比较重大的考试往往有A、B两套试卷&#xff0c;其中一套出现问题可以立马更换另一套。 定义基…

金融信贷风控系统设计

前言 近一年多以来在金融行业负责风控系统&#xff0c;根据自己工作中的经验&#xff0c;写下这篇文章。既是对自己在风控领域工作的总结&#xff0c;也是给刚入行和准备入行的朋友打个样&#xff0c;希望能有所帮助。 为什么要有风控系统 记得 2016 年信贷行业的发展形势还…

NAS如何成为生产力?使用绿联DX4600 Pro搭建图床并实现创作自由

NAS如何成为生产力&#xff1f;使用绿联DX4600 Pro搭建图床并实现创作自由 哈喽小伙伴们好&#xff0c;我是Stark-C~ 关注我的小伙伴都知道&#xff0c;我之前有分享过我的创作过程与工具&#xff0c;其中介绍了我个人其实一直都是使用Markdown的编辑器来进行图文创作的。 我…

多机多卡运行nccl-tests和channel获取

nccl-tests 环境1. 安装nccl2. 安装openmpi3. 单机测试4. 多机测试mpirun多机多进程多节点运行nccl-testschannel获取 环境 Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)cuda 11.8 cudnn 8nccl 2.15.1NVIDIA GeForce RTX 4090 *2 1. 安装nccl #查看cuda版本 nv…

shiny,一个好用的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个好用的 Python 库 - shiny。 Github地址&#xff1a;https://github.com/posit-dev/py-shiny Python Shiny 是一个用于创建交互式 Web 应用程序的开源库&#xff0c;它基于 Flask 和 React 技…