当前位置: 首页 > news >正文

Redis分布式锁

一、背景

  • 与分布式锁相对应的是「单机锁」,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来「互斥」,以保证共享变量的正确性,其使用范围是在「同一个进程」中。
  • 单机环境下,我们常用 synchronized 或者 Lock 锁解决多线程并发访问产生的数据安全问题,但是如果是在集群环境,本地锁就会失效。
  • 为解决分布式场景下的并发问题, 就需要用到分布式锁。下面介绍下Redis分布式锁。

二、实现思路

1. 如何实现互斥?

为达到互斥,可以使用SETNX 命令,这个命令表示Set If Not Exists,即如果 key 不存在,才会设置它的值,否则什么也不做。
如:

	SETNX lock 1    //加锁DEL lock //释放锁

但是这样有个问题,当客户端 1 拿到锁后,如果发生下面的场景,就会造成「死锁」:
①程序处理业务逻辑异常,未释放锁
②拿到锁后进程挂了,没机会释放锁
这时,这个客户端就会一直占用这个锁,而其它客户端就「永远」拿不到这把锁了。

2. 如何避免死锁?

锁无法释放,产生「死锁」。那么我们给这个锁加个「租期」,让它在一定时间内如果一直没释放就过期,问题不就解决了。Redis支持这种语法,示例:

SETNX lock 1    // 加锁
EXPIRE lock 10  // 10s后自动过期

但这样真的没问题了吗?
No!

现在的操作,加锁、设置过期是 2 条命令,有没有可能只执行了第一条,第二条却「来不及」执行的情况发生呢?例如:
①SETNX 执行成功,执行 EXPIRE 时由于网络问题,执行失败
②SETNX 执行成功,Redis 异常宕机,EXPIRE 没有机会执行
③SETNX 执行成功,客户端异常崩溃,EXPIRE 也没有机会执行
总之,这两条命令不能保证是原子操作(一起成功),就有潜在的风险导致过期时间设置失败,依旧发生「死锁」问题。

如何解决?
Redis 2.6.12 之后,Redis 扩展了 SET 命令的参数,用这一条命令就可以执行上述两步操作:

	// 一条命令保证原子性执行SET lock 1 EX 10 NX

我们再来看分析下,它还有什么问题?
试想这样一种场景:
①客户端 1 加锁成功,开始操作共享资源
②客户端 1 操作共享资源的时间,「超过」了锁的过期时间,锁被「自动释放」
③客户端 2 加锁成功,开始操作共享资源
④客户端 1 操作共享资源完成,释放锁(但释放的是客户端 2 的锁)

这里存在两个严重的问题:
锁过期:客户端 1 操作共享资源耗时太久,导致锁被自动释放,之后被客户端 2 持有
释放别人的锁:客户端 1 操作共享资源完成后,却又释放了客户端 2 的锁

3. 锁被别人释放怎么办?

解决办法是:客户端在加锁时,设置一个只有自己知道的「唯一标识」进去。释放时,先判断这把锁是否是自己所有,是的话再进行释放。
这样涉及到两步操作:
①判断这把锁是否是自己所有;
②释放锁。
非原子性,也会出现并发问题,如何解决呢?Lua脚本
我们可以写好Lua脚本后交给Redis执行,脚本如下:

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

4. 锁过期怎么办?

方案:

  • 可能是我们评估操作共享资源的时间不准确导致的。可以「冗余」适量过期时间,降低锁提前过期的概率。
  • 加锁时,先设置一个过期时间,然后我们开启一个「守护线程」,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行「续期」,重新设置过期时间。

备注:个人根据项目的业务情况,考虑「锁过期」对业务的影响,看是否需要续期。

5. Redisson

Redisson 是一个 Java 语言实现的 Redis SDK 客户端,在使用分布式锁时,它就采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。
Ression原理
除此之外,这个 SDK 还封装了很多易用的功能:

  • 可重入锁
  • 乐观锁
  • 公平锁
  • 读写锁
  • Redlock

6. Redlock

之前分析的场景都是,锁在「单个」Redis 实例中可能产生的问题,并没有涉及到 Redis 的部署架构细节。
而我们在使用 Redis 时,一般会采用主从集群 + 哨兵的模式部署,这样做的好处在于,当主库异常宕机时,哨兵可以实现「故障自动切换」,把从库提升为主库,继续提供服务,以此保证可用性。
那当「主从发生切换」时,这个分布锁会依旧安全吗?

试想这样的场景:

  1. 客户端 1 在主库上执行 SET 命令,加锁成功
  2. 此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)
  3. 从库被哨兵提升为新主库,这个锁在新的主库上,丢失了!
    Redis分布式锁集群问题
    为此,Redis 的作者提出一种解决方案,就是我们经常听到的 Redlock(红锁)。

Redlock 的方案基于 2 个前提:

不再需要部署从库和哨兵实例,只部署主库
但主库要部署多个,官方推荐至少 5 个实例
也就是说,想用使用 Redlock,你至少要部署 5 个 Redis 实例,而且都是主库,它们之间没有任何关系,都是一个个孤立的实例。
注意:不是部署 Redis Cluster,就是部署 5 个简单的 Redis 实例。

Redlock流程是这样的,一共分为 5 步:

  1. 客户端先获取「当前时间戳T1」
  2. 客户端依次向这 5 个 Redis 实例发起加锁请求(用前面讲到的 SET 命令),且每个请求会设置超时时间(毫秒级,要远小于锁的有效时间),如果某一个实例加锁失败(包括网络超时、锁被其它人持有等各种异常情况),就立即向下一个 Redis 实例申请加锁
  3. 如果客户端从 >=3 个(大多数)以上 Redis 实例加锁成功,则再次获取「当前时间戳T2」,如果 T2 - T1 < 锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败
  4. 加锁成功,去操作共享资源(例如修改 MySQL 某一行,或发起一个 API 请求)
  5. 加锁失败,向「全部节点」发起释放锁请求(前面讲到的 Lua 脚本释放锁)

上述过程有4个重点:

  1. 客户端在多个 Redis 实例上申请加锁
  2. 必须保证大多数节点加锁成功
  3. 大多数节点加锁的总耗时,要小于锁设置的过期时间
  4. 释放锁,要向全部节点发起释放锁请求

实际生产中,Redlock很少使用,所以就简单介绍到这里。

三、springboot项目实现Redis分布式锁

1. 依赖

	<!--Springboot依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --><!--Springboot 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.3.12.RELEASE</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --><!--Springboot Redis依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.12.RELEASE</version><!--排除掉默认的 lettuce ,lettuce 在使用中存在偶尔连接超时问题--><exclusions><exclusion><artifactId>lettuce-core</artifactId><groupId>io.lettuce</groupId></exclusion></exclusions></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-commons --><!-- Redis 需要引入这个依赖,否则报错 --><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId><version>2.3.9.RELEASE</version></dependency>

2. Redis配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;import java.util.HashSet;
import java.util.Set;@Configuration
public class RedisConfig {@Value("${spring.redis.cluster.nodes:IP:Port}")private String clusterNodes;@Value("${spring.redis.cluster.max-redirects:3}")private int maxRedirects;@Value("${spring.redis.password:***}")private String password;@Value("${spring.redis.timeout:3000}")private int timeout;/*** 最大空闲数*/@Value("${spring.redis.maxIdle:100}")private int maxIdle;/*** 控制一个pool可分配多少个jedis实例*/@Value("${spring.redis.maxTotal:100}")private int maxTotal;/*** 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制*/@Value("${spring.redis.maxWaitMillis:5000}")private int maxWaitMillis;/*** 最小空闲数*/@Value("${spring.redis.minIdle:5}")private int minIdle;/*** 连接的最小空闲时间 默认1800000毫秒(30分钟)*/@Value("${spring.redis.minEvictableIdleTimeMillis:300000}")private int minEvictableIdleTimeMillis;/*** 每次释放连接的最大数目,默认3*/@Value("${spring.redis.numTestsPerEvictionRun:3}")private int numTestsPerEvictionRun;/*** 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1*/@Value("${spring.redis.timeBetweenEvictionRunsMillis:30000}")private int timeBetweenEvictionRunsMillis;/*** 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个*/@Value("${spring.redis.testOnBorrow:true}")private boolean testOnBorrow;/*** 在空闲时检查有效性, 默认false*/@Value("${spring.redis.testWhileIdle:true}")private boolean testWhileIdle;@Beanpublic JedisPoolConfig getJedisPoolConfig() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大空闲数jedisPoolConfig.setMaxIdle(maxIdle);// 最小空闲数jedisPoolConfig.setMinIdle(minIdle);// 连接池的最大数据库连接数jedisPoolConfig.setMaxTotal(maxTotal);// 最大建立连接等待时间jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);// 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个jedisPoolConfig.setTestOnBorrow(testOnBorrow);// 在空闲时检查有效性, 默认falsejedisPoolConfig.setTestWhileIdle(testWhileIdle);return jedisPoolConfig;}/*** Redis集群的配置** @return RedisClusterConfiguration*/@Beanpublic RedisClusterConfiguration redisClusterConfiguration() {RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();// Set<RedisNode> clusterNodesString[] serverArray = clusterNodes.split(",");Set<RedisNode> nodes = new HashSet<>();for (String ipPort : serverArray) {String[] ipAndPort = ipPort.split(":");nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1])));}redisClusterConfiguration.setClusterNodes(nodes);redisClusterConfiguration.setMaxRedirects(maxRedirects);redisClusterConfiguration.setPassword(RedisPassword.of(password));return redisClusterConfiguration;}/*** redis连接工厂类** @return JedisConnectionFactory*/@Beanpublic JedisConnectionFactory jedisConnectionFactory() {// 集群模式return new JedisConnectionFactory(redisClusterConfiguration(), getJedisPoolConfig());}@Beanpublic RedisTemplate<String, String> poolRedisTemplate() {RedisTemplate<String, String> template = new RedisTemplate<>();template.setConnectionFactory(jedisConnectionFactory());// 如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(mapper);template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(new StringRedisSerializer());template.setDefaultSerializer(jackson2JsonRedisSerializer);template.setEnableDefaultSerializer(true);template.afterPropertiesSet();return template;}
}

3. Redis 分布式锁

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Service
public class RedisService {@Autowired@Qualifier("poolRedisTemplate")private RedisTemplate<String, String> stringRedisTemplate;public String getStr(String key) {return stringRedisTemplate.opsForValue().get(key);}/*** key 不存在 则进行设置* <p>* 原子性操作** @param k       键* @param v       值* @param timeout 超时时间* @param unit    超时时间的单位* @return key存在返回false,设置失败*/public Boolean setIfAbsent(String k, String v, long timeout, TimeUnit unit) {return stringRedisTemplate.opsForValue().setIfAbsent(k, v, timeout, unit);}/*** 加分布式锁** @param key     锁* @param timeout 超时时间,单位:秒* @return 空串 表示加锁失败, uuid 表示加锁成功,后续uuid要作为解锁的标识*/public String tryLock(String key, long timeout) {//释放锁时要根据uuid判断是否是自己的锁,防止释放其他人的锁
//        String uuid = System.currentTimeMillis() + "";String uuid = UUID.randomUUID().toString();Boolean tryLock = setIfAbsent(key, uuid, timeout, TimeUnit.SECONDS);if (tryLock) {return uuid;}//加锁失败,返回 空串return "";}/*** “判断值与旧值是否相等,相等则删除键” 的 Lua 脚本,保证原子性操作*/private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";/*** 释放分布式锁** @param key      锁* @param oldValue 加锁时放入的标识* @return 返回释放锁成功失败*/public Boolean unLock(String key, String oldValue) {List<String> keys = new ArrayList<>();keys.add(key);List<String> args = new ArrayList<>();//这里需要加下引号,原因:stringRedisTemplate获取的值带引号(即redis.call('get', KEYS[1]) 的结果带引号),而ARGV[1]不带引号,比较时会出现不等的问题args.add("\"" + oldValue + "\"");Long result = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {Object nativeConnection = connection.getNativeConnection();// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行// 集群模式if (nativeConnection instanceof JedisCluster) {return (Long) ((JedisCluster) nativeConnection).eval(SCRIPT, keys, args);}// 单机模式else if (nativeConnection instanceof Jedis) {return (Long) ((Jedis) nativeConnection).eval(SCRIPT, keys, args);}return 0L;});return result == 1L;}}

4. 测试类

import com.example.demo.redis.RedisService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StringUtils;@SpringBootTest
public class RedisTest {@Autowiredprivate RedisService redisService;@Testpublic void test() {String key = "test-lock-1235637";String uid = redisService.tryLock(key, 10000);if(StringUtils.isEmpty(uid)){return;}System.out.println(uid);String uid2 = redisService.tryLock("test-lock-123", 3000);System.out.println("重新获得锁是否成功:" + !StringUtils.isEmpty(uid2));try {//执行业务代码System.out.println("111111111");} finally {Boolean unlockSuccess = redisService.unLock(key, uid);System.out.println("解锁结果:" + unlockSuccess);System.out.println(redisService.getStr(key));}}
}

四、springboot项目中Redission的简单使用

1. 依赖

<!--redission相关依赖--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version></dependency>

2. 配置类

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissionConfig {@Beanpublic RedissonClient getRedisson() {
//        //单机
//        Config config = new Config();
//        //单机模式  依次设置redis地址和密码
//        config.useSingleServer().
//                setAddress("redis://" + host + ":" + port);
//        RedissonClient redisson = Redisson.create(config);
//
//        //主从
//        Config config = new Config();
//        config.useMasterSlaveServers()
//                .setMasterAddress("redis://127.0.0.1:6379")
//                .addSlaveAddress("redis://127.0.0.1:6389", "127.0.0.1:6332", "127.0.0.1:6419")
//                .addSlaveAddress("redis://127.0.0.1:6399");
//        RedissonClient redisson = Redisson.create(config);
//
//
//        //哨兵
//        Config config = new Config();
//        config.useSentinelServers()
//                .setMasterName("mymaster")
//                .addSentinelAddress("redis://127.0.0.1:26389", "127.0.0.1:26379")
//                .addSentinelAddress("redis://127.0.0.1:26319");
//        RedissonClient redisson = Redisson.create(config);//集群  ,,,,,Config config = new Config();config.useClusterServers().setScanInterval(2000) // cluster state scan interval in milliseconds.addNodeAddress("redis://ip:port", "redis://ip:port").setPassword("***");RedissonClient redisson = Redisson.create(config);return redisson;}
}

3. 测试类

import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@SpringBootTest
public class RedissionTest {@Resourceprivate RedissonClient redisson;@Testpublic void testRedission() {System.out.println("开始执行");String lockKey = "123456";RLock lock = redisson.getLock(lockKey);System.out.println("获取锁");try {//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位boolean b = lock.tryLock(1, 10, TimeUnit.SECONDS);System.out.println("是否获取锁:" + b);//执行业务逻辑System.out.println("执行业务逻辑....");Thread.sleep(3000);} catch (Exception e) {System.out.println("系统异常,稍后重试....");} finally {//删除锁lock.unlock();System.out.println("解锁成功");}}
}

五、参考文献

https://mp.weixin.qq.com/s/2et43aJT6qjBsJ8Z9pcZcQ

相关文章:

Redis分布式锁

一、背景 与分布式锁相对应的是「单机锁」&#xff0c;我们在写多线程程序时&#xff0c;避免同时操作一个共享变量产生数据问题&#xff0c;通常会使用一把锁来「互斥」&#xff0c;以保证共享变量的正确性&#xff0c;其使用范围是在「同一个进程」中。单机环境下&#xff0…...

京东前端经典面试题整理

img的srcset属性的作⽤&#xff1f; 响应式页面中经常用到根据屏幕密度设置不同的图片。这时就用到了 img 标签的srcset属性。srcset属性用于设置不同屏幕密度下&#xff0c;img 会自动加载不同的图片。用法如下&#xff1a; <img src"image-128.png" srcset&qu…...

django+mysql实现一个简单的web登录页面

目录 一、使用pyacharm创建一个django项目 二、启动django项目验证 三、配置mysql数据库 1、本地安装mysql数据库 1&#xff09;安装mysql数据库 2&#xff09;自己创建一个数据库 2、安装 pymysql 3、配置mysql数据库 1&#xff09;在项目同名包下的_init_.py里面添加…...

python cartopy手动导入地图数据绘制底图/python地图上绘制散点图:Downloading:warnings/散点图添加图里标签

……开学回所&#xff0c;打开电脑spyder一看一脸懵逼&#xff0c;简直不敢相信这些都是我自己用过的代码&#xff0c;想把以前的自己喊过来科研了&#xff08;&#xff09; 废话少说&#xff0c;最近写小综述论文&#xff0c;需要绘制一个地图底图&#xff0b;散点图&#xff…...

JavaScript中常用的数组方法

在日常开发中&#xff0c;我们会接触到js中数组的一些方法&#xff0c;这些方法对我们来说&#xff0c;可以很便利的达到我们想要的结果&#xff0c;但是因为方法比较多&#xff0c;有些方法也不常用&#xff0c;可能会过一段时间就会忘记&#xff0c;那么在这里我整理了一些数…...

磁疗为什么“没效果”?原来真相是这样!

很多人磁疗之后&#xff0c; 总爱迫不及待问一个问题&#xff1a; “这个多长时间见效啊&#xff1f;” …… 还有些人几天没有效果&#xff0c; 就果断下结论&#xff1a; “这东西没用&#xff01;” …… 有不少人错误地把磁疗等同于“药品”一样看待&#xff0c;总觉得…...

【直击招聘C++】5.1函数模板

5.1函数模板一、要点归纳1.定义函数模板2.实例化函数模板3.重载模板函数4.函数调用的匹配顺序一、要点归纳 1.定义函数模板 定义函数模板的一般格式如下&#xff1a; template<类型形参表> 返回类型 函数名&#xff08;形参表&#xff09; {函数体&#xff1b; }例如以…...

谈谈Java多线程离不开的AQS

如果你想深入研究Java并发的话&#xff0c;那么AQS一定是绕不开的一块知识点&#xff0c;Java并发包很多的同步工具类底层都是基于AQS来实现的&#xff0c;比如我们工作中经常用的Lock工具ReentrantLock、栅栏CountDownLatch、信号量Semaphore等&#xff0c;而且关于AQS的知识点…...

国际化语言,多语言三种方式

可以用透传的方式&#xff0c;自己写local的json文件&#xff0c;不需要配置什么&#xff0c;直接传&#xff0c;自己写方法i18n nextjsi18n umi4一、透传的方式 export const AppContext React.createContext<any>({})app.tsx 用context包裹import type { AppProps } f…...

C++——哈希3|位图

目录 常见哈希函数 位图 位图扩展题 位图的应用 常见哈希函数 1. 直接定址法--(常用) 这种方法不存在哈希冲突 取关键字的某个线性函数为散列地址&#xff1a;Hash&#xff08;Key&#xff09; A*Key B 优点&#xff1a;简单、均匀 缺点&#xff1a;需要事先知道关键字的…...

75 error

全部 答对 答错 选择题 3. 某公司非常倚重预测型方法交付项目&#xff0c;而其招聘的新项目经理却习惯于运用混合型方法。项目范围包含很多不清晰的需求。项目经理应该如何规划项目的交付&#xff1f; A company that is heavily focused on delivering projects using predi…...

ESP-C3入门8. 连接WiFi并打印信息

ESP-C3入门8. 连接WiFi并打印信息一、ESP32 连接WiFi的基本操作流程1. 初始化nvs存储2. 配置WiFi工作模式3. 设置WiFi登陆信息4. 启动WiFi5. 开启连接6. 判断是否成功二、事件处理函数1. 定义事件处理函数2. 创建事件组3. 在事件处理函数中设置事件组位4. 在其他任务中等待事件…...

使用python将EXCEL表格中数据转存到数据库

使用Python将excel表格中数据转存到数据库 1. 思路&#xff1a; 1&#xff09; 使用python读取excel表格中数据 2&#xff09;根据数据生成sql语句 3&#xff09;批量运行sql语句 2. 代码&#xff1a; import pandas as pddef readExcel(path, excel_file):return pd.read_e…...

【C++】类和对象(三)

目录 一、构造函数补充 1、初始化列表 1.1、初始化列表概念 1.2、初始化列表性质 2、explicit关键字 二、static成员 1、概念及使用 2、性质总结 三、友元 1、友元函数 2、友元类 四、内部类 五、拷贝对象时的一些编译器优化 一、构造函数补充 在《类和对象&#x…...

vTESTstudio - VT System CAPL Functions - General/Trigger Function

前面文章中我们已经介绍了常用的几种板卡的基本信息&#xff0c;那这些板卡该如何去通过软件调用呢&#xff1f;带着这个问题我们开始新的一块内容 - VT系统相关的自动化控制函数介绍&#xff0c;我会按照不同的板卡来分类&#xff0c;对其可控制的函数进行介绍&#xff0c;方便…...

IDEA 快捷键

ctrlD &#xff1a;复制当前行到下一行 ctrlO : 重写当前类的方法 ctrlshiftu : 大小写转化 Alt 上/下 &#xff1a;跳到上一个、下一个函数 Alt 左/右 : 回到上一个、下一个文件 Alt 回车 &#xff1a; 代码修正 Alt Insert &#xff1a; 插入代码 Ctrl Alt L &#xf…...

2023新华为OD机试题 - 入栈出栈(JavaScript) | 刷完必过

入栈出栈 题目 向一个空栈中依次存入正整数 假设入栈元素N(1 <= N <= 2^31-1) 按顺序依次为Nx ... N4、N3、N2、N1, 当元素入栈时,如果N1=N2+...Ny (y的范围[2,x],1 <= x <= 1000) 则N1到Ny全部元素出栈,重新入栈新元素M(M=2*N1) 如依次向栈存储6、1、2、3,当存…...

微信公众号扫码授权登录思路

引言 上学期研究了一下微信登录相关内容&#xff0c;也写了两三篇笔记&#xff0c;但是最后实际登录流程没有写&#xff0c;主要因为感觉功能完成有所欠缺&#xff0c;一直也没有好的思路&#xff1b;这两天我又看了看官方文档&#xff0c;重新构思了一下微信公众号登录相关的…...

数据结构与算法基础-学习-10-线性表之顺序栈的清理、销毁、压栈、弹栈

一、函数实现顺序栈的其他函数实现&#xff0c;请看之前的博客链接《数据结构与算法基础-学习-09-线性表之栈的理解、初始化顺序栈、判断顺序栈空、获取顺序栈长度的实现》。1、ClearSqStack&#xff08;1&#xff09;用途清理栈的空间。只需要栈顶指针和栈底指针相等&#xff…...

Hazel游戏引擎(005)

本人菜鸟&#xff0c;文中若有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/GameEngineLightWeight&#xff08;中文的注释适合中国人的你&#xff09; 文章目录前言关键操作代码文件关键代码代码流程代码文件关键代码exter…...

牛客网Python篇数据分析习题(四)

1.现有一个Nowcoder.csv文件&#xff0c;它记录了牛客网的部分用户数据&#xff0c;包含如下字段&#xff08;字段与字段之间以逗号间隔&#xff09;&#xff1a; Nowcoder_ID&#xff1a;用户ID Level&#xff1a;等级 Achievement_value&#xff1a;成就值 Num_of_exercise&a…...

盲盒如何创业?

所谓的“盲盒”&#xff0c;受众群体大部分是那些爱碰运气的人&#xff0c;顾客买的是那种在打开盲盒时一刹那的惊喜感和神秘感&#xff0c;在打开盲盒之前&#xff0c;谁也不知道自己会得到什么&#xff0c;这也是为什么消费者更愿意购买的原因。网上的盲盒&#xff0c;主要是…...

第1集丨Java中面向对象相关概念汇总

目录一、基本概念1.1 类1.2 属性1.3 方法1.4 静态1.5 包1.6 import二、高级概念2.1 构造方法2.2 继承2.3 super & this2.4 多态2.5 方法重载2.6 方法重写2.7 访问权限2.8 内部类2.9 final2.10 抽象2.11 接口2.12 匿名类面向对象的编程思想力图使计算机语言中对事物的描述与…...

高性能(二)

三、读写分离和分库分表 1.读写分离 1.1 概述 将数据库的读写操作分散到不同的数据库节点上 通常一主多从一台主数据库负责写&#xff0c;多台从数据库负责读。 主库和从库之间会进行数据同步&#xff0c;以保证从库中数据的准确性。 1.2 问题及解决 1.2.1 问题 主从同…...

Allegro如何实现同一个屏幕界面分屏显示操作指导

Allegro如何实现同一个屏幕界面分屏显示操作指导 在做PCB设计的时候,会需要分屏显示,比如一边是放大的视图,另外一边是缩小的视图,Allegro支持同一个屏幕界面下进行分屏显示,如下图 而且会实时同步起来 如何分屏,具体操作如下 点击View...

前后端一些下载与配置(第二篇 第10天过后)nuxt banner redis 短信服务

NUXT 应该是不用怎么装&#xff1f; 有现成的 axios 还需要在npm吗 好像已经有现成的了 banner banner 笔记汇总P396 Redis Linux安装redis tar -xzvf redis-6.2.6.tar.gz cd redis-6.2.6 照着他做 然后 cd /usr/local/redis/bin ./redis-server /usr/local/redis…...

OSG三维渲染引擎编程学习之四十八:“第五章:OSG场景渲染” 之 “5.6 多重纹理映射”

目录 第五章 OSG场景渲染 5.6 多重纹理映射 5.6.1 多重纹理映射介绍 5.6.2 多重纹理映射示例...

对Node.js 的理解?优缺点?应用场景?

一、是什么 Node.js 是一个开源与跨平台的 JavaScript 运行时环境 在浏览器外运行 V8 JavaScript 引擎&#xff08;Google Chrome 的内核&#xff09;&#xff0c;利用事件驱动、非阻塞和异步输入输出模型等技术提高性能 可以理解为 Node.js 就是一个服务器端的、非阻塞式I/…...

Bean的生命周期

所谓的生命周期指的是一个对象从诞生到销毁的整个生命过程&#xff0c;我们把这个过程就叫做一个对象的生命周期~~ Bean的生命周期分为以下五大部分&#xff1a; 实例化&#xff08;为 Bean 分配内存空间&#xff09; 设置属性&#xff08;Bean对象注入/装配&#xff09; 初…...

Python学习-----函数2.0(函数对象,名称空间,作用域-->全局变量与局部变量)

目录 前言&#xff1a; 1.函数对象 &#xff08;1&#xff09;函数对象的引用 &#xff08;2&#xff09;函数可以放到序列里面 &#xff08;3&#xff09;函数可以作为参数 &#xff0c; 传递给另一个函数 2.名称空间 3.作用域 &#xff08;1&#xff09;作用域的理解 …...

长治网站建设/seo查询 站长之家

∣A∪B∣∣A∣∣B∣−∣A∩B∣|A∪B| |A||B| - |A∩B| ∣A∪B∣∣A∣∣B∣−∣A∩B∣ ∣A∪B∪C∣∣A∣∣B∣∣C∣−∣A∩B∣−∣B∩C∣−∣C∩A∣∣A∩B∩C∣证明&#xff1a;∣AUBUC∣∣AUB∣∣C∣−∣(AUB)∩C∣∣A∣∣B∣−∣A∩B∣∣C∣−∣(A∩C)U(B∩C)∣∣A∣∣B∣−∣…...

无锡专业做网站建设/百度售后服务电话

在我们平常的编码中&#xff0c;通常会将一些对象保存起来&#xff0c;这主要考虑的是对象的创建成本。比如像线程资源、数据库连接资源或者 TCP 连接等&#xff0c;这类对象的初始化通常要花费比较长的时间&#xff0c;如果频繁地申请和销毁&#xff0c;就会耗费大量的系统资源…...

网站流量下滑/今日头条普通版

做工作都有需求在推动着我们&#xff0c;需求都需要强大的技术和分析能力做支撑&#xff0c;工欲善其事&#xff0c;必先利其器。在数据分析的过程中&#xff0c;我们需要针对不同用户的分析需求&#xff0c;去提供不同的分析模块、不同的模型&#xff0c;这些都需要有专业的分…...

大连做网站价格/网址提交

javascript中的继承实现转载于:https://www.cnblogs.com/daishuguang/p/4190761.html...

婚恋网站女孩子做美容/网站推广工具有哪些

MyBatis获取参数值MyBatis获取参数值的两种方式单个字面量类型的参数多个字面量类型的参数map集合类型的参数实体类类型的参数使用Param标识参数总结MyBatis获取参数值的两种方式 MyBatis获取参数值的两种方式&#xff1a; ${}的本质就是字符串拼接#{}的本质就是占位符赋值 ${…...

上海公司公开发行股票1984/优化大师哪个好

前言&#xff1a; 习惯了可视化操作git,就把git命令忘得差不多了&#xff0c;但是有些东西还必须命令解决。今天就是遇到了分支合并&#xff0c;代码回滚和代码强制提交。可视化操作不来&#xff0c;只能用git命令操作。 先一步步介绍&#xff1a; 一&#xff1a;代码的基本…...