分布式锁总结

文章目录

  • 分布式锁
    • 什么是分布式锁?
    • 分布式锁的实现方式
      • 基于数据库(mysql)实现
      • 基于缓存(redis)
        • 多实例并发访问问题演示
          • 项目代码(使用redis)
          • 配置nginx.conf
          • jmeter压测复现问题
            • 并发是1,即不产生并发问题
            • 并发30测试,产生并发问题(虽然单实例是`synchronized`,但是这是分布式多实例)
      • redis 分布式锁:setnx实现
        • 分布式锁的过期时间和看门狗
        • 附:redis setnx相关命令和分布式锁
        • Redisson
          • 代码&测试
          • Redisson 底层原理
          • 实现可重入锁
          • redis分布式锁的问题?
          • redis主从架构问题?
          • Redlock(超半数加锁成功才成功)
          • 高并发分布式锁如何实现
      • 基于ZooKeeper实现
        • zookeeper节点类型
        • zookeeper的watch机制
        • zookeeper lock
          • 普通临时节点(羊群效应)
          • 顺序节点(公平,避免羊群效应)
        • Curator InterProcessMutex(可重入公平锁)
          • code&测试
          • InterProcessMutex 内部原理
            • 初始化
            • 加锁
            • watch
            • 释放锁
      • redis vs zookeeper AI回答

分布式锁

什么是分布式锁?

锁:共享资源;共享资源互斥的;多任务环境
分布式锁:如果多任务是多个JVM进程,需要一个外部锁,而不是JDK提供的锁

在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问

  • 排它性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取

  • 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放)

  • 高可用:获取或释放锁的机制必须高可用且性能佳

分布式锁的实现方式

基于数据库(mysql)实现

新建一个锁表

CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',  
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '备注信息',  
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',  
PRIMARY KEY (`id`),  
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
  1. insert, delete(method_name有唯一约束)
    缺点:

    • 数据库单点会导致业务不可用
    • 锁没有失效时间:一旦解锁操作失败,就会导致锁记录一直在数据库中,其它线程无法再获得到锁。
    • 非重入锁:同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在记录了
    • 非公平锁
  2. 通过数据库的排它锁来实现

在查询语句后面增加for update(表锁,行锁),数据库会在查询过程中给数据库表增加排它锁。当某条记录被加上排他锁之后,其它线程无法再在该行记录上增加排它锁。可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过connection.commit()操作来释放锁

public boolean lock(){connection.setAutoCommit(false)while (true) {try {result = select * from methodLock where method_name=xxx for update;if (result == null) {return true;}} catch (Exception e) {}sleep(1000);}return false;
}public void unlock(){connection.commit();
}

基于缓存(redis)

多实例并发访问问题演示
项目代码(使用redis)

见项目代码:减库存的例子

  • 让Springboot项目启动两个实例(即有两个JVM进程)
curl -X POST \http://localhost:8088/deduct_stock_sync \-H 'Content-Type: application/json'curl -X POST \http://localhost:8089/deduct_stock_sync \-H 'Content-Type: application/json'

减库存调用测试
在这里插入图片描述

配置nginx.conf
http {include       mime.types;default_type  application/octet-stream;#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;sendfile        on;#tcp_nopush     on;#keepalive_timeout  0;keepalive_timeout  65;#gzip  on;upstream redislock{server localhost:8088 weight=1;server localhost:8089 weight=1;}server {listen       8080;server_name  localhost;#charset koi8-r;#access_log  logs/host.access.log  main;location / {root   html;index  index.html index.htm;proxy_pass  http://redislock;}}
}
  • nginx启动和关闭命令
mubi@mubideMacBook-Pro nginx $ sudo nginx
mubi@mubideMacBook-Pro nginx $ ps -ef | grep nginx0 47802     1   0  1:18下午 ??         0:00.00 nginx: master process nginx-2 47803 47802   0  1:18下午 ??         0:00.00 nginx: worker process501 47835 20264   0  1:18下午 ttys001    0:00.00 grep --color=always nginx
mubi@mubideMacBook-Pro nginx $
sudo nginx -s stop
  • 访问测试
curl -X POST \http://localhost:8080/deduct_stock_sync \-H 'Content-Type: application/json'
jmeter压测复现问题
  • redis 设置 stock 为 100
    在这里插入图片描述
并发是1,即不产生并发问题

在这里插入图片描述
库存减少30, redis get结果会是最终的70,符合

并发30测试,产生并发问题(虽然单实例是synchronized,但是这是分布式多实例)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 并发30访问测试结果:并不是最后的70

在这里插入图片描述

redis 分布式锁:setnx实现

在这里插入图片描述

  • 30的并发失败率是60%,即只有12个成功的,最后redis的stock值是88符合预期

可以看到大部分没有抢到redis锁,而返回了系统繁忙错误

在这里插入图片描述

分布式锁的过期时间和看门狗

机器宕机可能导致finally释放锁失败,所以必须为redis key设置一个过期时间,但是设置的过期时间是多少是个问题?

  • 超时时间是个问题:因为业务时长不确定的;如果设置短了而业务执行很长,那么会由于过期时间删除了可以,那么锁会被其它业务线程给抢了

  • 其它线程可能删除别的线程的锁,因为锁没有什么标记

  • 改进1

@PostMapping(value = "/deduct_stock_lock")
public String deductStockLock() throws Exception {// setnx,redis单线程String lockKey = "lockKey";String clientId = UUID.randomUUID().toString();// 如下两句要原子操作
//        Boolean setOk = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockVal);
//        stringRedisTemplate.expire(lockKey, 10 , TimeUnit.SECONDS); // 设置过期时间Boolean setOk = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);if (!setOk) {throw new Exception("业务繁忙,请稍后再试");}String retVal;try {// 只有一个线程能执行成功,可能有业务异常抛出来,可能宕机等等;但无论如何要释放锁retVal = stockReduce();} finally {// 可能失败if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {stringRedisTemplate.delete(lockKey);}}return retVal;
}
  • 过期时间短不够的问题:可以不断的定时设置,给锁续命: 看门狗;开启新线程,每隔一段时间,判断锁还在不在,然后重新设置过期时间
  • set key,value的时候,value设置为当前线程id,然后删除的时候判断下,确保删除正确
附:redis setnx相关命令和分布式锁
  1. setnx(SET if Not eXists)

  2. EXPIRE key seconds:设置key 的生存时间,当key过期(生存时间为0),会自动删除

如下,一个原子操作设置key:value,并设置10秒的超时

在这里插入图片描述

boolean lock(){ret = set key value(thread Id) 10 nx;if (!ret) {return false;}return true;
}void unlock(){val = get keyif ( val != null && val.equals( thread Id) ) {del key;}
}
Redisson

Redisson是一个基于Redis的Java客户端,提供了分布式锁的实现。其核心通过Redis的Lua脚本和原子操作保证锁的互斥性,支持可重入、公平锁、锁续期等功能。

代码&测试
@Bean
public Redisson redisson(){Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson)Redisson.create(config);
}
@Autowired
private Redisson redisson;@PostMapping(value = "/deduct_stock_redisson")
public String deductStockRedisson() throws Exception {String lockKey = "lockKey";RLock rLock = redisson.getLock(lockKey);String retVal;try {rLock.lock();// 只有一个线程能执行成功,可能有业务异常抛出来,可能宕机等等;但无论如何要释放锁retVal = stockReduce();} finally {rLock.unlock();}return retVal;
}

如下并发请求毫无问题:
在这里插入图片描述

Redisson 底层原理

在这里插入图片描述

  • setnx的设置key与过期时间用脚本实现原子操作
  • key设置成功默认30s,则有后台线程每10秒(1/3的原始过期时间定时检查)检查判断,延长过期时间
  • 未获取到锁的线程会自旋,直到那个获取到锁的线程将锁释放
实现可重入锁

value中多存储全局信息,可重入次数相关信息

{"count":1,"expireAt":147506817232,"jvmPid":22224, // jvm进程ID"mac":"28-D2-44-0E-0D-9A", // MAC地址"threadId":14 // 线程Id
}
redis分布式锁的问题?

Redis分布式锁会有个缺陷,就是在Redis哨兵模式下:

客户端1对某个master节点写入了redisson锁,此时会异步复制给对应的slave节点。但是这个过程中一旦发生master节点宕机,主备切换,slave节点从变为了master节点(但是锁信息是没有的)。这时客户端2来尝试加锁的时候,在新的master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁。

这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。缺陷在哨兵模式或者主从模式下,如果master实例宕机的时候,可能导致多个客户端同时完成加锁。

redis主从架构问题?

补充知识:redis单机qps支持:10w级别

redis主从架构是主同步到从,如果设置key成功,但是同步到还没结束就挂了;这样成为主,但是是没有key存在的,那么另一个线程又能够加锁成功。(redis主从架构锁失效问题?)

redis无法保证强一致性?zookeeper解决,但是zk性能不如redis

Redlock(超半数加锁成功才成功)

在这里插入图片描述

  • 加锁失败的回滚
  • redis加锁多,性能受影响
高并发分布式锁如何实现
  • 分段锁思想

基于ZooKeeper实现

回顾zookeeper的一些相关知识: 文件系统+监听通知机制

zookeeper节点类型
  1. PERSISTENT-持久节点

除非手动删除,否则节点一直存在于 Zookeeper 上; 重启Zookeeper后也会恢复

  1. EPHEMERAL-临时节点

临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。

  1. PERSISTENT_SEQUENTIAL-持久顺序节点

基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

  1. EPHEMERAL_SEQUENTIAL-临时顺序节点

基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

zookeeper的watch机制
  • 主动推送:watch被触发时,由zookeeper主动推送给客户端,而不需要客户端轮询
  • 一次性:数据变化时,watch只会被触发一次;如果客户端想得到后续更新的通知,必须要在watch被触发后重新注册一个watch
  • 可见性:如果一个客户端在读请求中附带 Watch,Watch 被触发的同时再次读取数据,客户端在得到 Watch消息之前肯定不可能看到更新后的数据。换句话说,更新通知先于更新结果
  • 顺序性:如果多个更新触发了多个 Watch ,那 Watch 被触发的顺序与更新顺序一致
zookeeper lock
普通临时节点(羊群效应)

在这里插入图片描述

比如1000个并发,只有1个客户端获取锁成功,其它999个客户端都处在监听并等待中;如果成功释放锁了,那么999个客户端都监听到,再次继续进行创建锁的流程。

所以每次锁有变化,几乎所有客户端节点都要监听并作出反应,这会给集群带来巨大压力,即为:羊群效应

顺序节点(公平,避免羊群效应)

在这里插入图片描述

  1. 首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型)

  2. 每个要获得锁的线程都会在这个节点下创建个临时顺序节点,

  3. 由于序号的递增性,可以规定排号最小的那个获得锁。

  4. 所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。

利用顺序性:每个线程都只监听前一个线程,事件通知也只通知后面都一个线程,而不是通知全部,从而避免羊群效应

Curator InterProcessMutex(可重入公平锁)

curator官方文档

code&测试

实践代码链接

@Component
public class CuratorConfiguration {@Bean(initMethod = "start")public CuratorFramework curatorFramework() {RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);return client;}
}
 @Autowired
private CuratorFramework curatorFramework;@PostMapping(value = "/deduct_stock_zk")
public String deductStockZk() throws Exception {String path = "/stock";InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, path);String retVal;try {interProcessMutex.acquire();retVal = stockReduce();} catch (Exception e) {throw new Exception("lock error");} finally {interProcessMutex.release();}return retVal;
}

在这里插入图片描述

  • 压测结果正常在这里插入图片描述

在这里插入图片描述

InterProcessMutex 内部原理
初始化
/**
* @param client client
* @param path   the path to lock
* @param driver lock driver
*/
public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
{this(client, path, LOCK_NAME, 1, driver);
}
 /*** Returns a facade of the current instance that tracks* watchers created and allows a one-shot removal of all watchers* via {@link WatcherRemoveCuratorFramework#removeWatchers()}** @return facade*/
public WatcherRemoveCuratorFramework newWatcherRemoveCuratorFramework();
加锁
private boolean internalLock(long time, TimeUnit unit) throws Exception
{/*Note on concurrency: a given lockData instancecan be only acted on by a single thread so locking isn't necessary*/Thread currentThread = Thread.currentThread();// 获取当前线程锁数据,获取到的化,设置可重入LockData lockData = threadData.get(currentThread);if ( lockData != null ){// re-enteringlockData.lockCount.incrementAndGet();return true;}// 尝试获取锁String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());if ( lockPath != null ){// 获取到锁,锁数据加入`threadData`的map结构中LockData newLockData = new LockData(currentThread, lockPath);threadData.put(currentThread, newLockData);return true;}// 没有获取到锁return false;
}
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{final long      startMillis = System.currentTimeMillis();final Long      millisToWait = (unit != null) ? unit.toMillis(time) : null;final byte[]    localLockNodeBytes = (revocable.get() != null) ? new byte[0] : lockNodeBytes;int             retryCount = 0;String          ourPath = null;boolean         hasTheLock = false;boolean         isDone = false;while ( !isDone ){isDone = true;try{ourPath = driver.createsTheLock(client, path, localLockNodeBytes);hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);}catch ( KeeperException.NoNodeException e ){// gets thrown by StandardLockInternalsDriver when it can't find the lock node// this can happen when the session expires, etc. So, if the retry allows, just try it all againif ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) ){isDone = false;}else{throw e;}}}if ( hasTheLock ){return ourPath;}return null;
}

创建锁是创建的临时顺序节点

@Override
public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception
{String ourPath;if ( lockNodeBytes != null ){ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);}else{ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);}return ourPath;
}
watch
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception
{boolean     haveTheLock = false;boolean     doDelete = false;try{if ( revocable.get() != null ){client.getData().usingWatcher(revocableWatcher).forPath(ourPath);}while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock ){// 获取lock下所有节点数据,并排序List<String>        children = getSortedChildren();String              sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash// 判断获取到锁PredicateResults    predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);if ( predicateResults.getsTheLock() ){haveTheLock = true;}else{String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();synchronized(this){try{// use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak// 监听前一个节点,并等待client.getData().usingWatcher(watcher).forPath(previousSequencePath);if ( millisToWait != null ){millisToWait -= (System.currentTimeMillis() - startMillis);startMillis = System.currentTimeMillis();if ( millisToWait <= 0 ){doDelete = true;    // timed out - delete our nodebreak;}wait(millisToWait);}else{wait();}}catch ( KeeperException.NoNodeException e ){// it has been deleted (i.e. lock released). Try to acquire again}}}}}catch ( Exception e ){ThreadUtils.checkInterrupted(e);doDelete = true;throw e;}finally{if ( doDelete ){deleteOurPath(ourPath);}}return haveTheLock;
}

是不是加锁成功:是不是最小的那个节点

@Override
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
{int             ourIndex = children.indexOf(sequenceNodeName);validateOurIndex(sequenceNodeName, ourIndex);boolean         getsTheLock = ourIndex < maxLeases;String          pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);return new PredicateResults(pathToWatch, getsTheLock);
}
释放锁

可重入判断;删除watchers,删除节点

/*** Perform one release of the mutex if the calling thread is the same thread that acquired it. If the* thread had made multiple calls to acquire, the mutex will still be held when this method returns.** @throws Exception ZK errors, interruptions, current thread does not own the lock*/
@Override
public void release() throws Exception
{/*Note on concurrency: a given lockData instancecan be only acted on by a single thread so locking isn't necessary*/Thread currentThread = Thread.currentThread();LockData lockData = threadData.get(currentThread);if ( lockData == null ){throw new IllegalMonitorStateException("You do not own the lock: " + basePath);}int newLockCount = lockData.lockCount.decrementAndGet();if ( newLockCount > 0 ){return;}if ( newLockCount < 0 ){throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + basePath);}try{internals.releaseLock(lockData.lockPath);}finally{threadData.remove(currentThread);}
}
final void releaseLock(String lockPath) throws Exception
{client.removeWatchers();revocable.set(null);deleteOurPath(lockPath);
}

redis vs zookeeper AI回答

  1. 一致性模型
    Zookeeper 提供强一致性,这意味着当客户端在一个服务器上看到某个状态更新后,其他服务器也会立即反映这一变化。这种特性使得 Zookeeper 非常适合用于需要严格一致性的场景。

相比之下,Redis 默认提供最终一致性。虽然可以通过 Redlock 算法来增强其一致性保障4,但在某些极端情况下(如网络分区或主从延迟较高时),仍然可能存在短暂的数据不一致问题。

  1. 可靠性与容错能力
    Zookeeper 使用 Paxos 或 ZAB 协议构建高可用集群,在部分节点失效的情况下仍能保持服务正常运行。因此,即使少数节点发生故障,整个系统依然能够继续运作。

然而,标准的 Redis 实现存在单点故障风险。尽管引入 Sentinel 或 Cluster 模式可以在一定程度上缓解此问题,但如果主节点崩溃且未及时完成 failover,则可能导致锁丢失的情况出现。此外,由于 Redis 主从之间采用异步复制机制,可能会进一步加剧该类问题的发生概率。

  1. 性能表现
    在高频次、低延时需求下,Redis 显示出了显著的优势。它是一种内存级数据库,所有操作几乎都在 O(1) 时间复杂度内完成,这使其成为高性能应用场景下的理想选择。

而 Zookeeper 更注重于稳定性和一致性而非极致速度。对于那些对实时响应要求不高但强调可靠性的业务来说,Zookeeper 是更合适的选择。

  1. 功能扩展性
    借助 Redisson 库的支持,开发者能够在 Redis 基础之上轻松获得诸如可重入锁、自动续期以及公平锁等功能。这些额外的功能极大地增强了 Redis 锁机制的实际应用价值。

至于 Zookeeper,虽然原生 API 较为简单直接,但它允许用户自定义复杂的逻辑流程以满足特定需求。不过相较于 Redisson 所提供的开箱即用型解决方案而言,开发成本相对更高一些。

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

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

相关文章

解决自签名证书HTTPS告警:强制使用SHA-256算法生成证书

解决自签名证书HTTPS告警&#xff1a;强制使用SHA-256算法生成证书 一、问题场景 在使用OpenSSL生成和配置自签名证书时&#xff0c;常遇到以下现象&#xff1a; 浏览器已正确导入根证书&#xff08;.pem文件&#xff09;&#xff0c;但访问HTTPS站点时仍提示不安全连接或证…

线上 Linux 环境 MySQL 磁盘 IO 高负载深度排查与性能优化实战

目录 一、线上告警 二、问题诊断 1. 系统层面排查 2. 数据库层面分析 三、参数调优 1. sync_binlog 参数优化 2. innodb_flush_log_at_trx_commit 参数调整 四、其他优化建议 1. 日志文件位置调整 2. 生产环境核心参数配置模板 3. 突发 IO 高负载应急响应方案 五、…

window 显示驱动开发-初始化和 DMA 缓冲区创建

若要指示 GPU 支持 GDI 硬件加速&#xff0c;显示微型端口驱动程序的 DriverEntry 函数实现必须使用指向驱动程序实现的 DxgkDdiRenderKm 函数的指针填充 DRIVER_INITIALIZATION_DATA 结构的 DxgkDdiRenderKm 成员。 DirectX 图形内核子系统调用 DxgkDdiRenderKm 函数&#xf…

Go语言实战:使用 excelize 实现多层复杂Excel表头导出教程

Go 实现支持多层复杂表头的 Excel 导出工具 目录 项目介绍依赖说明核心结构设计如何支持多层表头完整使用示例总结与扩展 项目介绍 在实际业务系统中&#xff0c;Excel 文件导出是一项常见功能&#xff0c;尤其是报表类需求中常见的复杂多级表头&#xff0c;常规表格组件往…

机器视觉6-halcon高级教程

机器视觉6-halcon高级教程 双目立体视觉原理视差外极线几何双目标定 双目立体视觉之Halcon标定一&#xff0e;标定结果二.Halcon标定过程1.获取左右相机图像中标定板的区域;2.提取左右相机图像中标定板的MARK点坐标和摄像机外部参数;3.执行双目标定;4.获取非标准外极线几何到标…

板凳-------Mysql cookbook学习 (六)

2025年Pytorch-gpu版本安装&#xff08;各种情况适用自己的安装需求&#xff0c;亲测绝对有效&#xff0c;示例安装torch2.6.0&#xff0c;过程详细面向小白&#xff09;_torch gpu版本-CSDN博客 https://blog.csdn.net/OpenSeek/article/details/145795127 2.2 查错 import s…

Spring boot和SSM项目对比

目录对比 springboot目录 project├─src│ ├─main│ │ ├─java│ │ │ ├─com.example.demo│ │ │ │ ├─config // 存放SpringBoot的配置类│ │ │ │ ├─controller // 存放控制器类│ │ │ │ ├─entity // 存…

《关于浔川社团退出DevPress社区及内容撤回的声明》

《关于浔川社团退出DevPress社区及内容撤回的声明》 尊敬的DevPress社区及读者&#xff1a; 经浔川社团内部决议&#xff0c;我社决定自**2025年5月26日**起正式退出DevPress社区&#xff0c;并撤回所有由我社成员在该平台发布的原创文章。相关事项声明如下&#xff1a; …

Python性能优化利器:__slots__的深度解析与避坑指南

核心场景&#xff1a;当需要创建数百万个属性固定的对象时&#xff0c;默认的__dict__字典存储会造成巨大内存浪费。此时__slots__能通过元组结构取代字典&#xff0c;显著提升内存效率&#xff08;实测节省58%内存&#xff09;&#xff01; 底层原理&#xff1a;为何能节省内…

Go 语言中的 Struct Tag 的用法详解

在 Go 语言中&#xff0c;结构体字段标签&#xff08;Struct Tag&#xff09; 是一种用于给字段添加元信息&#xff08;metadata&#xff09;的机制&#xff0c;常用于序列化&#xff08;如 JSON、XML&#xff09;、ORM 映射、验证等场景。你在开发 Web 应用或处理数据交互时&a…

微软正式发布 SQL Server 2025 公开预览版,深度集成AI功能

微软在今年的 Build 2025 大会上正式发布了 SQL Server 2025 公开预览版&#xff0c;标志着这一经典数据库产品在 AI 集成、安全性、性能及开发者工具方面的全面升级。 AI 深度集成与创新 原生向量搜索&#xff1a;SQL Server 2025 首次将 AI 功能直接嵌入数据库引擎&#xff…

React从基础入门到高级实战:React 基础入门 - React 的工作原理:虚拟 DOM 与 Diff 算法

React 的工作原理&#xff1a;虚拟 DOM 与 Diff 算法 引言 React 是现代前端开发的明星框架&#xff0c;它的出现彻底改变了我们构建用户界面的方式。无论是动态的 Web 应用还是复杂的单页应用&#xff08;SPA&#xff09;&#xff0c;React 都能以高效的渲染机制和简洁的组件…

解释一下NGINX的反向代理和正向代理的区别?

大家好&#xff0c;我是锋哥。今天分享关于【解释一下NGINX的反向代理和正向代理的区别?】面试题。希望对大家有帮助&#xff1b; 解释一下NGINX的反向代理和正向代理的区别? NGINX的反向代理和正向代理的区别主要体现在它们的功能和使用场景上。下面我会详细解释它们的定义…

Python学习——执行python时,键盘按下ctrl+c,退出程序

在 Python 中&#xff0c;当用户按下 CtrlC 时&#xff0c;程序默认会触发 KeyboardInterrupt 异常并终止。 1. 捕获 KeyboardInterrupt 异常&#xff08;推荐&#xff09; 使用 try-except 块直接捕获 KeyboardInterrupt 异常&#xff0c;适用于简单场景。 示例代码&#xff…

C++ 反向迭代器(Reverse Iterator)实现详解

目录 1. 反向迭代器概述 2. 代码实现分析 3. 关键点解析 3.1 模板参数设计 3.2 核心操作实现 4. 使用示例 1. 反向迭代器概述 反向迭代器是STL中一种重要的适配器&#xff0c;它允许我们以相反的顺序遍历容器。本文将详细讲解如何实现一个自定义的反向迭代器模板类。 2.…

动态DNS管理:【etcd+CoreDNS】 vs【BIND9】便捷性对比

对比 BIND9 集群和 etcdCoreDNS 集群在便捷性方面&#xff0c;通常情况下&#xff0c;对于需要动态、频繁变更 DNS 记录以及追求云原生和自动化集成的场景&#xff0c;etcdCoreDNS 方案更加便捷。 然而&#xff0c;“便捷性”也取决于具体的应用场景、团队的技术栈和运维习惯。…

基于大模型的短暂性脑缺血发作预测与干预全流程系统技术方案大纲

目录 一、系统概述二、系统架构(一)数据采集层(二)大模型核心层(三)应用服务层(四)数据存储与管理层三、全流程技术方案(一)术前阶段(二)术中阶段(三)术后阶段(四)并发症风险预测(五)手术方案制定(六)麻醉方案制定(七)术后护理(八)统计分析(九)技术验…

MSP430通用电机控制代码(Motor)设计与实现

一、代码结构概览 // Motor.h // Motor.h #ifndef __MOTOR_H_ #define __MOTOR_H_#include "A_include.h"void Motor_Init(void); // 初始化函数 void PWM_SET(int duty0, int duty1); // PWM设置函数#endif// Motor.c // Motor.c #include "Motor.h"…

25年软考架构师真题(回忆更新中)

论文题: 系统负载均衡设计方法事件驱动架构多模型数据库应用软件测试架构案例分析: 必选题:1.1填写质量属性的质量属性名 1.2解释器风格架构的组成图填空,以及解释为什么该模型适用解释器风格 选做题1redis2.1全量复制的流程图 <

优化用户体验:拦截浏览器前进后退、刷新、关闭、路由跳转等用户行为并弹窗提示

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 需求 首先列举一下需要拦截的行为&#xff0c;接下来我们逐个实现。 浏览器前进后退标签页刷新和关闭路由跳转 1、拦截浏览器前进后退 这里的实现是核心&#xff0c;涉及到大…