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

【Redis】如何实现一个合格的分布式锁

文章目录

    • 参考
    • 1、概述
    • 2、Redis粗糙实现
    • 3、遗留问题
      • 3.1、误删情况
      • 3.2、原子性保证
      • 3.3、超时自动解决
      • 3.4、总结
    • 4、Redis实现优缺
    • 5、集群问题
      • 5.1、主从集群
      • 5.2、集群脑裂
    • 6、RedLock
    • 7、Redisson
      • 7.1、简单实现
      • 7.2、看门狗机制

参考

  1. Redisson实现Redis分布式锁的N种姿势 (qq.com)
  2. 小林coding (xiaolincoding.com)

1、概述

在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用 synchronized 语法和 ReetrantLock 去保证,这实际上是本地锁的方式。而在如今分布式架构的热潮下,如何保证不同节点的线程同步执行呢?

实际上,对于分布式场景,我们可以使用分布式锁,分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。如下图所示:

img

分布式锁一般有如下的特点:

  • 互斥性: 同一时刻只能有一个线程持有锁
  • 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
  • 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
  • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
  • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

2、Redis粗糙实现

Redis 本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁,而且 Redis 的读写性能高,可以应对高并发的锁操作场景。

Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」,所以可以用它来实现分布式锁:

  • 如果key不存在,则显示插入成功,可以用来表示加锁成功;

  • 如果key存在,则会显示插入失败,可以用来表示加锁失败;

  • 当需要解锁时,将对应的key删除即可解锁成功;

  • 同时为了避免死锁,我们还需要加上过期时间。

针对以上过程描述,我们就可以得到一个十分粗糙的分布式锁实现:

// 尝试获得锁
if (setnx(key, 1) == 1){// 获得锁成功,设置过期时间expire(key, 30)try {//TODO 业务逻辑} finally {// 解锁del(key)}
}

但这种实现方式就是合格的分布式锁了吗?相信大家都注意到了我在说这种方式的时候,是说这种方式是一种十分粗糙的实现方式,这主要存在以下问题:

  1. 多条命令的操作是非原子性的,可能会导致死锁的产生;
  2. 存在锁误解除的可能;
  3. 业务超时自动解锁导致并发问题;
  4. 实现的分布式锁不可重入

针对上述问题,下面将会一一解决得到一个合格的分布式锁。

3、遗留问题

3.1、误删情况

在以下情况下会出现误删情况:

  • 持有锁的线程1在锁的内部出现了阻塞,导致他的锁TTL到期从而锁自动释放;
  • 此时线程2也来尝试获取锁,由于线程1已经释放了锁,所以线程2可以拿到;
  • 但是现在线程1阻塞完了,继续往下执行,要开始释放锁了;
  • 那么此时就会将属于线程2的锁释放,这就是误删别人锁的情况。

img

对应的解决方案也很简单,既然是一个线程误删了别人的锁,就相当于把别人的厕所门给误开了,那么在开门之前校验一下这扇门是不是自己关上的不就好了:

  • 在存入锁的时候,放入自己的线程标识,在删除锁的时候,判断当前这把锁是不是自己存入的;
  • 如果是,则进行删除;
  • 如果不是,则不进行删除。
// 尝试获得锁
if (setnx(key, "当前线程号") == 1){// 获得锁成功,设置过期时间expire(key, 30);try {//TODO 业务逻辑} finally {// 解锁if ("当前线程号".equals(get(key))) {del(key);}}
}

以上便是解决误删方案的伪代码实现。

同时,这种方式也能够将分布式锁改造成可重入的分布式锁,在获取锁的时候判断一下是否是当前线程获取的锁,锁标识自增便可。

3.2、原子性保证

前面说到,SETNXEXPIRE 操作是非原子性的,那么如果 SETNX 成功,还未设置锁超时时间时,由于服务器挂掉、重启或网络问题等原因,导致 EXPIRE 命令没有执行,锁没有设置超时时间就有可能会导致死锁产生。

同时,对于上面解决的误删问题,如果以下极端情况同样会出现并发问题:

  • 假设线程1已经获取了锁,在判断标识一致之后,准备释放锁的时候,又出现了阻塞(例如JVM垃圾回收机制);
  • 于是锁的TTL到期了,自动释放了;
  • 那么现在线程2趁虚而入,拿到了一把锁;
  • 但是线程1的逻辑还没执行完,那么线程1就会执行删除锁的逻辑;
  • 但是在阻塞前线程1已经判断了标识一致,所以现在线程1把线程2的锁给误删了;
  • 那么就相当于判断标识那行代码没有起到作用;
  • 因为线程1的拿锁,判断标识,删锁,不是原子操作,所以我们要防止刚刚的情况。

img

对于Redis中并没有对应的原子性API提供给我们进行调用,但是我们可以通过Lua脚本对Redis功能进行拓展。

-- 过期时间设置
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1) then return 0;
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1;-- 删除锁
-- 比较锁中的线程标识与线程标识是否一致
if (redis.call('get', KEYS[1]) == ARGV[1]) then-- 一致则释放锁return redis.call('del', KEYS[1])
end;
return 0

以上就是原子性保证的lua脚本实现,通过Java调用call方法执行lua脚本即可通过lua脚本实现原子性操作从而解决该问题。

3.3、超时自动解决

虽然上面解决误删和原子性问题,但是如果获取锁的线程阻塞时间超过了设置的TTL,那么该自动解锁还是得自动解锁。

对于这种情况,一个简单粗暴的方法就是把过期时间设置的长长的,在设置的TTL内,能够保证我这个逻辑一定能够执行完。但是这种方式和不设置TTL一个鬼样,如果发生意外宕机之类的话,下一个线程将会阻塞很长时间,十分不优雅。

因此针对这个问题,我们可以给线程单独开一个守护线程,去检测当前线程运行情况,如果TTL即将到期,由守护线程对TTL进行续期,保证当前线程能够正确的执行完业务逻辑。

img

3.4、总结

综上所述,基于 Redis 节点实现分布式锁时,我们至少需要实现以下需求:

  • 加锁/解锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但需要以原子操作的方式完成;
  • 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放出现死锁,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置其过期时间;
  • 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,出现误释放操作,所以,我们使用 SET 命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端;

4、Redis实现优缺

基于 Redis 实现分布式锁的优点

  1. 性能高效。这是选择缓存实现分布式锁最核心的出发点。
  2. 实现方便。很多研发工程师选择使用 Redis 来实现分布式锁,很大成分上是因为 Redis 提供了 setnx 方法,实现分布式锁很方便。
  3. 避免单点故障。因为 Redis 是跨集群部署的,自然就避免了单点故障。

基于 Redis 实现分布式锁的缺点

  • 超时时间不好设置。如果锁的超时时间设置过长,会影响性能,如果设置的超时时间过短会保护不到共享资源。对于这种情况可以使用前面提及到的守护线程进行续期操作使得锁得过期时间得到保障;
  • Redis 主从复制模式中的数据是异步复制的,这样导致分布式锁的不可靠性。如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。

5、集群问题

5.1、主从集群

为了保证 Redis 的可用性,一般采用主从方式部署。主从数据同步有异步和同步两种方式,Redis 将指令记录在本地内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一致的状态,一边向主节点反馈同步情况。

如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:

  1. 在Redis的master节点上拿到了锁;
  2. 但是这个加锁的key还没有同步到slave节点;
  3. master故障,发生故障转移,slave节点升级为master节点;
  4. 导致锁丢失。

5.2、集群脑裂

集群脑裂指因为网络问题,导致 Redis master 节点跟 slave 节点和 sentinel 集群处于不同的网络分区,因为 sentinel 集群无法感知到 master 的存在,所以将 slave 节点提升为 master 节点,此时存在两个不同的 master 节点。Redis Cluster 集群部署方式同理。

总结来说脑裂就是由于网络问题,集群节点之间失去联系。主从数据不同步;重新平衡选举,产生两个主服务。等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于会从节点会清空自己的缓冲区,所以导致之前客户端写入的数据丢失了。

当不同的客户端连接不同的 master 节点时,两个客户端可以同时拥有同一把锁。

6、RedLock

为了保证集群环境下分布式锁的可靠性,Redis 官方已经设计了一个分布式锁算法 Redlock(红锁)。

它是基于多个 Redis 节点的分布式锁,即使有节点发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。官方推荐是至少部署 5 个 Redis 节点,而且都是主节点,它们之间没有任何关系,都是一个个孤立的节点。

Redlock 算法的基本思路,是让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败

这样一来,即使有某个 Redis 节点发生故障,因为锁的数据在其他节点上也有保存,所以客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。

为了取到锁,客户端应该执行以下操作:

  • 获取当前Unix时间,以毫秒为单位。
  • 依次尝试从5个实例,使用相同的key具有唯一性的value(例如UUID)获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
  • 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功
  • 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  • 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁,这是因为即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁

可以看到,加锁成功要同时满足两个条件:

  1. 客户端从超过半数(大于等于N/2+1)的 Redis 节点上成功获取到了锁;
  2. 客户端从大多数节点获取锁的总耗时(t2-t1)小于锁设置的过期时间。

简单来说就是:如果有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功。

7、Redisson

7.1、简单实现

Redisson 是 Redis 的 Java 客户端之一,提供了丰富的功能和高级抽象,包括分布式锁、分布式集合、分布式对象等。因此我们能够很简单的通过Redisson实现分布式锁,而不用自己造轮子。

与此同时,Redisson是支持原子性加/解锁、锁重试、可重入锁、RedLock等功能的,感兴趣的话可以自行了解。

// 获取分布式锁
RLock lock = redissonClient.getLock("myLock");try {// 尝试加锁,最多等待 10 秒,加锁后的锁有效期为 30 秒boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);if (locked) {// 成功获取锁,执行业务逻辑System.out.println("获取锁成功,执行业务逻辑...");} else {// 获取锁失败,可能是超时等待或者其他原因System.out.println("获取锁失败...");}
} catch (InterruptedException e) {e.printStackTrace();
} finally {// 释放锁lock.unlock();// 关闭 Redisson 客户端redissonClient.shutdown(); 
}

对了这里提一嘴,Redisson存储分布式锁是通过Hash结构进行存储的,内置的键值对是<线程标识,重入次数>,其中重入次数便可用于实现可重入机制。

7.2、看门狗机制

在 Redisson 中,看门狗机制(Watchdog)是用于维持 Redis 键的过期时间的一种机制。

通常情况下,当我们给 Redis 中的键设置过期时间后,Redis 会自动管理键的生命周期,并在键过期时通过过期删除策略对其进行处理。然而,如果 Redis 进程崩溃或者网络故障导致 Redis 服务器与客户端连接中断,那么键的过期时间可能无法得到及时删除,从而导致键仍然存在于 Redis 中。

为了解决这个问题,Redisson 引入了看门狗机制。当 Redisson 客户端为一个键设置过期时间时,它会启动一个看门狗线程,该线程会监视键的过期时间,并在过期时间快到期时自动对键进行续期操作。这样,即使因为 Redis 进程崩溃或者网络故障导致连接中断,看门狗仍然可以继续维护键的过期时间。

看门狗机制的工作原理如下:

  1. 当客户端获取分布式锁时,Redisson 会在 Redis 服务器中创建一个对应的键值对,并给这个键值对设置一个过期时间(通常是锁的持有时间);
  2. 同时,Redisson 会启动一个看门狗线程,在分布式锁的有效期内定时续期锁的过期时间;
  3. 看门狗线程会周期性地检查客户端是否还持有锁,如果持有锁,则会为锁的键值对设置新的过期时间,从而延长锁的有效期;
  4. 如果客户端在锁的有效期内未能续期,即看门狗线程无法找到对应的锁键值对,那么锁会自动过期,其他客户端就可以获取这个锁。

在Redisson中,默认续约时间是30s(可配置),即每隔30s续约一次,延长30s。

设置较短的续约时间可以更快地释放锁,但可能会增加续约的频率;较长的续约时间可以减少续约的次数,但会使得锁的有效期更长。

看门狗机制的好处是保证了在获取分布式锁后,业务逻辑可以在锁的有效期内运行,不会因为锁的过期而导致锁失效。当业务逻辑执行时间超过锁的过期时间时,看门狗线程会自动延长锁的过期时间,从而避免了锁的自动释放。

需要注意的是,看门狗线程是后台线程(守护线程),不会影响到客户端的正常业务逻辑。同时,为了避免看门狗线程过多占用 Redis 的 CPU 资源,Redisson 会动态调整看门狗的检查周期,使得看门狗线程在不影响性能的情况下维持锁的有效性。

相关文章:

【Redis】如何实现一个合格的分布式锁

文章目录 参考1、概述2、Redis粗糙实现3、遗留问题3.1、误删情况3.2、原子性保证3.3、超时自动解决3.4、总结 4、Redis实现优缺5、集群问题5.1、主从集群5.2、集群脑裂 6、RedLock7、Redisson7.1、简单实现7.2、看门狗机制 参考 Redisson实现Redis分布式锁的N种姿势 (qq.com)小…...

组件化开发复习

1.vue的根组件使用 // 1.创建appconst app Vue.createApp({// data: option apidata() {return {message: "Hello Vue",counter: 0,counter2: 0,content: ""}},watch: {content(newValue) {console.log("content:", newValue)}}}) createApp 函…...

【设计模式】设计原则-里氏替换原则

里氏替换原则 定义 任何基类可以出现的地方&#xff0c;子类一定可以出现。 通俗理解&#xff1a;子类可以扩展父类的功能&#xff0c;但不能改变父类原有的功能。 换句话说&#xff0c;子类继承父类时&#xff0c;除添加新的方法完成新增功能外&#xff0c;尽量不要重写父类…...

v2ex站点base64编码解码

最近在刷v站&#xff0c;我毕竟也是入坑不久的小白&#xff0c;发现各位兄弟的联系方式都是乱码&#xff0c;我以为是经过md5处理之类的&#xff0c;最后搜了下发现是对信息进行了base64编解码处理&#xff0c;目的是为了防止社工对个人信息的爬取处理。 下面是通过python对个人…...

PostgreSQL数据库动态共享内存管理器——Dynamic shared memory areas

dsm.c提供的功能允许创建后端进程间共享的共享内存段。DSA利用多个DSM段提供共享内存heap&#xff1b;DSA可以利用已经存在的共享内存&#xff08;DSM段&#xff09;也可以创建额外的DSM段。和系统heap使用指针不同的是&#xff0c;DSA提供伪指针&#xff0c;可以转换为backend…...

Redission分布式锁详解

前言 ​ 在分布式系统中&#xff0c;当不同进程或线程一起访问共享资源时&#xff0c;会造成资源争抢&#xff0c;如果不加以控制的话&#xff0c;就会引发程序错乱。而分布式锁它采用了一种互斥机制来防止线程或进程间相互干扰&#xff0c;从而保证了数据的一致性。 常见的分…...

063、故障处理之快速恢复数据

数据丢失快速恢复的重要性 目的&#xff1a;尽快修复数据&#xff0c;恢复业务 快速恢复相关技术对比 常用备份恢复技术 数据快速恢复原理 MVCC 是TiDB数据库原生的一项功能&#xff0c;默认使用无需配置&#xff0c;它使用多个历史快照的方式来维护数据在某个时间点对并…...

从零开始学习CTF

前言 CTF简介 中文一般译作夺旗赛&#xff0c;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式 CTF起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式 竞赛模式 解题模式&#xff1a; 在解题模式…...

【stable diffusion】保姆级入门课程05-Stable diffusion(SD)图生图-涂鸦重绘的用法

1.什么是涂鸦重绘 涂鸦重绘又称手涂蒙版。 简单来说&#xff0c;局部重绘手涂蒙版 就是涂鸦局部重绘的结合体&#xff0c;这个功能的出现是为了解决用户不想改变整张图片的情况下&#xff0c;对多个元素进行修改。 功能支持&#xff1a; 1.支持蒙版功能 2.笔刷决定绘制的元素…...

HBase 源码编译部署包

1. 版本 Java 版本: 1.8.0_201 HBase 版本: hbase-2.5.5 2.打包 git clone https://github.com/apache/hbase.git cd hbase git checkout branch-2.5 编译整个工程的 tar 包&#xff0c;编译一次 10 分钟左右 mvn clean package assembly:single -DskipTests ll hbase-assemb…...

备战秋招 | 笔试强训16

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、下列一段 C 代码的输出结果是&#xff08;&#xff09; #include <iostream> class Base { public:int Bar(char x){return (int)(x);}virtual int Bar(int x){return (2 * x);} }; clas…...

01 Excel常用高频快捷键汇总

目录 一、简介二、快捷键介绍2.1 常用基本快捷键1 复制&#xff1a;CtrlC2 粘贴&#xff1a;CtrlV3 剪切&#xff1a;CtrlX4 撤销&#xff1a;CtrlZ5 全选&#xff1a;CtrlA 2.2 常用高级快捷键1 单元格内强制换行&#xff1a;AltEnter2 批量输入相同的内容&#xff1a;CtrlEnt…...

PHP Laravel 路由、中间件、数据库等例子

以下是使用Laravel框架时的一些常见示例&#xff1a; 1. 路由&#xff08;Routes&#xff09;&#xff1a; // 定义基本路由 Route::get(/home, HomeControllerindex); // 带有参数的路由 Route::get(/user/{id}, UserControllershow); // 路由组 Route::middleware([auth])-&…...

Unity小游戏——使被砍中的怪物四处飞散

被武士砍中后&#xff0c;怪物将向四面八方飞散。 动作的不同将导致攻击力度的强弱表现不同&#xff0c;被攻击的各个对象的反应也有很大差异。在格斗游戏中&#xff0c;对对手一顿拳打脚踢后&#xff0c;看到其步履蹒跚的样子&#xff0c;往往可以感受到他的疼痛。相反如果对…...

hive之文件格式与压缩

hive文件格式&#xff1a; 概述&#xff1a; 为Hive表中的数据选择一个合适的文件格式&#xff0c;对提高查询性能的提高是十分有益的。Hive表数据的存储格式&#xff0c;可以选择text file、orc、parquet、sequence file等。 文本文件&#xff1a; 文本文件就是txt文件&…...

云原生容器内的一次pg_repack排错和解决过程

postgresql的pg_repack 这个cronjob一直执行不了。 排错过程: 用命令 kubectl describe job pg-repack-scheduler-manual-wv82r -n xxx没有查看用有用信息想办法进它启动的pod查看&#xff0c;于是在执行pg_repack.sh命令前&#xff0c;先加一个睡眠时间&#xff0c;如下: - …...

Centos Certbot 使用

安装 可选配置&#xff1a;启动EPEL存储库 非必要项 yum install -y epel-release yum clean all yum makecache #启用可选通道 可以不配置 yum -y install yum-utils yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional必要配置…...

VL163的基本信息

VL163是2:4差分通道多路复用/demux开关USB 3.1应用&#xff0c;为交换机信号性能支持高达USB 3.1&#xff0c;并使用QFN-28 3.5x4.5mm绿色封装。 VL163 QFN28 只能处理2Lane数据信号。自己没有CC识别沟通协议&#xff0c;如果要做USB-C Swtich&#xff0c;就要通过别的USB-C协…...

IntelliJ IDEA 2023.2 新版本,拥抱 AI

IntelliJ IDEA 近期连续发布多个EAP版本&#xff0c;官方在对用户体验不断优化的同时&#xff0c;也新增了一些不错的功能&#xff0c;尤其是人工智能助手补充&#xff0c;AI Assistant&#xff0c;相信在后续IDEA使用中&#xff0c;会对开发者工作效率带来不错的提升。 以下是…...

softmax回归

模型 softmax回归是多类分类模型&#xff0c;用于获取每个分类的置信度&#xff0c;置信度计算方式如下 经过全连接层&#xff0c;得到输出O&#xff0c;将O作为softmax的输入 O是输出向量&#xff0c;每个分量表示一个类别&#xff0c;y_hat_i表示i类别的置信度&#xff0…...

.NET 8 Preview 5推出!

作者&#xff1a;Jiachen Jiang 排版&#xff1a;Alan Wang 我们很高兴与您分享 .NET 8 Preview 5 中的所有新功能和改进&#xff01;此版本是 Preview 4 版本的后续版本。在每月发布的版本中&#xff0c;您将看到更多新功能。.NET 6 和 7 用户可以密切关注此版本&#xff0c;而…...

Spring核心概念、IoC和DI的认识、Spring中bean的配置及实例化、bean的生命周期

初始Spring 一、Spring核心概念1.1IoC(Inversion of Contral)&#xff1a;控制反转1.2IoC代码实现1.2DI代码实现 二、bean的相关操作2.1bean的配置2.1.1bean的基础配置2.1.2bean的别名配置2.1.3bean的作用范围配置 2.2bean的实例化 - - 构造方法2.3bean的实例化 - - 实例工厂与…...

git冲突“accept theirs”和“accept yours”

Accept Yours 就是直接选取本地的代码&#xff0c;覆盖掉远程仓库的 Accept Theirs 是直接选取远程仓库的&#xff0c;覆盖掉自己本地的 我们选择Merge,自己手动行进选择、修改。 这里左边部分是你本地仓库的代码&#xff0c;右边部分是远程仓库的代码&#xff0c;中间的res…...

Vision Transformer (ViT)

生成式模型与判别式模型 生成式模型,又称概率模型,是指通过学习数据的分布来建立模型P(y|x),然后利用该模型来生成新的数据。生成式模型的典型代表是朴素贝叶斯模型,该模型通过学习数据的分布来建立概率模型,然后利用该模型来生成新的数据。判别式模型,又称非概率模型,…...

OpenGL Metal Shader 编程:解决图片拉伸变形问题

前面发了一些关于 Shader 编程的文章&#xff0c;有读者反馈太碎片化了&#xff0c;希望这里能整理出来一个系列&#xff0c;方便系统的学习一下 Shader 编程。 由于主流的 Shader 编程网站&#xff0c;如 ShaderToy, gl-transitions 都是基于 GLSL 开发 Shader &#xff0c;加…...

[SQL挖掘机] - 字符串函数 - concat

介绍: concat函数用于连接字符串的函数。它接受多个字符串作为参数&#xff0c;并将它们按顺序连接起来形成一个新的字符串。 用法: 以下是concat函数的语法&#xff1a; concat(string1, string2, ...)其中&#xff0c;string1, string2, …是要连接的字符串参数。你可以传…...

Rust之所有权

1、所有权的概念&#xff1a; 程序需要管理自己在运行时使用的计算机内部空间。Rust语言采用包含特定规则的所有权系统来管理内存&#xff0c;这套规则允许编译器在编译的过程中执行检查工作&#xff0c;而不会产生任何的运行时开销。 (1)、所有权规则&#xff1a; Rust中的…...

RabbitMQ帮助类的封装

RabbitMQ帮助类的封装 基本部分 public class RabbitMQInvoker {#region Identy private static IConnection _CurrentConnection null;private readonly string _HostName null;private readonly string _UserName null;private readonly string _Password null;#endreg…...

mac 移动硬盘未正常退出,再次链接无法读取(显示)

&#xff08;1&#xff09;首先插入自己的硬盘&#xff0c;然后找到mac的磁盘工具 &#xff08;2&#xff09;打开磁盘工具&#xff0c;发现自己的磁盘分区在卸载状态&#xff1b;点击无法成功装载。 &#xff08;3&#xff09;打开终端&#xff0c;输入 diskutil list查看自…...

短视频账号矩阵系统源码开发部署路径

一、短视频批量剪辑的开发逻辑算法 1.视频剪辑之开发算法 自己研发视频剪辑是指通过对视频素材进行剪切、调整、合并等操作&#xff0c;利用后台计算机算法&#xff0c;进行抽帧抽组抽序进行排列以达到对视频内容进行修改和优化的目的。自己研发的视频剪辑工具可以通过后台码…...