手写redis实现分布式锁详细教程,满足可续锁、可重入等分布式锁条件

news2024/9/20 12:25:16

前言

本文将讨论的做一个高并发场景下避不开的话题,即redis分布式锁。比如在淘宝 的秒杀场景、热点新闻和热搜排行榜等。可见分布式锁是一个程序员面向高级的一门必修课,下面请跟着本篇文章好好学习。

redis分布式锁有哪些面试题

1.Redis做分布式的时候需要注意什么问题?
2.你们公司自己实现的分布式锁是否用的setnx命令实现?这个是最合适的吗?你如何考虑分布式锁的可重入问题?
3.如果Redis是单点部署的,会带来什么问题?准备怎么解决单点问题呢?
Redis集群模式下,比如主从模式下,CAP方面有没有什么问题?

1.分布式锁是什么?

1.1 锁的种类介绍

锁的种类锁的概念
单机单机版同一个JVM虚拟机内,synchronized或者lock接口
分布式分布式多个不同的java虚拟机,单机的线程锁机制不再起作用了,资源类在不同的服务器之间共享了。

1.2 一个正经的分布式锁具有哪些刚需

在这里插入图片描述
独占性:任何时刻只能有且仅有一个线程持有
高可用:若redis集群环境下,不能因为某个节点挂了而出现获取锁或者释放锁失败。高并发请求下依旧能够保证良好使用。
防止死锁:杜绝死锁,必须有超时控制或者撤销操作,有个兜底终止跳出方案
不乱抢:防止张冠李戴,不能私下uolock别人的锁,只能自己加锁自己释放,自己约的锁自己要释放,可以设置过期时间,或者业务代码执行完毕以后删除对一个的锁。
可重入:同一个节点的同一个线程如果获得锁之后,他也可以再次获得这个锁。

1.3 redis分布式锁

setnx key values

1.4 java实现分布式锁的案例

先来个乞丐版的分布式锁,并没有遵循上面五大原则。然后慢慢进行优化,乞丐版分布锁案例如下代码所示:

public String sale() {
    String resMessgae = "";
    String key = "luojiaRedisLocak";
    String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();

    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
    // 抢不到的线程继续重试
    if (!flag) {
        // 线程休眠20毫秒,进行递归重试
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sale();
    } else {
        try {
            // 1 抢锁成功,查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory01");
            // 2 判断库存书否足够
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            // 3 扣减库存,每次减少一个库存
            if (inventoryNum > 0) {
                stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));
                resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;
                log.info(resMessgae);
            } else {
                resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;
                log.info(resMessgae);
            }
        } finally {
            stringRedisTemplate.delete(key);
        }
    }
    return resMessgae;
}

请看看以上代码有哪些问题?既没有删除过期时间 ,也没有判断redis获取的redis值进行删除,有可能删除错锁。如果进一步优化可以redis可以存一个流水号,业务代码执行完了以后,判断流水号是否相等,然后进行删除。可重入问题可以通过递归实现重试,但是依旧有问题:手工设置5000个线程来抢占锁,压测OK,但是容易导致StackOverflowError,在高并发不推荐使用,需要进一步完善。改进获取重试方法代码如下所示:

public String sale() {
    String resMessgae = "";
    String key = "luojiaRedisLocak";
    // 标记线程id,知道使哪个线程在执行
    String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();

    // 不用递归了,高并发容易出错,我们用自旋代替递归方法重试调用;也不用if,用while代替
    while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)) {
        // 线程休眠20毫秒,进行递归重试
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
    }
    try {
        // 1 抢锁成功,查询库存信息
        String result = stringRedisTemplate.opsForValue().get("inventory01");
        // 2 判断库存书否足够
        Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
        // 3 扣减库存,每次减少一个库存
        if (inventoryNum > 0) {
            stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));
            resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        } else {
            resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        }
    } finally {
        stringRedisTemplate.delete(key);
    }
    return resMessgae;
}

为了防止出现死锁,需要给锁设置过期时,关键点在于过期时间设置,以避免代码异常出现,而该线程持续占有该锁。其java代码如下所示:

while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            // 线程休眠20毫秒,进行递归重试
            try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        }

为了防止误删key,在执行完了业务代码以后需要删掉锁,在try-catch-finally 中添加如下删除锁的代码 :

 try {
     //和上一个代码块重复,省略掉了
    } finally {
        // v5.0 改进点,判断加锁与解锁是不同客户端,自己只能删除自己的锁,不误删别人的锁
        if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {
            stringRedisTemplate.delete(key);
        }
    }

在finally中删除key并不能保持它的原子性,当业务执行时间大于锁的过期时间是,其他线程可以抢占该锁,没法保证业务执行的完整性,所以以下代码借助Lua脚本进行优化,java代码如下所示:

public String sale() {
    String resMessgae = "";
    String key = "luojiaRedisLocak";
    String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();

    // 不用递归了,高并发容易出错,我们用自旋代替递归方法重试调用;也不用if,用while代替
    while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
        // 线程休眠20毫秒,进行递归重试
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
    }

    try {
        // 1 抢锁成功,查询库存信息
        String result = stringRedisTemplate.opsForValue().get("inventory01");
        // 2 判断库存书否足够
        Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
        // 3 扣减库存,每次减少一个库存
        if (inventoryNum > 0) {
            stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));
            resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        } else {
            resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        }
    } finally {
        // 改进点,修改为Lua脚本的Redis分布式锁调用,必须保证原子性,参考官网脚本案例
        String luaScript =
            "if redis.call('get',KEYS[1]) == ARGV[1] then " +
            "return redis.call('del',KEYS[1]) " +
            "else " +
            "return 0 " +
            "end";
        stringRedisTemplate.execute(new DefaultRedisScript(luaScript, Boolean.class), Arrays.asList(key), uuidValue);

    }
    return resMessgae;
}

上述代码,既然所已经被删除了,如何兼顾锁的可重入问题?这个问题下文会做出解释,希望读者耐心看完。可以在业务代码(同步代码块)前后添加lock和unlock实现加锁。而今重新进入时没必要重新获得一把锁。
可重入锁的概念:
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有synchronized修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁。下文1.5将展示详细的可重入分布式锁。

1.5 优化分布式锁

本次优化主要解决的问题有:宕机防止死锁、防止误删key、Lua保证原子性。设置 过期时间的同时,当业务执行时间大于过期时间,自动续锁功能等,分布式锁实现可重入需要使用的hset,记录进入次数。java代码如下所示:

新增续锁功能,java代码如下所示,其中实现步骤为:
步骤一:复原程序为初识无锁版本(即上述乞丐版)
步骤二:新建RedisDistributedLock类实现JUC里面的Lock接口
步骤三:满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码
步骤四:结合设计模式开发属于自己的Redis分布式锁工具类
```java
package com.luojia.redislock.mylock;

import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 自研的分布式锁,实现了Lock接口
 */
public class RedisDistributedLock implements Lock {

    private StringRedisTemplate stringRedisTemplate;

    private String lockName; // KEYS[1]
    private String uuidValule; // ARGV[1]
    private long expireTime; // ARGV[2]

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValule = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        this.expireTime = 50L;
    }

    @Override
    public void lock() {
        tryLock();
    }

    @Override
    public boolean tryLock() {
        try {
            tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (-1 == time) {
	        //lua脚本加锁
            String script =
                    "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
                        "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                        "redis.call('expire', KEYS[1], ARGV[2]) " +
                        "return 1 " +
                    "else " +
                         "return 0 " +
                    "end";
            System.out.println("lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);

            // 加锁失败需要自旋一直获取锁
            while (!stringRedisTemplate.execute(
                    new DefaultRedisScript<>(script, Boolean.class),
                    Arrays.asList(lockName),
                    uuidValule,
                    String.valueOf(expireTime))) {
                // 休眠60毫秒再来重试
                try {TimeUnit.MILLISECONDS.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}
            }
            return true;
        }
        return false;
    }

    @Override
    public void unlock() {
        //lua脚本解锁
        String script = "" +
                "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +
                "return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        System.out.println("lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);
        
        // LUA脚本由C语言编写,nil -> false; 0 -> false; 1 -> true;
        // 所以此处DefaultRedisScript构造函数返回值不能是Boolean,Boolean没有nil
        Long flag = stringRedisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(lockName),
                uuidValule);
        if (null == flag) {
            throw new RuntimeException("this lock does not exists.");
        }
    }

    // 下面两个暂时用不到,不用重写
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

其中lua脚本加锁说明,如下图所示:
在这里插入图片描述
返回为1情况说明。
在这里插入图片描述
lua脚本解锁说明
在这里插入图片描述
完整的分布式锁java代码如下所示:

// v7.0 使用自研的lock/unlock+LUA脚本自研的Redis分布式锁
Lock redisDistributedLock = new RedisDistributedLock(stringRedisTemplate, "luojiaRedisLock");
public String sale() {
    String resMessgae = "";
    redisDistributedLock.lock();
    try {
        // 1 抢锁成功,查询库存信息
        String result = stringRedisTemplate.opsForValue().get("inventory01");
        // 2 判断库存书否足够
        Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
        // 3 扣减库存,每次减少一个库存
        if (inventoryNum > 0) {
            stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));
            resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        } else {
            resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        }
    } finally {
        redisDistributedLock.unlock();
    }
    return resMessgae;
}

小总结
在这里插入图片描述
引入工厂模式
可重入测试,在InventoryService类新增可重入测试方法。

// v7.1 使用工厂类创建锁
@Autowired
private DistributedLockFactory distributedLockFactory;
public String sale() {
    String resMessgae = "";
    Lock redisLock = distributedLockFactory.getDistributedLock("REDIS", "luojiaRedisLock");
    redisLock.lock();
    try {
        // 1 抢锁成功,查询库存信息
        String result = stringRedisTemplate.opsForValue().get("inventory01");
        // 2 判断库存书否足够
        Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
        // 3 扣减库存,每次减少一个库存
        if (inventoryNum > 0) {
            stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));
            resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
            testReEntry();
        } else {
            resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;
            log.info(resMessgae);
        }
    } finally {
        redisLock.unlock();
    }
    return resMessgae;
}

private void testReEntry() {
    Lock redisLock = distributedLockFactory.getDistributedLock("REDIS", "luojiaRedisLock");
    redisLock.lock();
    try {
        log.info("=================测试可重入锁=================");
    } finally {
        redisLock.unlock();
    }
}

引入工厂模式

package com.luojia.redislock.mylock;

import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.locks.Lock;

@Component
public class DistributedLockFactory {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String uuid;
    public DistributedLockFactory() {
        this.uuid = IdUtil.simpleUUID();
    }
    public Lock getDistributedLock(String lockType, String lockName) {
        if (lockType == null) {
            return null;
        }
        if ("REDIS".equalsIgnoreCase(lockType)) {
            return new RedisDistributedLock(stringRedisTemplate, lockName, uuid);
        } else if ("ZOOKEEPER".equalsIgnoreCase(lockType)) {
            // 后面存在就返回对应的分布式锁
        } else if ("MYSQL".equalsIgnoreCase(lockType)) {
            // 后面存在就返回对应的分布式锁
        }
        return null;
    }
}

在RedisDistributedLock中,修改构造方法:

public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {
    this.stringRedisTemplate = stringRedisTemplate;
    this.lockName = lockName;
    this.uuidValule = uuid + ":" + Thread.currentThread().getId();
    this.expireTime = 50L;
}

锁的自动续费功能
确保RedisLock过期时间大于业务执行时间的问题,以便确保时间到了,业务没有执行完需要自动续期,对tryLock进行改正。
自动续锁的Lua脚本:

// 自动续期的LUA脚本
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then
    return redis.call('expire', KEYS[1], ARGV[2])
else
    return 0
end
```java
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    if (-1 == time) {
        String script =
            "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
            "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
            "redis.call('expire', KEYS[1], ARGV[2]) " +
            "return 1 " +
            "else " +
            "return 0 " +
            "end";
        System.out.println("lock() lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);

        // 加锁失败需要自旋一直获取锁
        while (!stringRedisTemplate.execute(
            new DefaultRedisScript<>(script, Boolean.class),
            Arrays.asList(lockName),
            uuidValule,
            String.valueOf(expireTime))) {
            // 休眠60毫秒再来重试
            try {TimeUnit.MILLISECONDS.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}
        }
        // 新建一个后台扫描程序,来检查Key目前的ttl,是否到我们规定的剩余时间来实现锁续期
        resetExpire();
        return true;
    }
    return false;
}

// 自动续期
private void resetExpire() {
    String script =
        "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
        "return redis.call('expire', KEYS[1], ARGV[2]) " +
        "else " +
        "return 0 " +
        "end";
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            if (stringRedisTemplate.execute(
                new DefaultRedisScript<>(script, Boolean.class),
                Arrays.asList(lockName),
                uuidValule,
                String.valueOf(expireTime))) {
                // 续期成功,继续监听
                System.out.println("resetExpire() lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);
                resetExpire();
            }
        }
    }, (this.expireTime * 1000 / 3));
}

总结

1.synchronized单机版OK;
2.Nginx分布式微服务,轮询多台服务器,单机锁不行;
3.取消单机锁,上redis分布式锁setnx,中小企业使用没问题;
4.​ 只是加锁了,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁
5.​ 如果服务宕机,部署了微服务代码层面根本就没有走到finally这块,没办法保证解锁,这个Key没有被删除,需要对锁设置过期时间 -
6.​ 为redis的分布式锁key增加过期时间,还必须要保证setnx+过期时间在同一行,保证原子性
7.​ 程序由于执行超过锁的过期时间,所以在finally中必须规定只能自己删除自己的锁,不能把别人的锁删除了,防止张冠李戴
8.将Lock、unlock变成LUA脚本保证原子性;
9.保证锁的可重入性,hset替代setnx+Lock变成LUA脚本,保障可重入性;
10.锁的自动续期 。

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

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

相关文章

C语言 | Leetcode C语言题解之第404题左叶子之和

题目&#xff1a; 题解&#xff1a; bool isLeafNode(struct TreeNode *node) {return !node->left && !node->right; }int sumOfLeftLeaves(struct TreeNode *root) {if (!root) {return 0;}struct TreeNode **q malloc(sizeof(struct TreeNode *) * 2001);in…

JVM 一个对象是否已经死亡?

目录 前言 引用计数法 可达性分析法 引用 finalize() 方法区回收 前言 虚拟机中垃圾回收器是掌握对象生死的判官, 只要是垃圾回收器认为需要被回收的, 那么这个对象基本可以宣告"死亡". 但是也不是所有的对象, 都需要被回收, 因此, 我们在学习垃圾回收的时候…

Linux系统应用之知识补充——OpenEuler(欧拉)的安装和基础配置

前言 这篇文章将会对OpenEuler的安装进行详解&#xff0c;一步一步跟着走下去就可以成功 注意 &#xff1a;以下的指令操作最好在root权限下进行&#xff08;即su - root&#xff09; ☀️工贵其久&#xff0c;业贵其专&#xff01; 1、OpenEuler的安装 这里我不过多介绍&a…

markdown 使用技巧

文章目录 markdown使用技巧1.标题快捷键设置2.文档可读性设置 markdown使用技巧 1.标题快捷键设置 ctl 1:一级标题 ctl 2:二级标题 ctl 3:三级标题 ctl 4:四级标题 ...2.文档可读性设置 输入~~~pro 可选择代码框&#xff0c;并且可以选择不同的字体 ctrl shift ] : 可…

Flink学习2

创建一个无界流 package com.qyt; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.Keye…

《微信小程序实战(2) · 组件封装》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

138、Java内部类源码

01.代码如下&#xff1a; package TIANPAN;class Outer { // 外部类private String msg "Hello World !";class Inner { // 定义一个内部类private String info "世界&#xff0c;你好&#xff0…

【深度分析】OpenAI o1是最强的推理模型,却不是最强模型!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

磁盘写操作压力测试工具的设计与实现

磁盘写操作压力测试工具的设计与实现 1. 设计概述2. 关键技术点3. 伪代码设计4. C代码实现5. 运行与测试6. 结论在进行磁盘性能评估时,写操作压力测试是不可或缺的一部分。本篇文章将介绍如何使用C语言结合系统调用,设计并实现一个针对磁盘写操作的压力测试工具。这个工具将模…

【设计模式-桥接】

定义 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过将抽象部分与实现部分分离&#xff0c;使它们都可以独立地变化。桥接模式的关键在于将类的抽象部分与其实现部分解耦&#xff0c;以便两者可以独立地变化。这种设计模式的一个主要…

湖北产教融合教育研究院成功协办武汉工程大学2024年同等学力申硕开学典礼

9月7日&#xff0c;武汉工程大学&#xff08;流芳校区&#xff09;教育教学综合楼102报告厅内庄严肃穆&#xff0c;近百位怀揣梦想、追求卓越的学子与校领导、教师代表汇聚一堂&#xff0c;共同迎接“乘风破浪 逐光前行”武汉工程大学2024年同等学力申请硕士学位人员开学典礼的…

【觅图网-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

搜索二叉树的认识以及底层实现

如果说到对一个数组进行查找相应的数据&#xff0c;要求效率最高&#xff0c;大家会想到什么方式呢&#xff1f;二分查找&#xff1f;二分查找的效率确实很高&#xff0c;时间复杂度为O(logN)。但是如果我们想要在数组当中添加新的数据呢&#xff1f;加上这一功能之后二分查找的…

KVM创建的虚拟机无法访问外网

基础环境如下&#xff1a; [rootlocalhost ~]# virsh domifaddr CentOS7_YFName MAC address Protocol Address -------------------------------------------------------------------------------vnet0 52:54:00:cb:a6:0d ipv4 192.168.…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0917)

十一、登录注册页面 [element-plus 表单 & 表单校验] 注册登录 静态结构 & 基本切换 安装 element-plus 图标库 pnpm i element-plus/icons-vue静态结构准备 <script setup> import { User, Lock } from element-plus/icons-vue import { ref } from vue cons…

P2865 [USACO06NOV] Roadblocks G

*原题链接* 次短路模版题 在刚学最短路时&#xff0c;我做过这道题集合位置&#xff0c;那时博客上写的是枚举删除最短路上的边&#xff0c;然后求解。不过这种做法最坏时间复杂度可以有&#xff0c;对于这道题数据范围较大&#xff0c;所以可以用更好写&#xff0c;思维难度…

Linux学习记录十四----------线程的创建和回收

文章目录 五、Linux线程1.守护进程1.1.守护进程的特点1.2.进程组1.3会话1.4创建守护进程模型 2.线程的概念3.线程的创建及相关函数3.1.创建线程‐‐pthread_create3.2.单个线程退出 --pthread_exit3.3.阻塞等待线程退出&#xff0c;获取线程退出状态--pthread_join3.4.线程分离…

python怎么运行cmd命令

使用os.system(“cmd”) 该方法在调用完shell脚本后,返回一个16位的二进制数,低位为杀死所调用脚本的信号号码,高位为脚本的退出状态码,即脚本中“exit 1”的代码执行后,os.system函数返回值的高位数则是1,如果低位数是0的情况下,则函数的返回值是0100,换算为10进制得到256。 …

JavaScript web API完结篇---多案例

BOM window对象 >包含docment Browser Object Model 定时器–延时函数 之前学的是间歇函数 让代码延迟执行 仅执行一次 setTimeout(回调函数&#xff0c;等待毫秒数) 消除 clearTimeout(timer) > 用于递归时需要进行去除 JS执行机制 单线程 > 一个任务结束&…

ROS组合导航笔记2:使用外部定位系统

在上一单元中&#xff0c;我们了解了如何合并不同传感器的数据以生成机器人的姿势估计。因此&#xff0c;基本上&#xff0c;我们介绍了图表的以下部分&#xff0c;其中向 robot_localization 节点提供了不同的传感器&#xff0c;以便通过卡尔曼滤波器进行合并。 但是...图表的…