Redis分布式锁 - 基于Jedis和LUA的分布式锁

先基于单机模式,基于Jedis手工造轮子实现自己的分布式锁。

首先看两个命令:

Redis 分布式锁机制,主要借助 setnx expire 两个命令完成。

setnx命令:

setnx  set  if not exists  的简写。将 key 的值设为 value ,当且仅当 key 不存在 ; 若给定的 key 已经存在,则 setnx  不做任何动作。
下面为客户端使用示例:
127.0.0.1:6379> set lock "unlock"
OK
127.0.0.1:6379> setnx lock "unlock"
(integer) 0
127.0.0.1:6379> setnx lock "lock"
(integer) 0
127.0.0.1:6379>

expire命令:

expire 命令为 key 设置生存时间,当 key 过期时 ( 生存时间为 0 ) ,它会被自动删除。
其格式为: expire key seconds
下面为客户端使用示例:
127.0.0.1:6379> expire lock 10
(integer) 1
127.0.0.1:6379> ttl lock
8

基于Jedis API的分布式锁的总体流程:

通过 Redis setnx expire 命令可以实现简单的锁机制:
  • key不存在时创建,并设置value和过期时间,返回值为1;成功获取到锁;
  • key存在时直接返回0,抢锁失败;
  • 持有锁的线程释放锁时,手动删除key; 或者过期时间到,key自动删除,锁释放。

线程调用setnx方法成功返回1认为加锁成功,其他线程要等到当前线程业务操作完成释放锁后,才能再次调用setnx加锁成功。

以上简单redis分布式锁的问题:

如果出现了这么一个问题:如果 setnx 是成功的,但是 expire 设置失败,一旦出现了释放锁失败,或者没有手工释放,那么这个锁永远被占用,其他线程永远也抢不到锁。
所以 , 需要保障 setnx expire 两个操作的原子性,要么全部执行,要么全部不执行,二者不能分开。

解决的办法有两种: 

  • 使用set的命令时,同时设置过期时间,不再单独使用 expire 命令;
  • 使用lua脚本,将加锁的命令放在lua脚本中原子性的执行。

简单加锁:使用set的命令时,同时设置过期时间

使用set的命令时,同时设置过期时间的示例如下:

127.0.0.1:6379> set unlock "234" EX 100 NX
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set test "111" EX 100 NX
OK
这样就完美的解决了分布式锁的原子性; set 命令的完整格式:
set key value [EX seconds] [PX milliseconds] [NX|XX] 
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)
加锁的简单代码实现
@Slf4j
@Data
@AllArgsConstructor
public class JedisCommandLock {private RedisTemplate redisTemplate;private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";/*** 尝试获取分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @param expireTime 超期时间* @return 是否获取成功*/public static boolean tryGetDistributedLock(Jedis jedis, String lockKey,String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}
}
可以看到,我们加锁用到了 Jedis set Api
jedis.set(String key, String value, String nxxx, String expx, int time)
这个 set() 方法一共有五个形参:
  • 第一个为key,我们使用key来当锁,因为key是唯一的。
  • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。 requestId可以使用 UUID.randomUUID().toString() 方法生成。
  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
  • 第五个为time,与第四个参数相呼应,代表key的过期时间。

 总的来说,执行上面的set()方法就只会导致两种结果:

1. 当前没有锁( key 不存在),那么就进行加锁操作,并对锁设置个有效期,同时 value 表示加锁的客户端。
2. 已有锁存在,不做任何操作。
心细的童鞋就会发现了,我们的加锁代码满足前面描述的四个条件中的三个。
  • 首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。
  • 其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会被永远占用(而发生死锁)。
  • 最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
  • 由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑。
 基于Jedis API实现简单解锁代码
@Slf4j
@Data
@AllArgsConstructor
public class JedisCommandLock {private static final Long RELEASE_SUCCESS = 1L;/*** 释放分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @return 是否释放成功*/public static boolean releaseDistributedLock(Jedis jedis, String lockKey,String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then returnredis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {return true;}return false;}
}
那么这段  Lua  代码的功能是什么呢?
其实很简单,首先获取锁对应的  value  值,检查是否与  requestId  相等,如果相等则删除锁(解锁)。
第一行代码,我们写了一个简单的 Lua 脚本代码。
第二行代码,我们将 Lua 代码传到 jedis.eval() 方法里,并使参数  KEYS[1]  赋值为  lockKey ARGV[1] 赋值为requestId。 eval()  方法是将  Lua  代码交给  Redis  服务端执行。
那么为什么要使用 Lua 语言来实现呢?
因为要确保上述操作是原子性的。那么为什么执行  eval()  方法可以确保原子性,源于  Redis 的特性 . 简单来说,就是在 eval  命令执行  Lua  代码的时候, Lua  代码将被当成一个命令去执行,并且直到 eval 命令执行完成, Redis  才会执行其他命
 错误示例1:
最常见的解锁代码就是直接使用 jedis.del() 方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。
public static void wrongReleaseLock1(Jedis jedis, String lockKey) {jedis.del(lockKey);
}
错误示例2 :
这种解锁代码乍一看也是没问题,甚至我之前也差点这样实现,与正确姿势差不多,唯一区别的是分成两条命令去执行,代码如下:
public static void wrongReleaseLock2(Jedis jedis, String lockKey, StringrequestId) {// 判断加锁与解锁是不是同一个客户端if (requestId.equals(jedis.get(lockKey))) {// 若在此时,这把锁突然不是这个客户端的,则会误解锁jedis.del(lockKey);}
}
基于Lua脚本实现分布式锁
lua脚本的好处

        为什么要使用Lua语言来实现呢? 因为要确保上述操作是原子性的。那么为什么执行 eval()方法可以确保原子性,源于Redis的特性,简单来说,就是在 eval 命令执行 Lua 代码的时候,Lua代码将被当成一个命令去执行,并且直到 eval 命令执行完成,Redis才会执行其他命令。大部分的开源框架(如 redission)中的分布式锁组件,都是用纯lua脚本实现的。Lua 脚本是高并发、高性能的必备脚本语言。

基于纯Lua脚本的分布式锁的执行流程

加锁和删除锁的操作,使用纯 lua 进行封装,保障其执行时候的原子性。
基于纯Lua脚本实现分布式锁的执行流程,大致如下:
 加锁的Lua脚本: lock.lua
--- -1 failed
--- 1 success
---
local key = KEYS[1]
local requestId = KEYS[2]
local ttl = tonumber(KEYS[3])
local result = redis.call('setnx', key, requestId)
if result == 1 then--PEXPIRE:以毫秒的形式指定过期时间redis.call('pexpire', key, ttl)
elseresult = -1;-- 如果value相同,则认为是同一个线程的请求,则认为重入锁local value = redis.call('get', key)if (value == requestId) thenresult = 1;redis.call('pexpire', key, ttl)end
end
-- 如果获取锁成功,则返回 1
return result
解锁的Lua脚本: unlock.lua
--- -1 failed
--- 1 success
-- unlock key
local key = KEYS[1]
local requestId = KEYS[2]
local value = redis.call('get', key)
if value == requestId thenredis.call('del', key);return 1;
end
return -1
Java中调用lua脚本,完成加锁操作
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;@Slf4j
@Data
@AllArgsConstructor
public class JedisLock implements Lock {private RedisTemplate redisTemplate;RedisScript<Long> lockScript = null;RedisScript<Long> unLockScript = null;public static final int DEFAULT_TIMEOUT = 2000;public static final Long LOCKED = Long.valueOf(1);public static final Long UNLOCKED = Long.valueOf(1);public static final Long WAIT_GAT = Long.valueOf(200);public static final int EXPIRE = 2000;String key;String lockValue; // lockValue 锁的value ,代表线程的uuid/*** 默认为2000ms*/long expire = 2000L;public JedisLock(String lockKey, String lockValue) {this.key = lockKey;this.lockValue = lockValue;}private volatile boolean isLocked = false;private Thread thread;/*** 获取一个分布式锁 , 超时则返回失败** @return 获锁成功 - true | 获锁失败 - false*/@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {//本地可重入if (isLocked && thread == Thread.currentThread()) {return true;}expire = unit != null ? unit.toMillis(time) : DEFAULT_TIMEOUT;long startMillis = System.currentTimeMillis();Long millisToWait = expire;boolean localLocked = false;int turn = 1;while (!localLocked) {localLocked = this.lockInner(expire);if (!localLocked) {millisToWait = millisToWait - (System.currentTimeMillis() -startMillis);startMillis = System.currentTimeMillis();if (millisToWait > 0L) {/*** 还没有超时*/ThreadUtil.sleepMilliSeconds(WAIT_GAT);log.info("睡眠一下,重新开始,turn:{},剩余时间:{}", turn++,millisToWait);} else {log.info("抢锁超时");return false;}} else {isLocked = true;localLocked = true;}}return isLocked;}/*** 有返回值的抢夺锁** @param millisToWait*/public boolean lockInner(Long millisToWait) {if (null == key) {return false;}try {List<String> redisKeys = new ArrayList<>();redisKeys.add(key);redisKeys.add(lockValue);redisKeys.add(String.valueOf(millisToWait));Long res = (Long) redisTemplate.execute(lockScript, redisKeys);return res != null && res.equals(LOCKED);} catch (Exception e) {e.printStackTrace();throw BusinessException.builder().errMsg("抢锁失败").build();}}
}
Java中调用lua脚本,完成解锁操作
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;@Slf4j
@Data
@AllArgsConstructor
public class JedisLock implements Lock {private RedisTemplate redisTemplate;RedisScript<Long> lockScript = null;RedisScript<Long> unLockScript = null;//释放锁@Overridepublic void unlock() {if (key == null || requestId == null) {return;}try {List<String> redisKeys = new ArrayList<>();redisKeys.add(key);redisKeys.add(requestId);Long res = (Long) redisTemplate.execute(unLockScript, redisKeys);} catch (Exception e) {e.printStackTrace();throw BusinessException.builder().errMsg("释放锁失败").build();}}
}
 编写RedisLockService用于管理JedisLock
编写个分布式锁服务,用于加载  lua  脚本,创建分布式锁,代码如下:
import com.crazymaker.springcloud.common.util.IOUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;@Slf4j
@Data
public class RedisLockService {private RedisTemplate redisTemplate;static String lockLua = "script/lock.lua";static String unLockLua = "script/unlock.lua";static RedisScript<Long> lockScript = null;static RedisScript<Long> unLockScript = null;{String script =IOUtil.loadJarFile(RedisLockService.class.getClassLoader(), lockLua);// String script = FileUtil.readString(lockLua, Charset.forName("UTF-8"));if (StringUtils.isEmpty(script)) {log.error("lua load failed:" + lockLua);}lockScript = new DefaultRedisScript<>(script, Long.class);// script = FileUtil.readString(unLockLua, Charset.forName("UTF-8"));script =IOUtil.loadJarFile(RedisLockService.class.getClassLoader(), unLockLua);if (StringUtils.isEmpty(script)) {log.error("lua load failed:" + unLockLua);}unLockScript = new DefaultRedisScript<>(script, Long.class);}public RedisLockService(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public Lock getLock(String lockKey, String lockValue) {JedisLock lock = new JedisLock(lockKey, lockValue);lock.setRedisTemplate(redisTemplate);lock.setLockScript(lockScript);lock.setUnLockScript(unLockScript);return lock;}
}
测试用例
import lombok.extern.slf4j.Slf4j;import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoCloudApplication.class})
// 指定启动类
public class RedisLockTest {@ResourceRedisLockService redisLockService;private ExecutorService pool = Executors.newFixedThreadPool(10);@Testpublic void testLock() {int threads = 10;final int[] count = {0};CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() ->{String lockValue = UUID.randomUUID().toString();try {Lock lock = redisLockService.getLock("test:lock:1",lockValue);boolean locked = lock.tryLock(10, TimeUnit.SECONDS);if (locked) {for (int j = 0; j < 1000; j++) {count[0]++;}log.info("count = " + count[0]);lock.unlock();} else {System.out.println("抢锁失败");}} catch (Exception e) {e.printStackTrace();}countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("10个线程每个累加1000为: = " + count[0]);//输出统计结果float time = System.currentTimeMillis() - start;System.out.println("运行的时长为(ms):" + time);System.out.println("每一次执行的时长为(ms):" + time / count[0]);}
}

执行结果

2021-05-04 23:02:11.900 INFO 22120 --- [pool-1-thread-7]
c.c.springcloud.lock.RedisLockTest LN:50 count = 6000
2021-05-04 23:02:11.901 INFO 22120 --- [pool-1-thread-1]
c.c.springcloud.standard.lock.JedisLock LN:81 睡眠一下,重新开始,turn:3,剩余时间:
9585
2021-05-04 23:02:11.902 INFO 22120 --- [pool-1-thread-1]
c.c.springcloud.lock.RedisLockTest LN:50 count = 7000
2021-05-04 23:02:12.100 INFO 22120 --- [pool-1-thread-4]
c.c.springcloud.standard.lock.JedisLock LN:81 睡眠一下,重新开始,turn:3,剩余时间:
9586
2021-05-04 23:02:12.101 INFO 22120 --- [pool-1-thread-5]
c.c.springcloud.standard.lock.JedisLock LN:81 睡眠一下,重新开始,turn:3,剩余时间:
9585
2021-05-04 23:02:12.101 INFO 22120 --- [pool-1-thread-8]
c.c.springcloud.standard.lock.JedisLock LN:81 睡眠一下,重新开始,turn:3,剩余时间:
9585
2021-05-04 23:02:12.101 INFO 22120 --- [pool-1-thread-4]
c.c.springcloud.lock.RedisLockTest LN:50 count = 8000
2021-05-04 23:02:12.102 INFO 22120 --- [pool-1-thread-8]
c.c.springcloud.lock.RedisLockTest LN:50 count = 9000
2021-05-04 23:02:12.304 INFO 22120 --- [pool-1-thread-5]
c.c.springcloud.standard.lock.JedisLock LN:81 睡眠一下,重新开始,turn:4,剩余时间:
9383
2021-05-04 23:02:12.307 INFO 22120 --- [pool-1-thread-5]
c.c.springcloud.lock.RedisLockTest LN:50 count = 10000
10个线程每个累加1000为: = 10000
运行的时长为(ms):827.0
每一次执行的时长为(ms):0.0827

STW导致的锁过期问题

下面有一个简单的使用锁的例子,在 10 秒内占着锁:
//写数据到文件
public void writeData(filename,data){boolean locked=lock.tryLock(10,TimeUnit.SECONDS);if(!locked){throw'Failed to acquire lock';}try{//将数据写到文件var file=storage.readFile(filename);var updated=updateContents(file,data);storage.writeFile(filename,updated);}finally{lock.unlock();}
}
问题是:如果在写文件过程中,发生了 fullGC ,并且其时间跨度较长, 超过了 10 秒, 那么,分布式锁就自动释放了。
在此过程中, client2 抢到锁,写了文件。
client1 fullGC 完成后,也继续写文件, 注意,此时 client1 的并没有占用锁,此时写入会导致文件数 据错乱,发生线程安全问题,这就是STW导致的锁过期问题。

 

STW导致的锁过期问题,大概的解决方案 

 1: 模拟CAS乐观锁的方式,增加版本号(如下图中的token)
2watch dog自动延期机制 
客户端 1 加锁的锁 key 默认生存时间才 30 秒,如果超过了 30 秒,客户端 1 还想一直持有这把锁,怎么办?
简单!只要客户端 1 一旦加锁成功,就会启动一个 watch dog 看门狗, 他是一个后台线程,会每隔 10 秒检查一下,如果客户端 1 还持有锁 key ,那么就会不断的延长锁 key 的生存时间。

 redission,采用的就是这种方案, 此方案不会入侵业务代码

注意:
单机版的  watch dog 并不能解决 STW  的过期问题, 需要分布式版本的 watch dog , 独立的看门狗服务。
锁删除之后, 取消看门狗服务的对应的 key 记录, 当然,这就使得系统变得复杂, 还要保证看门狗服务的高并发、高可用、数据一致性的问题。

 

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

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

相关文章

uniapp问题归类

最近使用uniapp中&#xff0c;遇到了一些问题&#xff0c;这边mark下。 1. 启动页变形 设置启动页的时候发现在部分android手机上启动页被拉伸了&#xff0c;最后看了下官方建议使用9.png图 生成9.png地址&#xff0c;推荐图片大小为1080x2340 uniapp推荐官方地址传送门 我…

【Linux驱动层】iTOP-RK3568学习之路(四):杂项设备驱动框架

一、杂项设备驱动简介 在 Linux 中&#xff0c;把无法归类的五花八门的设备定义成杂项设备。相较于字符设备&#xff0c;杂项设备有以下两个优点: (1)节省主设备号:杂项设备的主设备号固定为 10&#xff0c;而字符设备不管是动态分配还是静态分配设备号&#xff0c;都会消耗一…

【leetcode面试经典150题】71. 对称二叉树(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

Golang | Leetcode Golang题解之第48题旋转图像

题目&#xff1a; 题解&#xff1a; func rotate(matrix [][]int) {n : len(matrix)// 水平翻转for i : 0; i < n/2; i {matrix[i], matrix[n-1-i] matrix[n-1-i], matrix[i]}// 主对角线翻转for i : 0; i < n; i {for j : 0; j < i; j {matrix[i][j], matrix[j][i]…

Nuxt3项目如何通过开启ssr让网页实现seo自由!

nuxt.config开启ssr # nuxt.config.tsexport default defineNuxtConfig({// 是否开启SSRssr: true }) 终端运行 npm run generate generate 预渲染应用程序的每个路由&#xff0c;并将结果存储为纯HTML文件。 "scripts": {"generate": "nuxt genera…

【Git教程】(十七)发行版交付 — 概述及使用要求,执行过程及其实现,替代解决方案 ~

Git教程 发行版交付 1️⃣ 概述2️⃣ 使用要求3️⃣ 执行过程及其实现3.1 预备阶段&#xff1a;创建 stable 分支3.2 预备并创建发行版3.3 创建补丁 4️⃣ 替代解决方案 对于每个项目或产品来说&#xff0c;发布版本的创建都需要一定的时间&#xff0c;其具体过程因各公司或组…

C++ Qt QMainWindow实现无边框窗口自定义标题栏可拖拽移动拉伸改变窗口大小

本篇博客介绍C Qt QMainWindow实现无边框窗口&#xff0c;适用于win10/win11系统。 QMainWindow相对于QWidget多了dockedwidget功能&#xff0c;跟多人可能更喜欢用QMainWindow做主窗口&#xff0c;如果不需要dockedwidget功能&#xff0c;QMainWindow与QWidget做主窗口基本无…

MATLAB实现蚁群算法栅格路径优化

蚁群算法是一种模拟自然界中蚂蚁觅食行为的优化算法&#xff0c;常用于解决路径规划问题。在栅格路径优化中&#xff0c;蚁群算法可以帮助找到从起点到终点的最优路径。以下是蚁群算法栅格路径优化的基本流程步骤&#xff1a; 初始化参数&#xff1a; (1)设置蚂蚁数量&#xff…

linux的“>”和“>>”

在Linux中&#xff0c;>和>>都是用于文件重定向的操作符&#xff0c;它们用于将命令的输出发送到文件中。 > 用于创建一个新文件或覆盖现有文件的内容。当你执行一个如 command > file.txt 的命令时&#xff0c;如果 file.txt 文件存在&#xff0c;它的内容将被…

2024最新版JavaScript逆向爬虫教程-------基础篇之深入JavaScript运行原理以及内存管理

目录 一、JavaScript运行原理1.1 前端需要掌握的三大技术1.2 为什么要学习JavaScript1.3 浏览器的工作原理1.4 浏览器的内核1.5 浏览器渲染过程1.6 认识JavaScript引擎1.7 V8引擎以及JavaScript的执行过程1.8 V8引擎执行过程 二、JavaScript的执行过程2.1 初始化全局对象2.2 执…

OriginPro作图之箱线图

前言 箱线图(Box-plot) 又称为盒须图、盒式图或箱线图&#xff0c;是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。 本文将结合实例阐述其意义和绘图过程。 箱线图简介 箱线图(Boxplot) 也称箱须图( Box-whisker Plot)&#xff0c;是利用数据中的五个统计量…

ffmpeg的安装以及使用

1.FFmpeg 的主要功能和特性&#xff1a; 格式转换&#xff1a;FFmpeg 可以将一个媒体文件从一种格式转换为另一种格式&#xff0c;支持几乎所有常见的音频和视频格式&#xff0c;包括 MP4、AVI、MKV、MOV、FLV、MP3、AAC 等。视频处理&#xff1a;FFmpeg 可以进行视频编码、解…

ArcGIS无法开始编辑TIN!开始编辑TIN显示灰色

ArcGIS无法开始编辑TIN&#xff01;开始编辑TIN显示灰色&#xff1f; 解决方案&#xff01; 1、确认自定义——扩展模块中空间分析、3D分析模块勾选。 2、确认以上后&#xff0c;还是不能编辑的话&#xff0c;我们可以调出 3D分析分析工具条&#xff0c;你就会发现。TIN编辑工…

Window + Ubuntu 双系统无Ubuntu Bios 启动项

文章目录 安装硬盘位置不重要&#xff01;&#xff01;&#xff01;&#xff08;但是我安装在了第二张HDD&#xff09;问题是多盘分位置会导致磁盘主分区变成了简单卷 Bios Ubuntu 启动项修复参考Ubuntu安装U盘进入Try Ubuntu 使用Terminal修复完提示Disable Secure Boot进入Te…

【存储】cosbench对象存储测试工具

目录 简略说明 原理 用法 详细说明 简介 用法 一 安装 二 简单验证 三 编写配置文件 四 提交配置文件下IO 五 测试结果查看 结果概览 查看详情 每秒钟的io情况查看 工作负载配置 参数配置&#xff08;controller和driver&#xff09; 查看错误的方法和错误记录 查看错误的方法 …

【匹配】匈牙利匹配算法

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 匈牙利匹配算法 1. 正文 1.1 基础概念 二分图 顶点分为两个集合&#xff0c;集合间顶点相连&#xff0c;集合内点不相连 匹配 一个匹配就是一个边的…

Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC(231017)

前言 Oracle 一键安装脚本&#xff0c;演示 Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC&#xff08;231017&#xff09;过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&…

代理IP干货:如何正确使用防范风险?

在今天的数字时代&#xff0c;代理IP地址已成为互联网世界中不可或缺的一部分。无论您是寻求绕过地理限制、保护个人隐私还是执行网络任务&#xff0c;代理IP地址都发挥着关键作用。我们将为您探讨代理IP地址的重要性以及如何防范潜在的风险和威胁。 一、代理IP地址的潜在风险 …

STM32H7独立看门狗 (IWDG)的应用方法介绍

目录 概述 1 认识独立看门狗 (IWDG) 1.1 定义独立看门狗 (IWDG) 1.2 IWDG 主要特性 2 IWDG 功能说明 2.1 IWDG 框图 2.2 IWDG 内部信号 2.3 窗口选项 2.3.1 Enable WIN IWDG 2.3.2 Disable WIN IWDG 2.4 硬件看门狗 2.5 低功耗冻结 2.6 停止和待机模式下的行为 …

网工学习云计算HCIE感受如何?

作为一名网工&#xff0c;我经常会在各种网络论坛里查询搜索一些网络技术资料&#xff0c;以及跟论坛里的网友交流讨论平时在工作、学习中遇到的问题、故障&#xff0c;因此也经常能在论坛的首页看到誉天的宣传信息。机缘巧合之下关注了誉天的B站号&#xff0c;自从关注了誉天的…