分布式锁:Mysql实现,Redis实现,Zookeeper实现

news2024/12/24 9:57:35

目录

前置知识

Mysql实现分布式锁

1.get_lock函数

 Java代码实现:

2.for update尾缀

 Java代码实现:

3.自己定义锁表 

Java代码实现:

4.时间戳列实现乐观锁

Java代码实现:

Redis实现分布式锁

Zookeeper实现分布式锁:

Java代码实现:


前置知识

想要了解更多线程和锁相关的知识,可以看下面这个文章,了解线程和锁知识有助于理解本文内容

JAVA:创建线程,线程安全,线程锁,线程的生命周期,线程池icon-default.png?t=N7T8https://blog.csdn.net/dxh9231028/article/details/140676316?spm=1001.2014.3001.5501

Mysql实现分布式锁

Mysql实现分布式锁有四种方式,分别是get_lock函数,for update尾缀,定义一个锁表,手动实现锁逻辑实现悲观锁,以及通过定义时间戳的锁表实现乐观锁。

1.get_lock函数

get_lock函数是mysql中内置的函数,可以通过select get_lock(lockname,timeout)来实现锁的功能,其中lockname是锁的名字,timeout为获取锁的超时时间,当想要限制多个线程无法同时操作同一个资源时,只需要在这些线程在执行sql之前,使用相同的lockname先执行select get_lock(lockname,timeout),第一个执行线程,mysql会创建一个lockname和当前这个会话的id的对应关系,并返回1,而其他线程执行select get_lock(lockname,timeout)时则会返回0,线程在执行完对应的操作后,需要显示的调用select release_lock(lockname)来释放锁。

该方法有一个非常致命的缺陷,当一个线程由于某些原因突然失联,没有来的及执行release_lock来释放锁,那么该资源将处于长时间的不能访问状态。

 Java代码实现:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MySQLDistributedLock {

    private Connection connection;
    //构造函数,new的时候创建连接
    public MySQLDistributedLock(String url, String user, String password) throws Exception {
        connection = DriverManager.getConnection(url, user, password);
    }

    // 获取锁
    public boolean acquireLock(String lockName, int timeout) throws Exception {
        //创建sql模板
        String sql = "SELECT GET_LOCK(?, ?)";
        //准备连接,sql模板预解析
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            //传入锁名
            stmt.setString(1, lockName);
            stmt.setInt(2, timeout);
            //执行sql
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    //如果结果为1则返回true,证明获取锁成功
                    return rs.getInt(1) == 1;
                }
            }
        }catch (Exception e){
            //发生异常释放锁
            releaseLock(lockName);
        }
        return false;
    }

    // 释放锁
    public boolean releaseLock(String lockName) throws Exception {
        String sql = "SELECT RELEASE_LOCK(?)";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setString(1, lockName);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return rs.getInt(1) == 1;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        MySQLDistributedLock lock = null;
        //定义锁名
        String lockName = "my_distributed_lock";
        try {
            //创建连接
            lock = new MySQLDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");
            //获取锁
            if (lock.acquireLock(lockName, 10)) {
                System.out.println("Lock acquired!");

                // 执行需要同步的业务逻辑
                Thread.sleep(5000);
                //释放锁
                lock.releaseLock(lockName);
                System.out.println("Lock released!");
            } else {
                System.out.println("Failed to acquire lock.");
            }
        } catch (Exception e) {
            //发生异常释放锁
            try{
                lock.releaseLock(lockName);
            }catch(Exception ex){
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }
}

2.for update尾缀

在执行mysql语句时,在后面添加上for update,那么当前事务操作的行,在当前事务执行完之前,不允许其他事务来进行修改操作,或读操作但是sql语句后面也拼接了for update,通过这个机制,也可以实现分布式锁。

我们可以通过创建一个锁表,多个线程通过select * where lockname = lockname for update,通过这种方式,只有一个线程可以成功获取到锁表中的锁。

相较于get_lock方法,for update当会话意外关闭时,事务会马上回滚,不会造成get_lock方法那样长时间的无法获取锁,并且for update不仅可以获取锁的方法来实现多个进程不操作同一资源,也可以直接操作目标资源,不过没有定义锁表获取锁更灵活。

 Java代码实现:

/*
    创建如下锁表
    CREATE TABLE distributed_lock (
        lock_name VARCHAR(255) NOT NULL PRIMARY KEY,
        lock_value INT,
        lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB;
*/



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MySQLInnoDBDistributedLock {

    private Connection connection;

    public MySQLInnoDBDistributedLock(String url, String user, String password) throws Exception {
        connection = DriverManager.getConnection(url, user, password);
        connection.setAutoCommit(false); // 开启事务管理
    }

    // 获取锁
    public boolean acquireLock(String lockName) throws Exception {
        String sql = "SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setString(1, lockName);
            try (ResultSet rs = stmt.executeQuery()) {
                return rs.next()
            }
        }
    }

    // 释放锁
    public void releaseLock() throws Exception {
        connection.commit(); // 提交事务释放锁
    }
    //关闭数据库连接
    public void close() throws Exception {
        if (connection != null) {
            connection.close();
        }
    }

    public static void main(String[] args) {
        try {
            MySQLInnoDBDistributedLock lock = new MySQLInnoDBDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");

            String lockName = "my_distributed_lock";

            if (lock.acquireLock(lockName)) {
                System.out.println("Lock acquired!");

                // 执行需要同步的业务逻辑...

                lock.releaseLock();
                System.out.println("Lock released!");
            } else {
                System.out.println("Failed to acquire lock.");
            }

            lock.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.自己定义锁表 

通过自己定义一个锁表,并且定义一个不可重复的列,然后多个线程通过插入同一个数据来判断是否获取到锁,而释放锁的操作则是删除这条数据,通过自己实现这一过程,可以实现更灵活的分布式锁机制。

Java代码实现:

/*
    CREATE TABLE distributed_lock (
        lock_name VARCHAR(255) NOT NULL PRIMARY KEY,
        locked_by VARCHAR(255),
        lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
*/

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class MySQLTableDistributedLock {

    private Connection connection;

    public MySQLTableDistributedLock(String url, String user, String password) throws Exception {
        connection = DriverManager.getConnection(url, user, password);
    }

    // 获取锁
    public boolean acquireLock(String lockName, String clientId) throws Exception {
        String sql = "INSERT INTO distributed_lock (lock_name, locked_by) VALUES (?, ?)";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setString(1, lockName);
            stmt.setString(2, clientId);
            return stmt.executeUpdate() == 1;
        } catch (Exception e) {
            // 插入失败意味着锁已被其他客户端持有
            return false;
        }
    }

    // 释放锁
    public boolean releaseLock(String lockName, String clientId) throws Exception {
        String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND locked_by = ?";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setString(1, lockName);
            stmt.setString(2, clientId);
            return stmt.executeUpdate() == 1;
        }
    }

    public static void main(String[] args) {
        try {
            MySQLTableDistributedLock lock = new MySQLTableDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");

            String lockName = "my_distributed_lock";
            String clientId = "client_1";

            if (lock.acquireLock(lockName, clientId)) {
                System.out.println("Lock acquired!");

                // 执行需要同步的业务逻辑
                Thread.sleep(5000);

                lock.releaseLock(lockName, clientId);
                System.out.println("Lock released!");
            } else {
                System.out.println("Failed to acquire lock.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.时间戳列实现乐观锁

通过在需要限制多个线程同时访问的资源表中加入一个时间戳列保存最后的编辑时间,在执行修改操作之前先获取目标的时间戳,并且执行操作时在原有的条件的基础上添加一个时间戳等于之前获取的时间戳的条件,并且在修改后更新时间戳。通过这种方式,可以验证当前操作的数据是否在获取之前是否被篡改过,如果篡改过则会导致无法选择目标资源。

乐观锁在低并发情况下性能更好,但在高并发的情况下可能会导致某些线程可以更快的操作成功,有些线程则长时间无法操作成功,操作是否成功具有随机性。

Java代码实现:

/*
    CREATE TABLE users (
        id INT PRIMARY KEY,
        username VARCHAR(100),
        email VARCHAR(100),
        last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );
*/

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;

public class OptimisticLockingExample {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test";
    private static final String JDBC_USER = "root";
    private static final String JDBC_PASSWORD = "password";

    public static void main(String[] args) {
        Connection connection = null;

        try {
            // 1. 建立数据库连接
            connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);

            // 2. 读取记录及其时间戳
            int userId = 1;
            UserRecord userRecord = getUserRecord(connection, userId);
            if (userRecord == null) {
                System.out.println("User not found.");
                return;
            }

            System.out.println("Original User Record: " + userRecord);

            // 3. 尝试使用乐观锁更新记录
            boolean updateSuccess = updateUserEmail(connection, userId, "newemail@example.com", userRecord.getLastModified());

            if (updateSuccess) {
                System.out.println("Update successful.");
            } else {
                System.out.println("Update failed due to concurrent modification. Please retry.");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭数据库连接
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 获取用户记录及其时间戳
    private static UserRecord getUserRecord(Connection connection, int userId) throws SQLException {
        String selectSql = "SELECT id, username, email, last_modified FROM users WHERE id = ?";
        try (PreparedStatement stmt = connection.prepareStatement(selectSql)) {
            stmt.setInt(1, userId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    int id = rs.getInt("id");
                    String username = rs.getString("username");
                    String email = rs.getString("email");
                    Timestamp lastModified = rs.getTimestamp("last_modified");

                    return new UserRecord(id, username, email, lastModified);
                }
            }
        }
        return null;
    }

    // 使用乐观锁更新用户的邮箱地址
    private static boolean updateUserEmail(Connection connection, int userId, String newEmail, Timestamp lastModified) throws SQLException {
        String updateSql = "UPDATE users SET email = ?, last_modified = CURRENT_TIMESTAMP WHERE id = ? AND last_modified = ?";
        try (PreparedStatement stmt = connection.prepareStatement(updateSql)) {
            stmt.setString(1, newEmail);
            stmt.setInt(2, userId);
            stmt.setTimestamp(3, lastModified);

            int rowsAffected = stmt.executeUpdate();
            return rowsAffected > 0;
        }
    }

    // 简单的 UserRecord 类,用于存储用户记录
    private static class UserRecord {
        private int id;
        private String username;
        private String email;
        private Timestamp lastModified;

        public UserRecord(int id, String username, String email, Timestamp lastModified) {
            this.id = id;
            this.username = username;
            this.email = email;
            this.lastModified = lastModified;
        }

        public int getId() {
            return id;
        }

        public String getUsername() {
            return username;
        }

        public String getEmail() {
            return email;
        }

        public Timestamp getLastModified() {
            return lastModified;
        }

        @Override
        public String toString() {
            return "UserRecord{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", email='" + email + '\'' +
                    ", lastModified=" + lastModified +
                    '}';
        }
    }
}

Redis实现分布式锁

redis通过其内置的setnx指令实现,setnx指令在目标不存在会创建成功,存在时创建失败,这样可以让多个线程创建同一个key来实现分布式锁的功能,创建成功则为获取到锁。创建完key后还要设置过期时间,防止会话意外关闭导致无法释放锁。

redis大多以集群模式出现,当多个多个主机接收到不同的写操作时可能会造成同时写入成功,对此redis官方提出了redlock算法,其规定在获取锁时需要对每个主机都下发一条创建指令,只有当超过半数主机认为创建成功时,才会成功获取锁,不过这个算法并没有被redis实现,但是可以使用一些第三方驱动,或者手动实现也不算复杂,以下是一种实现方式:

import redis.clients.jedis.Jedis;

import java.util.UUID;

public class Redlock {
    //保存所有redis实例。
    private Jedis[] redisInstances;
    // 锁的过期时间 (10秒)
    private long lockTimeout = 10000; 
    // 假设有五个实例,那么则成功数则需要达到3个
    private int quorum = 3; 
    //构造函数,传入redis实例
    public Redlock(Jedis... redisInstances) {
        this.redisInstances = redisInstances;
    }
    //尝试获取传入的锁,也就是创建传入的key
    public String tryLock(String lockKey) {
        //获取一个唯一id作为value
        String lockValue = UUID.randomUUID().toString();
        //获取当前时间时间戳
        long startTime = System.currentTimeMillis();
        //记录成功的数量
        int lockCount = 0;
        //开始在各个redis实例上创建key。
        for (Jedis jedis : redisInstances) {
            if (jedis.set(lockKey, lockValue, "NX", "PX", lockTimeout) != null) {
                lockCount++;
            }
        }

        // 判断是否获取锁成功(成功数量大于实力数量的一般,并且整个过程没有超过超时时间)
        long elapsedTime = System.currentTimeMillis() - startTime;
        if (lockCount >= quorum && elapsedTime < lockTimeout) {
            return lockValue;
        } else {
            // 获取失败,释放已获取到的锁
            for (Jedis jedis : redisInstances) {
            // 只删除value是自己的UUID的key,也就是只删除自己创建的key
                if (lockValue.equals(jedis.get(lockKey))) {
                    jedis.del(lockKey);
                }
            }
            return null;
        }
    }

    public void unlock(String lockKey, String lockValue) {
        for (Jedis jedis : redisInstances) {
            if (lockValue.equals(jedis.get(lockKey))) {
                jedis.del(lockKey);
            }
        }
    }

    public static void main(String[] args) {
        // 假设你有5个独立的 Redis 实例,实际的IP和端口是在Nacos或Zookeeper中获取
        Jedis redis1 = new Jedis("localhost", 6379);
        Jedis redis2 = new Jedis("localhost", 6380);
        Jedis redis3 = new Jedis("localhost", 6381);
        Jedis redis4 = new Jedis("localhost", 6382);
        Jedis redis5 = new Jedis("localhost", 6383);

        Redlock redlock = new Redlock(redis1, redis2, redis3, redis4, redis5);

        String lockKey = "myLock";
        String lockValue = redlock.tryLock(lockKey);

        if (lockValue != null) {
            System.out.println("Lock acquired!");

            // 执行需要保护的操作

            redlock.unlock(lockKey, lockValue);
            System.out.println("Lock released!");
        } else {
            System.out.println("Failed to acquire lock.");
        }
    }
}

Zookeeper实现分布式锁:

zookeeper实现分布式锁有着绝对的优势,因为其四种节点类型中有一种节点类型是临时有序的节点。在创建相同key的有序节点时,创建并不会失败,而是会在key后面加上一串数字,这个数字是递增的,而临时节点的特性是在会话关闭时临时节点会删除这个key,这就使zookeeper在实现分布式锁时可以创建临时有序节点,客户端只需要判断自己创建的key后面的序号是不是其中最小的即可,这样不仅可以做到有序的获取锁,还能让会话意外断开时自动删除key,释放锁。

实际操作中可以创建一个/lock目录,然后所有进程都创建/lock/lockname,并且在内部判断lockname后面的数字是不是最小的,如果是最小的则开始操作目标资源,操作完毕后删除自己的key。了解zookeeper知识,可以看下面这篇文章:

Zookeeper使用快速入门:基础命令,wacth监控,权限控制icon-default.png?t=N7T8https://blog.csdn.net/dxh9231028/article/details/141105185?spm=1001.2014.3001.5501

在客户端正常断开会话前仍需手动释放,因为zookeeper判断会话断开需要时间,所以这个特性只能防止会话意外断开时锁长时间无法释放的情况,不能作为锁释放的正常途径。

Java代码实现:

import org.apache.zookeeper.*;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs.Ids;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZookeeperDistributedLock {
    //zookeeper的ip,实际项目中写在配置文件中
    private static final String ZK_ADDRESS = "localhost:2181";
    //锁的根目录,在这个目录下创建子节点锁
    private static final String LOCK_PATH = "/locks";
    //要创建的锁的路径
    private static final String LOCK_NODE_PREFIX = LOCK_PATH + "/lockname";

    private ZooKeeper zooKeeper;
    private String currentNode;
    private String lockNode;
    
    public static void main(String[] args) throws Exception {
        ZookeeperDistributedLock lock = new ZookeeperDistributedLock();
        lock.connect();
        try {
            lock.acquireLock();
            System.out.println("Lock acquired!");
            
            // 执行需要保护的操作
            Thread.sleep(5000); // 模拟操作
            
        } finally {
            lock.releaseLock();
            System.out.println("Lock released!");
            lock.close();
        }
    }
    //连接zookeeper客户都安,并将连接赋予zookeeper变量
    public void connect() throws IOException {
        zooKeeper = new ZooKeeper(ZK_ADDRESS, 3000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (event.getType() == EventType.NodeDeleted && event.getPath().equals(lockNode)) {
                    synchronized (this) {
                        notify();
                    }
                }
            }
        });
    }
    //获取锁,也就是创建key的过程
    public void acquireLock() throws Exception {
        //检查父目录/lock是否存在,如果不存在则创建
        if (zooKeeper.exists(LOCK_PATH, false) == null) {
            zooKeeper.create(LOCK_PATH, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //在/lock下创建/lock/lockname
        currentNode = zooKeeper.create(LOCK_NODE_PREFIX, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        //循环判断当下最小的节点是不是本线程创建的
        while (true) {
            List<String> nodes = zooKeeper.getChildren(LOCK_PATH, false);
            Collections.sort(nodes);
            if (currentNode.endsWith(nodes.get(0))) {
                // This is the smallest node, thus the lock is acquired
                lockNode = currentNode;
                break;
            }

            // Listen for changes to the previous node
            String previousNode = findPreviousNode(nodes);
            if (previousNode != null) {
                String previousNodePath = LOCK_PATH + "/" + previousNode;
                synchronized (this) {
                    zooKeeper.exists(previousNodePath, true);
                    wait();
                }
            }
        }
    }

    private String findPreviousNode(List<String> nodes) {
        String myNode = currentNode.substring(LOCK_PATH.length() + 1);
        for (int i = nodes.size() - 1; i >= 0; i--) {
            String node = nodes.get(i);
            if (node.compareTo(myNode) < 0) {
                return node;
            }
        }
        return null;
    }
    //删除key,释放锁
    public void releaseLock() throws KeeperException, InterruptedException {
        zooKeeper.delete(currentNode, -1);
    }
    //关闭连接,释放资源
    public void close() throws InterruptedException {
        zooKeeper.close();
    }
}

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

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

相关文章

Oracle搭建一主两备dataguard环境的详细步骤

​ 上一篇文章介绍了Oracle一主两备的DG环境&#xff0c;如何进行switchover切换&#xff0c;也许你会问Oracle一主两备dataguard环境要怎么搭建&#xff0c;本篇文章将为你讲述一主两备dataguard详细搭建步骤。 环境说明 主机名IP地址db_unique_name数据库角色ora11g10.10.1…

驱动数智化升级,AI大模型准备好了吗?

大数据产业创新服务媒体 ——聚焦数据 改变商业 AI大模型的快速崛起&#xff0c;为企业带来了前所未有的变革机遇。从自然语言处理到图像识别&#xff0c;从精准营销到智能制造&#xff0c;AI大模型正逐步渗透到各行各业的核心业务中。然而&#xff0c;随着技术的不断演进&…

力扣刷题-循环队列

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 思路&#xff1a; 我们在这里采用的是用数组的形式实现循环链表&#xff0c;我认为这个用数组是更为简单的&#xff0c;我们只需要控制下标就可以实现循环链表的效果。具体实现代…

Python数据可视化案例——折线图

目录 json介绍&#xff1a; Pyecharts介绍 安装pyecharts包 构建一个基础的折线图 配置全局配置项 综合案例&#xff1a; 使用工具对数据进行查看 &#xff1a; 数据处理 json介绍&#xff1a; json是一种轻量级的数据交互格式&#xff0c;采用完全独立于编程语言的文…

2024 该学前端还是学后端?

2024 该学前端还是学后端&#xff1f; 现状分析pragmatic-drag-and-drop后端开发 现状分析 对于这个问题&#xff0c;个人作为Java后端开发者&#xff0c;那么当然是比较熟悉Java后端开发&#xff0c;从这么久的工作体验来说&#xff0c;因为个人也是比较熟悉Java后端&#xf…

【第19章】Spring Cloud之Gateway自定义Logback配置

文章目录 前言一、内置配置1. 关联依赖2. 内置配置 二、自定义配置1. 日志级别2. 彩色日志3. 自定义配置4. 增加打印语句5. 效果展示 总结 前言 网关层作为我们程序的主入口&#xff0c;有着至关重要的作用&#xff0c;下面我们通过自定义Logback配置增强网关层的日志输出&…

【实用工具】Stirling-PDF入门安装教程: 优质开源的PDF处理工具/编辑工具

文章目录 项目简介功能展示Page Operations 页面操作Conversion Operations 转换操作Security & Permissions 安全与权限Other Operations 其他业务 如何安装并使用Docker RunDocker Compose 项目简介 这是一款使用 Docker 的基于本地托管网络的强大 PDF 操作工具。它能让…

2024年翻译工具新风尚:实时翻译与精准度并进

语言交流的障碍随着全球化的不断深入日益成为连接不同文化和国家的挑战。然而&#xff0c;在科技日新月异的今天&#xff0c;类似谷歌翻译这样的工具正在高速发展这。这次我们来一起探讨深受用户喜欢的翻译工具有哪些。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pd…

贷齐乐系统最新版SQL注入(绕过WAF可union select跨表查询)

目录 标题&#xff1a;贷齐乐系统最新版SQL注入&#xff08;绕过WAF可union select跨表查询&#xff09; 内容&#xff1a; 一&#xff0c;环境部署 二&#xff0c;源码分析 三&#xff0c;sql注入 总结&#xff1a; [回到顶部]&#xff08;#article_top&#xff09; 一&am…

Linux使用学习笔记1到2 命令行与shell 基础运维命令

在学习使用ubuntu等各种喜他构建服务器的过程中遇到很多问题&#xff0c;意识到只是跟着网络的教程没办法管理好一个完整的应用部署和运行。遂开始学习linux基本知识&#xff0c;以应对服务器常见问题和软件的使用和维护。 shell 望文生义&#xff0c;大概意思是一个外壳&…

交错字符串[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定三个字符串s1、s2、s3&#xff0c;请你帮忙验证s3是否是由s1 和s2交错 组成的。 两个字符串s和t交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 ... sn t t1 t2 …

数据结构---单链表实现

单链表是什么 我的理解是“特殊的数组”&#xff0c;通过访问地址来连接起来 1怎么创建链表 ----通过结构体&#xff08;成员有存入数据的data和指向下一个节点的地址的指针&#xff08;结构体指针&#xff09;next 初始架构---DataType 对应存入数据类型&#xff0c;此处的N…

一款基于Java外卖配送系统,专为多商户入驻设计,包含用户端、商家端、配送端以及总管理后台(附源码)

前言 在当前的外卖配送市场中&#xff0c;软件系统的状态常常面临一些挑战&#xff0c;例如多商户管理复杂性、用户体验不一致、后端服务的稳定性和安全性等。这些痛点不仅影响了商户和用户的满意度&#xff0c;也限制了平台的扩展性和发展潜力。 为了解决这些现状&#xff0…

B站搜索建库架构优化实践

前言 搜索是B站的重要基础功能&#xff0c;需要对包括视频、评论、图文等海量的站内优质资源建立索引&#xff0c;处理来自用户每日数亿的检索请求。离线索引数据的正确、高效产出是搜索业务的基础。我们在这里分享搜索离线架构整体的改造实践&#xff1a;从周期长&#xff0c;…

【论文阅读】BoT-SORT: Robust Associations Multi-Pedestrian Tracking

题目&#xff1a;BoT-SORT: Robust Associations Multi-Pedestrian Tracking 作者&#xff1a;Nir Aharon* Roy Orfaig Ben-Zion Bobrovsky motivation: 作者来得很直接&#xff0c;就说他们用相机运动模型和优化卡尔曼做了个可以解决具有挑战的跟踪问题的算法:BOT-SORT;说他们…

工程数学线性代数(同济大学数学系)第六版(更新中)

第1章 行列式 2 全排列和对换 一、排列及其逆序数 全排列 1个逆序、逆序数 奇排列&#xff0c;偶排列 二、对换 对换&#xff1a;排列中任意两个元素对调 相邻对换&#xff1a;相邻两个元素对换 对换改变排列的奇偶性。 4 行列式的性质 5 行列式按行&#xff08;列&…

有趣的的rce漏洞复现分析

目录 无字母数字绕过正则表达式 解读代码 解题思路 异或 或 取反 无字母数字绕过正则表达式 首先我们依然是搭建环境&#xff08;环境依然是Ubuntu下部署&#xff0c;和之前的漏洞环境一样&#xff09; <?php error_reporting(0); highlight_file(__FILE__); $code…

<数据集>车间工人、安全帽、安全背心识别<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3465张 标注数量(xml文件个数)&#xff1a;3465 标注数量(txt文件个数)&#xff1a;3465 标注类别数&#xff1a;3 标注类别名称&#xff1a;[person, helmet, vest] 序号类别名称图片数框数1person346594732helm…

Android 13 GMS 内置壁纸

如图&#xff0c;原生系统上&#xff0c;设备上的壁纸 显示系统内置壁纸。如果没有添加内置壁纸&#xff0c;就显示默认的壁纸。点击进去就是预览页面 扩展下&#xff0c;默认壁纸在 frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png frameworks/b…

云开发微信小程序--即时聊天(单人聊天,多人聊天室)

云开发微信小程序–即时聊天 介绍&#xff1a;本小程序包含欢迎界面&#xff0c;注册&#xff0c;登录&#xff0c;一对一聊天&#xff0c;群聊&#xff0c;好友添加请求验证过程&#xff0c;修改好友备注以及删除好友&#xff0c;退出群聊&#xff0c;特殊角色卡片展示&#…