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

Redis --- 分布式锁的使用

我们在上篇博客高并发处理 --- 超卖问题+一人一单解决方案讲述了两种锁解决业务的使用方法,但是这样不能让锁跨JVM也就是跨进程去使用,只能适用在单体项目中如下图:

为了解决这种场景,我们就需要用一个锁监视器对全部集群进行监视:

 这就引出了分布式锁的概念。


什么是分布式锁?


分布式锁是一种在分布式系统中用于控制多个实例(如多个微服务节点)对共享资源的并发访问的机制。分布式锁的核心目标是避免在并发情况下出现数据不一致的问题,比如多个线程同时对同一数据进行操作时,可能会导致数据竞争、脏数据或者业务逻辑错误。

总而言之:分布式锁就是满足分布式系统或者集群模式下多进程可见并且互斥的锁。


为什么要使用分布式锁?


在单机环境中,使用常规的同步机制(如 Java 中的 synchronized)可以避免并发问题,但在分布式系统中,多个服务或应用实例可能同时操作共享的资源。传统的同步方法无法跨机器或进程工作,因此需要引入分布式锁来确保在多台机器中,只有一个节点可以操作共享资源,其他节点必须等待

分布式锁的核心实现多进程之间互斥

分布式锁的实现方式有很多种,主要使用分布式系统中能共享的工具和技术。常见的实现方式包括基于 数据库RedisZookeeper 等的分布式锁。

那么接下来我们就对基于Redis实现分布式锁进行讲解:


Redis 是目前使用最广泛的分布式锁实现方式之一,因为 Redis 提供了高性能的存储和锁机制,适合高并发的场景。 

我们在SimpleRedisLock类去继承ILock接口,以此来实现获取释放锁的过程:

import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock{private static final String KEY_PREFIX = "lock:";private String keyName;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String keyName, StringRedisTemplate stringRedisTemplate) {this.keyName = keyName;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {// 获取当前线程标识long threadId = Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + keyName, threadId + "", timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + keyName);}
}

 随后我们通过自定义的锁工具类进行使用:

首先new一个工具类对象,通过对象调用获取锁方法,随后判断是否获得锁,之后使用动态代理获得代理对象后,使用代理对象调用事务方法。


业务内容请点击下面地址:高并发处理 --- 超卖问题+一人一单解决方案

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Resourceprivate StringRedisTemplate stringRedisTemplate;public Result seckillVoucherBySelf(Long voucherId) {// 业务逻辑...Long userId = UserHolder.getUser().getId();
//        synchronized (userId.toString().intern()){
//            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); // 获取当前类的代理对象  (需要引入aspectjweaver依赖,并且在实现类加入@EnableAspectJAutoProxy(exposeProxy = true)以此来暴露代理对象)
//            return proxy.createVoucherOrder(voucherId);
//        }SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);boolean isLock = lock.tryLock(1200);// 判断是否获取锁成功if (!isLock) {// 获取锁失败,返回错误或重试return Result.fail("不允许重复下单");}try {IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); // 获取当前类的代理对象  (需要引入aspectjweaver依赖,并且在实现类加入@EnableAspectJAutoProxy(exposeProxy = true)以此来暴露代理对象)return proxy.createVoucherOrder(voucherId);} finally {lock.unlock();}}@Transactionalpublic Result createVoucherOrder(Long voucherId) {// 业务逻辑...    }
}

 但是这样会出现一种情况:虽然我们的线程1还没有结束,但是由于锁设定的时间到期而被释放销毁,此时线程2就能够开始获取锁,过一段时间后线程1结束就会要释放锁,这个时候释放的锁就是线程2刚加上去的锁,所以导致线程安全问题。

解决方案:在获取锁的过程存入线程标识(可用UUID表示),以便于在释放锁去判断这个锁是不是自己的,如果是则释放,如果不是则不释放。(总而言之:设置线程标识的目的是判断释放锁是不是同一个线程获取的,以此来避免类似线程2创建的锁被线程1释放引发的线程安全问题

 

 那么我们就需要再释放锁的方法中加入判断逻辑:

public class SimpleRedisLock implements ILock{private static final String KEY_PREFIX = "lock:"; // 锁的前缀private static final String ID_PREFIX = UUID.randomUUID().toString() + "-"; // 线程标识的前缀private String keyName;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String keyName, StringRedisTemplate stringRedisTemplate) {this.keyName = keyName;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {// ...}@Overridepublic void unlock() {// 获取线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中标识String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + keyName);// 判断标识是否一致if(threadId.equals(id)){// 如果一致则释放锁stringRedisTemplate.delete(KEY_PREFIX + keyName);}}
}

 但是还是有问题,请看下图:

如果出现一种情况,在线程1事务结束后释放锁,这时按照我们上述逻辑,会判断锁key与线程标识是否一致,随后假如非常凑巧,这个时候发生了线程阻塞并且锁还正好到期而释放,那么这个时候线程2就会获取锁,随后线程1阻塞结束后就会执行释放锁的代码,那么这个时候刚好就会将线程2创建的锁释放掉。 

所以这个时候我们就必须保证判断锁标识与释放锁是原子性的。

这个时候我们就可以使用 lua 脚本编写多条Redis命令,从而确保多条命令执行时的原子性

可以参考下面网站学习:Lua 教程 | 菜鸟教程



什么是Lua脚本?


  LUA 是一种轻量级的脚本语言,具有简单、易于嵌入等特点。在 Redis 中,LUA 脚本用于实现服务器端的逻辑操作,它可以在 Redis 服务器上执行,并且具有 原子性,即 Redis 在执行 LUA 脚本时,会确保脚本中的所有操作都要么全部执行成功,要么全部不执行

  Redis 支持通过 EVAL 命令执行 LUA 脚本,使用这种方式可以实现一些复杂的操作,避免了在客户端执行多个 Redis 命令时可能发生的网络延迟和操作不一致问题。


LUA 脚本如何确保原子性?


在 Redis 中,LUA 脚本是原子性执行的,即在脚本执行过程中,其他 Redis 命令不会被插入。这是因为 Redis 会一次性地加载、解析并执行整个脚本,直到脚本执行完毕,Redis 才会处理其他命令。因此,在一个脚本中,所有的操作都在 Redis 服务器端执行,不会中断,也不会被其他命令打断。

这就意味着在执行 LUA 脚本时,Redis 会确保以下几点:

  1. 脚本中的所有命令要么都执行,要么都不执行:例如,如果一个脚本修改了多个 Redis 键值,并且中途遇到了错误,那么整个脚本的操作将会失败,Redis 会保证在执行过程中没有部分操作成功,部分操作失败的情况。
  2. 不需要担心并发问题:多个客户端同时执行相同的 LUA 脚本时,它们会按顺序依次执行,不会出现竞争条件。
  3. 性能提升:将多个操作通过一个 LUA 脚本提交给 Redis 执行,避免了多次网络请求,提高了性能。

LUA 脚本在 Redis 中的使用方式


Redis 通过 EVALEVALSHA 命令执行 LUA 脚本:

  1. EVAL 命令:直接执行脚本。
  2. EVALSHA 命令:执行已加载的脚本(通过 SCRIPT LOAD 命令加载脚本),使用脚本的 SHA1 校验和来标识脚本。

一个 LUA 脚本的示例:

local current = redis.call('GET', KEYS[1])
if current thenredis.call('SET', KEYS[1], current + 1)
elseredis.call('SET', KEYS[1], 1)
end
return redis.call('GET', KEYS[1])

假设我们有一个 Lua 脚本要将一个键的值更新为某个参数:

return redis.call('set', KEYS[1], ARGV[1])

在 Redis Lua 脚本中,KEYS[]ARGV[] 是两个特殊的数组,用来传递键和值。它们的内容由 execute 方法的 keysargs 参数提供。

  1. KEYS[] 数组用于传递 Redis 中的键,通常在 Lua 脚本中用于表示数据库中需要操作的 Redis 键。你可以在 Lua 脚本中通过 KEYS[1]KEYS[2] 等方式来引用这些键。
  2. ARGV[] 数组用于传递非键值的参数。这些参数通常用于脚本中的计算、条件判断等,而不是 Redis 键。例如,ARGV 可以用来传递数字、字符串等作为参数传给 Redis 命令。

 那么我们将java代码提取改造成Lua脚本:

// 获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁中标识
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + keyName);
// 判断标识是否一致
if(threadId.equals(id)){// 如果一致则释放锁stringRedisTemplate.delete(KEY_PREFIX + keyName);
}

那么改造后的Lua脚本:

-- 锁的key
local key = KEYS[1]
-- 当前线程标识
local threadId = ARGV[1]-- 获取锁中的线程标识  get key
local id = redis.call('get', key)
-- 比较线程标识与锁中的标识是否一致
if(id == threadId) then-- 释放锁 del keyreturn redis.call('del', key)
end
return 0

我们下载EmmyLua插件,在/resource/unlock.lua文件中将上面代码粘贴进入即可。

随后我们在unlock方法内进行修改,按住Ctrl+H在右面即可查看类型的全部继承类。

为了调用Lua脚本文件,我们可以使用 RedisTemplateexecute 方法,这样可以直接执行 Lua 脚本,并传递相应的参数。RedisTemplateexecute 方法允许你执行自定义的 Redis 命令,包括 Lua 脚本。

在Java底层,execute()方法的源码如下:

public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {return this.scriptExecutor.execute(script, keys, args);
}

1.RedisScript<T> script: RedisScript 是一个用于表示 Redis 脚本(通常是 Lua 脚本)的对象。T 是脚本执行结果的返回类型。你可以将 Lua 脚本作为 RedisScript 的参数传入,执行该脚本后,它会返回一个类型为 T 的结果。

2.List<K> keyskeys 参数是 Lua 脚本的 KEYS[] 数组,对应传入 Redis 的键。这些键是你在 Lua 脚本中使用的 KEYS[] 部分。Redis 脚本允许你使用多个键(最多可以传递 16 个键),这些键是传递给 Lua 脚本的。

3.Object... argsargs 是可变参数,表示 Lua 脚本中的 ARGV[] 数组。在 Lua 脚本中,ARGV[] 用于传递参数,这些参数通常是值(字符串、数字等),而不是 Redis 键。args 可以是任意类型的参数。

public class SimpleRedisLock implements ILock{private static final String KEY_PREFIX = "lock:"; // 锁的前缀private static final String ID_PREFIX = UUID.randomUUID().toString() + "-"; // 线程标识的前缀private String keyName;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String keyName, StringRedisTemplate stringRedisTemplate) {this.keyName = keyName;this.stringRedisTemplate = stringRedisTemplate;}// Lua代码private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; // 泛型内填入返回值类型static { // 静态属性要使用静态代码块进行初始化UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setResultType(Long.class);UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));}// 调用Lua脚本@Overridepublic void unlock() {// 调用Lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + keyName),ID_PREFIX + Thread.currentThread().getId()); }
}

但是基于我们自己创建的锁有以下几个方面的问题:

  1. 不可重入:同一个线程无法多次获取同一把锁。
  2. 不可重试:获取锁只尝试一次就返回false,没有重试机制。
  3. 超时释放:锁超时释放虽然可以避免死锁,但是如果是业务执行耗时较长,也会导致锁的释放,存在安全隐患问题。
  4. 主从一致性:如果Redis提供了主从集群,主从同步存在延迟,当主宕机时,如果从并同步主中的锁数据,则会出现锁实现。

那么这个时候我们就需要使用Redission来实现分布式锁:


下面是官方文档: Redission --- 快速入门


Redisson 是一个基于 Redis 的客户端,它提供了分布式锁的功能,适用于解决分布式系统中资源的竞争问题。我们可以通过 Redisson 来实现分布式锁,确保同一时刻只有一个实例能处理某个资源或任务。

首先引入依赖:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.32.0</version>
</dependency>

之后配置Redission的客户端:

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 RedissonConfig {@Beanpublic RedissonClient redissonClient() {// 配置 Redisson 客户端Config config = new Config();// 添加redis的地址,也可使用config.useClusterServers()添加集群地址config.useSingleServer().setAddress("redis://localhost:6379").setPassword("password");  // 如果有密码return Redisson.create(config);}
}

之后是使用Redission实现的分布式锁的案例

Redisson 提供了 RLock 对象,允许在业务逻辑中加锁和释放锁。

例如,假设有一个需要保证只允许一个实例执行的业务操作,可以这样做:

@Service
public class BusinessService {@Autowiredprivate RedissonClient redissonClient;public void performBusinessLogic() {// 获取锁,锁名为 "myLock",设置等待时间和锁的持有时间RLock lock = redissonClient.getLock("myLock");try {// 尝试获取锁,最多等待 10 秒,锁的持有时间为 30 秒// tryLock(long waitTime, long leaseTime, TimeUnit unit) if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {try {// 执行需要加锁的业务逻辑System.out.println("Executing business logic...");// 例如处理任务,更新数据库等} finally {// 释放锁lock.unlock();}} else {System.out.println("Could not acquire the lock, try again later.");}} catch (InterruptedException e) {e.printStackTrace();}}
}

那么上面代码使用Redission实现的分布式锁为:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redissonClient;public Result seckillVoucherBySelf(Long voucherId) {// ...Long userId = UserHolder.getUser().getId();
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        boolean isLock = lock.tryLock(1200);RLock lock = redissonClient.getLock("lock:order:" + userId);boolean isLock = lock.tryLock();// 判断是否获取锁成功if (!isLock) {// 获取锁失败,返回错误或重试return Result.fail("不允许重复下单");}try {IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); // 获取当前类的代理对象  (需要引入aspectjweaver依赖,并且在实现类加入@EnableAspectJAutoProxy(exposeProxy = true)以此来暴露代理对象)return proxy.createVoucherOrder(voucherId);} finally {lock.unlock();}}
}

下图讲述了Redission实现分布式锁的原理: 

 

那么如何实现可重入锁呢?


我们先了解什么是可重入锁

Redis 的 可重入锁 是指同一个线程可以多次获得锁,而不会导致死锁。Redisson 提供了内置的可重入锁(RLock)功能,能够自动支持锁的可重入性。它的原理是通过记录每个线程对锁的持有次数来实现的,每当一个线程重新获取锁时就会增加持有次数释放锁时会检查持有次数,直到持有次数为 0 才会真正释放锁

我们可以按照下面逻辑进行分析:

当然,我们这里的读写锁的操作同样要保证原子性,那么这个时候就需要写Lua脚本:

(1)判断锁是否存在:①不存在:获取锁,设置有效期   ②存在且是自己的:重入次数+1,设置有效期

local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断是否存在
if(redis.call('exist',key) == 0) then-- 不存在,获取锁redis.call('hset',key,threadId,'1');-- 设置有效期redis.call('expire',key,releaseTime);
end;
-- 锁已经存在,判断threadId是否是自己
if(redis.call('hexists',key,threadId) == 1) then-- 不存在,获取锁,重入次数+1redis.call('hincrby',key,threadId,1);-- 设置有效期redis.call('expire',key,releaseTime);return 1; -- 返回结果
end;
return 0; -- 代码走到这里,则说明获取锁的不是自己,获取锁失败

 (2)判断锁是否是自己的:

  1.  不是就直接返回 nil
  2.  是自己锁就 -1 并判断重入次数是否为 0:① >0则重置有效期   ② =0则直接删除锁
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断当前锁是否还是被自己持有
if(redis.call('hexists',key,threadId) == 0) thenreturn nil; -- 如果已经不是自己,则直接返回
end;
-- 是自己的锁,则冲入次数-1
local count = redis.call('hincrby',key,threadId,-1);
-- 判断重入次数是否已经=0
if(count > 0) then-- 不为0,说明不能释放锁,则重置有效期然后返回redis.call('expire',key,releaseTime);return nil;
else -- =0说明可以释放锁,直接删除锁redis.call('del',key);return nil;
end;

在使用分布式锁时,尤其是可重入锁,务必注意以下几点:

  • 锁的自动释放:设置 releaseTime 时间确保锁在指定时间内被释放,防止线程因为异常或超时没有释放锁而导致死锁。
  • 及时释放锁:在业务执行完后,一定要确保调用 unlock() 方法释放锁。尤其在复杂业务场景下,确保每个分支都有对应的释放逻辑,避免丢锁。

相关文章:

Redis --- 分布式锁的使用

我们在上篇博客高并发处理 --- 超卖问题一人一单解决方案讲述了两种锁解决业务的使用方法&#xff0c;但是这样不能让锁跨JVM也就是跨进程去使用&#xff0c;只能适用在单体项目中如下图&#xff1a; 为了解决这种场景&#xff0c;我们就需要用一个锁监视器对全部集群进行监视…...

LeetCode100之全排列(46)--Java

1.问题描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案 示例1 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例2 输入&#xff1a;nums [0,1] 输出&#xf…...

goframe 博客分类文章模型文档 主要解决关联

goframe 博客文章模型文档 模型结构 (BlogArticleInfoRes) BlogArticleInfoRes 结构体代表系统中的一篇博客文章&#xff0c;包含完整的元数据和内容管理功能。 type BlogArticleInfoRes struct {Id uint orm:"id,primary" json:"id" …...

【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南

文章目录 &#x1f30d;一. WEB 开发❄️1. 介绍 ❄️2. BS 与 CS 开发介绍 ❄️3. JavaWeb 服务软件 &#x1f30d;二. Tomcat❄️1. Tomcat 下载和安装 ❄️2. Tomcat 启动 ❄️3. Tomcat 启动故障排除 ❄️4. Tomcat 服务中部署 WEB 应用 ❄️5. 浏览器访问 Web 服务过程详…...

安卓日常问题杂谈(一)

背景 关于安卓开发中&#xff0c;有很多奇奇怪怪的问题&#xff0c;有时候这个控件闪一下&#xff0c;有时候这个页面移动一下&#xff0c;这些对于快速开发中&#xff0c;去查询&#xff0c;都是很耗费时间的&#xff0c;因此&#xff0c;本系列文章&#xff0c;旨在记录安卓…...

Kitchen Racks 2

Kitchen Racks 2 吸盘置物架 Kitchen Racks-CSDN博客...

嵌入式学习笔记-杂七杂八

文章目录 连续波光纤耦合激光器工作原理主要特点应用领域设计考虑因素 数值孔径&#xff08;Numerical Aperture&#xff0c;简称NA&#xff09;数值孔径的定义数值孔径的意义数值孔径的计算示例数值孔径与光纤 四象限探测器检测目标方法四象限划分检测目标的步骤1. 数据采集2.…...

14-7C++STL的stack容器

&#xff08;一&#xff09;stack容器的入栈与出栈 &#xff08;1&#xff09;stack容器的简介 stack堆栈容器&#xff0c;“先进后出”的容器&#xff0c;且stack没有迭代器 &#xff08;2&#xff09;stack对象的默认构造 stack采用模板类实现&#xff0c;stack对象的默认…...

Vue 3 中的响应式系统:ref 与 reactive 的对比与应用

Vue 3 的响应式系统是其核心特性之一&#xff0c;它允许开发者以声明式的方式构建用户界面。Vue 3 引入了两种主要的响应式 API&#xff1a;ref 和 reactive。本文将详细介绍这两种 API 的用法、区别以及在修改对象属性和修改整个对象时的不同表现&#xff0c;并提供完整的代码…...

物业巡更系统助推社区管理智能化与服务模式创新的研究与应用

内容概要 在现代社区管理中&#xff0c;物业巡更系统扮演着至关重要的角色。首先&#xff0c;我们先来了解一下这个系统的概念与发展背景。物业巡更系统&#xff0c;顾名思义&#xff0c;是一个用来提升物业管理效率与服务质量的智能化工具。随着科技的发展&#xff0c;传统的…...

windows蓝牙驱动开发-生成和发送蓝牙请求块 (BRB)

以下过程概述了配置文件驱动程序生成和发送蓝牙请求块 (BRB) 应遵循的一般流程。 BRB 是描述要执行的蓝牙操作的数据块。 生成和发送 BRB 分配 IRP。 分配BRB&#xff0c;请调用蓝牙驱动程序堆栈导出以供配置文件驱动程序使用的 BthAllocateBrb 函数。&#xff1b;初始化 BRB…...

Linux网络之序列化和反序列化

目录 序列化和反序列化 上期我们学习了基于TCP的socket套接字编程接口&#xff0c;并实现了一个TCP网络小程序&#xff0c;本期我们将在此基础上进一步延伸学习&#xff0c;实现一个网络版简单计算器。 序列化和反序列化 在生活中肯定有这样一个情景。 上图大家肯定不陌生&a…...

linux设置mysql远程连接

首先保证服务器开放了mysql的端口 然后输入 mysql -u root -p 输入密码后即可进入mysql 然后再 use mysql; select user,host from user; update user set host"%" where user"root"; flush privileges; 再执行 select user,host from user; 即可看到变…...

react-native网络调试工具Reactotron保姆级教程

在React Native开发过程中&#xff0c;调试和性能优化是至关重要的环节。今天&#xff0c;就来给大家分享一个非常强大的工具——Reactotron&#xff0c;它就像是一个贴心的助手&#xff0c;能帮助我们更轻松地追踪问题、优化性能。下面就是一份保姆级教程哦&#xff01; 一、…...

erase() 【删数函数】的使用

**2025 - 01 - 25 - 第 48 篇 【函数的使用】 作者(Author) 文章目录 earse() - 删除函数一. vector中的 erase1 移除单个元素2 移除一段元素 二. map 中的erase1 通过键移除元素2 通过迭代器移除元素 earse() - 删除函数 一. vector中的 erase vector 是一个动态数组&#x…...

性能测试丨内存火焰图 Flame Graphs

内存火焰图的基本原理 内存火焰图是通过分析堆栈跟踪数据生成的一种图形化表现&#xff0c;能够展示应用程序在运行时各个函数的内存占用情况。火焰图的宽度代表了函数占用的内存量&#xff0c;而火焰的高度则显示了函数在调用栈中的层级关系。通过这种可视化方式&#xff0c;…...

AIGC的企业级解决方案架构及成本效益分析

AIGC的企业级解决方案架构及成本效益分析 一,企业级解决方案架构 AIGC(人工智能生成内容)的企业级解决方案架构是一个多层次、多维度的复杂系统,旨在帮助企业实现智能化转型和业务创新。以下是总结的企业级AIGC解决方案架构的主要组成部分: 1. 技术架构 企业级AIGC解决方…...

Linux 入门 常用指令 详细版

欢迎来到指令小仓库&#xff01;&#xff01; 宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 什么是指令&#xff1f; 指令和可执行程序都是可以被执行的-->指令就是可执行程序。 指令一定是在系统的每一个位置存在的。 1.ls指令 语法&#xff1a; ls [选项][目…...

【R语言】流程控制

R语言中&#xff0c;常用的流程控制函数有&#xff1a;repeat、while、for、if…else、switch。 1、repeat循环 repeat函数经常与 break 语句或 next 语句一起使用。 repeat ({x <- sample(c(1:7),1)message("x ", x, ",你好吗&#xff1f;")if (x …...

猿人学第一题 js混淆源码乱码

首先检查刷新网络可知&#xff0c;m参数被加密&#xff0c;这是一个ajax请求 那么我们直接去定位该路径 定位成功 观察堆栈之后可以分析出来这应该是一个混淆&#xff0c;我们放到解码平台去还原一下 window["url"] "/api/match/1";request function…...

计算机组成原理(2)王道学习笔记

数据的表示和运算 提问&#xff1a;1.数据如何在计算机中表示&#xff1f; 2.运算器如何实现数据的算术、逻辑运算&#xff1f; 十进制计数法 古印度人发明了阿拉伯数字&#xff1a;0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#…...

【AI日记】25.01.26

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛&#xff1a;Forecasting Sticker Sales 读书 书名&#xff1a;自由宪章 律己 AI&#xff1a;6 小时作息&#xff1a;00:30-8:30短视频&#xff1a;大于 1 小时读书和写作&a…...

三. Redis 基本指令(Redis 快速入门-03)

三. Redis 基本指令(Redis 快速入门-03) 文章目录 三. Redis 基本指令(Redis 快速入门-03)1. Redis 基础操作&#xff1a;2. 对 key(键)操作&#xff1a;3. 对 DB(数据库)操作4. 最后&#xff1a; Reids 指定大全(指令文档)&#xff1a; https://www.redis.net.cn/order/ Redis…...

设计模式的艺术-代理模式

结构性模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解代理模式 代理模式&#xff08;Proxy Pattern&#xff09;&#xff1a;给某一个对象提供一个代理&#xff0c;并由代理对象控制对原对象的引用。代理模式是一种对象结构型模式。 代理模式类型较多…...

C#新语法

目录 顶级语句&#xff08;C#9.0&#xff09; using 全局using指令&#xff08;C#10.0&#xff09; using资源管理问题 using声明&#xff08;C#8.0&#xff09; using声明陷阱 错误写法 正确写法 文件范围的命名空间声明&#xff08;C#10.0&#xff09; 可空引用类型…...

微信小程序压缩图片

由于wx.compressImage(Object object) iOS 仅支持压缩 JPG 格式图片。所以我们需要做一下特殊的处理&#xff1a; 1.获取文件&#xff0c;判断文件是否大于设定的大小 2.如果大于则使用canvas进行绘制&#xff0c;并生成新的图片路径 3.上传图片 async chooseImage() {let …...

通义灵码插件保姆级教学-IDEA(安装及使用)

一、JetBrains IDEA 中安装指南 官方下载指南&#xff1a;通义灵码安装教程-阿里云 步骤 1&#xff1a;准备工作 操作系统&#xff1a;Windows 7 及以上、macOS、Linux&#xff1b; 下载并安装兼容的 JetBrains IDEs 2020.3 及以上版本&#xff0c;通义灵码与以下 IDE 兼容&…...

windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】

注意版本依赖【本实验版本如下】 Hadoop 3.1.1 spark 2.3.2 scala 2.11 1.依赖环境 1.1 java 安装java并配置环境变量【如果未安装搜索其他教程】 环境验证如下&#xff1a; C:\Users\wangning>java -version java version "1.8.0_261" Java(TM) SE Runti…...

倍频增量式编码器--角度插值法输出A,B(Aangular Interpolation)

问题是&#xff1a; 最大速度&#xff0c;周期刻度&#xff0c;最小细分刻度&#xff0c;可以计算得到&#xff1a; 结论&#xff1a; 按照最高速度采样&#xff1b;数字A,B输出间隔时间&#xff1a;按照计算角度 插入细分角度运算算时间&#xff08;最快速度&#xff09;&a…...

LSM对于特殊数据的优化手段

好的&#xff0c;我现在需要帮助用户理解如何针对不同的特殊工作负载优化LSM树结构。用户提到了四种情况&#xff1a;时态数据、小数据、半排序数据和追加为主的数据。我需要分别解释每种情况下的优化方法&#xff0c;并参考用户提供的LHAM的例子&#xff0c;可能还有其他例子。…...