分布式锁实现方案

分布式锁

1 什么是分布式锁

​ 就是在分布式环境下,保证某个公共资源只能在同一时间被多进程应用的某个进程的某一个线程访问时使用锁。

2 几个使用场景分析

一段代码同一时间只能被同一个不同进程的一个线程执行

  • 库存超卖 (库存被减到 负数),上面案例就是库存超卖

  • 定时任务

  • 分布式缓存中缓存同步

  • 转账(多个进程修改同一个账户)

3 需要什么样的分布式锁-特征

  • 可以保证在分布式部署的应用集群中同一个方法在同一时间只能被一台机器上的一个线程执行。(互斥性)

  • 这把锁要是一把可重入锁(避免死锁)(重入性)

  • 这把锁最好是一把阻塞锁(自旋)(根据业务需求考虑要不要这条)

  • 这把锁最好是一把公平锁(根据业务需求考虑要不要这条)

  • 获取锁和释放锁的性能要好

4 常见的分布式锁解决方案

1.4.1. 思路

  • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

  • 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。

  • 分布式锁还是可以将标记存在公共内存(redis),只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件(oss),zk等做锁与单机的实现是一样的,只要保证标记能互斥就行。

    在多个进程公共能够访问地方放个标识!一个进程的某个线程进去时,标识已经进去(获取到锁),其他线程就要等待!直到原来的线程释放,新的线程才能可以获取锁.

1.4.2. 分布式锁三种方式

  • 基于数据库操作

  • 基于redis缓存和过期时间

  • 基于zookeeper 临时顺序节点+watch

​ 从理解的难易程度角度(从低到高)数据库 > redis > Zookeeper

​ 从实现的复杂性角度(从低到高)数据库> redis >Zookeeper

​ 从性能角度(从高到低)redis > Zookeeper > 数据库

​ 从可靠性角度(从高到低)Zookeeper > redis > 数据库

Zookeeper >redis>数据库(基本不用)

基于数据库基本不用,zk或redis要根据项目情况来决定,如果你项目本来就用到zk,就使用zk,否则redis

分布式环境互斥实现

1 数据库锁

1.1 悲观锁 innodb行锁

  • 共享锁(S Lock):允许事务读一行数据,具有锁兼容性质,允许多个事务同时获得该锁。
  • 排它锁(X Lock):允许事务删除或更新一行数据,具有排它性,某个事务要想获得锁,必须要等待其他事务释放该对象的锁。

X锁和其他锁都不兼容,S锁值和S锁兼容,S锁和X锁都是行级别锁,兼容是指对同一条记录(row)锁的兼容性情况。

Mysql innodb锁的默认操作:

  • 我们对某一行数据进行查询是会默认使用S锁加锁,如果硬是要把查询也加X锁使用
 	@Select("select * from t_goods where id = #{id}")Goods laodByIdForUpdate(Long id);
  • 读的时候硬是要加x锁
  @Select("select * from t_goods where id = #{id} **for update**")Goods laodByIdForUpdate(Long id); 
  • 当我们对某一行数据进行增删改是会加X锁

1.2 乐观锁

​ 乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

​ 直接用:表中添加一个时间戳或者版本号的字段来实现,update account set version = version + 1 where id = #{id} and version = #{oldVersion} 当更新不成功,客户端重试,重新读取最新的版本号或时间戳,再次尝试更新,类似 CAS 机制,推荐使用。

2 分布式锁

疑问?既然可以使用数据库悲观锁和乐观锁保证分布式环境的互斥!那为什么还要分布式锁!

有的操作是没有数据库参与的,又想分布式环境互斥! 就必须使用分布式锁!

2.1 基于数据库的

  • 方案1 主键

    主键不能重复

      //基于数据库的分布式锁实现
    public class DbGoodsLock {private Long goodsId = null;public DbGoodsLock(Long goodsId) {this.goodsId = goodsId;}/*** 能插入就能获取获取锁* @return*/public  boolean  trylock(){Connection connection = null;try{connection  = JDBCUtils.getConnection();Statement statement = connection.createStatement();statement.execute("insert into t_goods_lock(id) values("+this.goodsId+")");System.out.println(Thread.currentThread().getName()+"加锁,插入数据 goodsId="+goodsId);return true;}catch (Exception e) {//e.printStackTrace();System.out.println(Thread.currentThread().getName()+"加锁异常====================:"+e.getMessage());return false;}finally {if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}//阻塞获取锁public  void lock(){if (trylock())return;try {Thread.sleep(10);System.out.println("尝试获取锁...");} catch (InterruptedException e) {e.printStackTrace();}lock();}//释放锁public  boolean  unlock(){Connection connection = null;try{connection  = JDBCUtils.getConnection();Statement statement = connection.createStatement();statement.execute("delete from t_goods_lock where id = "+goodsId);System.out.println(Thread.currentThread().getName()+"解锁,删除数据 goodsId="+goodsId);return true;}catch (Exception e) {System.out.println(Thread.currentThread().getName()+"解锁异常====================:"+e.getMessage());//e.printStackTrace();return false;}finally {if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}
    }
  • 方案2

    唯一字段不能重复,和上面原来一样

  • l 数据库是单点?搞两个数据库,数据之键双向同步,一旦挂掉快速切换到备库上。 主备切换

  • l 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。

  • l 非阻塞的?搞一个 while 循环,直到 insert 成功再返回成功。

  • l 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

    n 获取:再次获取锁的同时更新count(+1).

    n 释放:更新count-1,当count==0删除记录。

    l 非公平的?-mq

​ 数据库实现分布式锁,一般都很少用

2.2 redis

方案1:原生

1 setnx(如果不存在才设置成功)+del没有可以就添加  setnx goods_id = 1   del goods_id
2 expire+watchdog续约时间(不好做,我不做)
3 value是uuid,获取判断,删除
4 lua脚本
public interface IDistributedLock {/*** 自旋上锁*/void lock();/*** 释放锁*/void unlock();/*** 尝试获取锁*/boolean tryLock();
}
public class RedisLock implements IDistributedLock {private String resourceName;private String lockVal;  //try del都有用到uuid,所以构造的时候产生一个成员变量private RedisTemplate redisTemplate;//不交给spring管理就一般用不了RedisTemplatepublic RedisLock(String resourceName) {this.resourceName = resourceName;this.lockVal = UUID.randomUUID().toString();ApplicationContext context = ApplicationContextHolder.getApplicationContext();redisTemplate = (RedisTemplate) context.getBean("redisTemplate");}@Overridepublic void lock() {if (tryLock())return;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}lock();}@Overridepublic void unlock() {//get check del--->lua   key value/*if redis.call('get', KEYS[1]) == KEYS[2] thenreturn redis.call('del', KEYS[1])elsereturn 0end*/List<String> params = Arrays.asList(resourceName, lockVal); //goods_1 jfjjfjflfjofredisTemplate.execute(redisScript(),params);}public RedisScript<Long> redisScript(){DefaultRedisScript<Long> script = new DefaultRedisScript<>();script.setResultType(Long.class);//script.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis.lua")));script.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");return script;}@Overridepublic boolean tryLock() {//uuid setnx expire//hash K(resourceName) k(uuid) v(count)+
//        redisTemplate.opsForHash().putIfAbsent(resourceName,lockVal,1);
//        redisTemplate.expire(resourceName,3,TimeUnit.SECONDS);Boolean result = redisTemplate.opsForValue().setIfAbsent(resourceName, lockVal, 10, TimeUnit.SECONDS);System.out.println(resourceName);System.out.println(lockVal);return  result;}
}
@Service
public class GoodsServiceImpl_redis_Lock implements IGoodsService {//    @Autowired
//    private IDistributedLock lock;@Autowiredprivate GoodsMapper goodsMapper;@Overridepublic Goods getById(Long id) {return goodsMapper.laodById(1L);}@Override@Transactionalpublic void updateNum(Map<String,Object> params) {Long goodsId = (Long) params.get("id");Integer num = (Integer) params.get("num");String resourceName = "goods_"+goodsId;IDistributedLock lock = new RedisLock(resourceName);try{lock.lock();System.out.println(Thread.currentThread().getName()+" get lock!");Goods goods = goodsMapper.laodById(goodsId);Thread.sleep(4000);System.out.println(goods);System.out.println(Thread.currentThread().getName()+goods.getCount()+":"+num);if (goods.getCount()>=num){goodsMapper.updateNum(params);System.out.println(Thread.currentThread().getName()+"buy "+num+"!");}}catch (Exception e){e.printStackTrace();}finally {if (lock != null) {lock.unlock();}}}
}

方案2:框架实现

​ 业界也提供了多个现成好用的框架予以支持分布式锁,比如Redisson、spring-integration-redis,redlock redisson,redlock 底层原理

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.2.3</version>
</dependency>
@Value("${spring.redis.host}")private String redisHost;@Value("${spring.redis.port}")private int redisPort;@Value("${spring.redis.database}")private int redisdatabase;@Value("${spring.redis.password}")private String redisPassword;@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress(redisHost + ":" + redisPort);config.useSingleServer().setDatabase(redisdatabase);config.useSingleServer().setPassword(redisPassword);RedissonClient redisson = Redisson.create(config);return redisson;}
@Service
public class GoodsServiceImpl_redission implements IGoodsService {@Autowiredprivate GoodsMapper goodsMapper;@Autowiredprivate RedissonClient redissonClient;@Overridepublic void updateNum(Map<String,Object> params) {Long goodsId = (Long) params.get("id");Integer num = (Integer) params.get("num");System.out.println(Thread.currentThread().getName()+"enter!");String resourceName = "goods" + goodsId;RLock rLock = redissonClient.getLock(resourceName);try{rLock.lock();System.out.println(Thread.currentThread().getName()+" get lock!");Goods goods = goodsMapper.laodById(goodsId);System.out.println(goods);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(goods.getCount()+":"+num);if (goods.getCount()>=num){goodsMapper.updateNum(params);System.out.println(Thread.currentThread().getName()+"buy "+num+"!");}}catch (Exception e){e.printStackTrace();}finally {if (rLock != null) {rLock.unlock();System.out.println(Thread.currentThread().getName()+" unlock!");}}}@Overridepublic Goods getById(Long id) {return goodsMapper.laodById(1L);}
}

2.3 zk

2.3.1 zk理论
2.3.1.1 是什么?

ZooKeeper是Apache下的一个Java开源项目(最初由Yahoo开发, 后捐献给了Apache)。

ZooKeeper的原始功能很简单,基于它的层次型的目录树的数据结构,并通过对树上的节点进行有效管理,可以设计出各种各样的分布式集群管理功能。此外, ZooKeeper本身 也是分布式的。

2.3.1.2 数据库模型

Zookeeper会维护一个具有层次关系的树状的数据结构,它非常类似于一个标准的文件系统,如下图所

示:同一个目录下不能有相同名称的节点
在这里插入图片描述

2.3.1.3 节点分类

ZooKeeper 节点是有生命周期的这取决于节点的类型,在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。 是否持久化,是否有序

  • 持久节点(PERSISTENT)与临时节点(EPHEMERAL)
    所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

    和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

  • 顺序节点(SEQUENTIAL) 无序节点
    这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。 00010001 00010002 00010003

    无序节点就是没有顺序

    具体组合得到四种:持久有序 持久无序 临时有序 临时无序

2.3.2 入门
2.3.2.1 安装
  • 官方下载地址:http://mirrors.cnnic.cn/apache/zookeeper/ , 下载后获得,解压即可安装。

  • 安装配置: 把conf目录下的zoo_sample.cfg改名成zoo.cfg,这里我是先备份了zoo_sample.cfg再改的名。修改zoo.cfg的值如下:

    dataDir=D:/zookeeper-3.4.9/data/data

    dataLogDir=D:/zookeeper-3.4.9/data/log

  • 启动 :点击bin目录下的zkServer.cmd 这时候出现下面的提示就说明配置成功了。

  • 图形界面-ZooViewer:https://blog.csdn.net/u010889616/article/details/80792912

2.3.2.2 代码测试-使用代码创建各种节点
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency><groupId>com.101tec</groupId><artifactId>zkclient</artifactId><version>0.10</version>
</dependency>

API总结:

  • l new ZkClient(“127.0.0.1:2181”,5000); 创建zookeeper客户端

  • l client.getChildren(“/”):获取子节点 “/”代表根节点

  • l client.createPersistent:创建持久节点

  • l client.createPersistentSequential:创建持久有顺节点,会在path后面增加序号

  • l client.createEphemeral:创建临时节点

  • l client.createEphemeralSequential:创建临时有序节点

  • l client.subscribeChildChanges:订阅子节点的改变

  • l client.subscribeDataChanges:订阅某个节点的数据改变

@Test //持久化节点
public void test1() throws Exception {//创建客户端ZkClient client = new ZkClient("127.0.0.1:2181",5000);//获取根节点List<String> children = client.getChildren("/");for (String child : children) {System.out.println(child);  //zookeeper}//创建持久节点client.createPersistent("/zookeeper/createPersistent");//创建持久顺序节点String persistentSequential = 
client.createPersistentSequential("/zookeeper/createPersistentSequential", "111");System.out.println("persistentSequential="+persistentSequential);    
//  /zookeeper/createPersistentSequential0000000003//创建临时节点client.createEphemeral("/zookeeper/createEphemeral");//client.createEphemeral("/zookeeper/createEphemeral"); //重复创建会报错//创建临时顺序节点String ephemeralSequential = 
client.createEphemeralSequential("/zookeeper/createEphemeralSequential", "111");System.out.println("ephemeralSequential="+ephemeralSequential);//关闭client.close();
}
//测试监听@Testpublic void test3() throws Exception {//创建客户端ZkClient client = new ZkClient("127.0.0.1:2181",5000);if(!client.exists("/yhptest")){client.createPersistent("/yhptest");}//操作节点client.createPersistentSequential("/yhptest/test","x1");client.createPersistentSequential("/yhptest/test","x2");client.createPersistentSequential("/yhptest/test","x3");client.createPersistentSequential("/yhptest/test","x4");client.createPersistent("/yhptest/tests","aa");List<String> children = client.getChildren("/yhptest");for (String child : children) {System.out.println(child);}//关闭client.subscribeChildChanges("/yhptest", new IZkChildListener() {@Overridepublic void handleChildChange(String s, List<String> list) throws Exception {System.out.println("子节点改变:"+s);System.out.println("子节点改变:"+list);}});client.subscribeDataChanges("/yhptest/tests", new IZkDataListener() {@Overridepublic void handleDataChange(String s, Object o) throws Exception {//数据改变System.out.println("数据改变:"+s);System.out.println("数据改变:"+o.toString());}@Overridepublic void handleDataDeleted(String s) throws Exception {System.out.println("数据删除");}});client.delete("/yhptest/tests");Thread.sleep(2000);client.close();}
2.3.3 zk分布式锁-原生

在这里插入图片描述

2.3.3.1 非公平锁

​ 根据Zookeeper的临时节点的特性实现分布式锁,先执行的线程在zookeeper创建一个临时节点,代表获取到锁,后执行的线程需要等待,直到临时节点被删除说明锁被释放,第二个线程可以尝试获取锁。

T1 创建临时无序节点goods_2 执行业务逻辑 关闭

T2 创建临时无序节点goods_2 执行业务逻辑 关闭

2.3.3.2 公平锁

在这里插入图片描述

public class ZookeeperDistributedLock implements ExampleLock {ZkClient client = new ZkClient("127.0.0.1:2181",5000);CountDownLatch cdl = new CountDownLatch(1); //不为零阻塞住,不让他往下走//父节点路径String parent = "";//当前节点路径String currentPath = "";//1 goods// lock_goods_id 父节点(持久节点)// lock_goods_id_001// lock_goods_id_002@Overridepublic void lock(String resourceName) {parent = "/"+resourceName;//判断父节点是否存在,如果不存在要创建一个持久节点if (!client.exists(parent)){client.createPersistent(parent,"root");}//前面的节点都处理完成,自己变成第一个节点才加锁成功。if (!tryLock(resourceName)){lock(resourceName);}}@Overridepublic void unlock(String resourceName) {//自己操作完毕,删除自己,让下一个节点执行。System.out.println(currentPath);System.out.println(System.currentTimeMillis());System.out.println(client.delete(currentPath));client.close();}@Overridepublic boolean tryLock(String resourceName) {//创建子节点-临时顺序节点if (StringUtils.isEmpty(currentPath)){currentPath = client.createEphemeralSequential(parent + "/test", "test"); //test0001}//如果是第一个节点,就获取到锁了。List<String> children = client.getChildren(parent);System.out.println(currentPath+"jjj");for (String child : children) {System.out.println(child);}Collections.sort(children);///goods_1/test0000000003jjj//test0000000003if (currentPath.contains(children.get(0))){return true;}else{//如果不是第一个节点,监听前一个节点,要再这儿等待,知道被触发,再次判断是否是第一个节点进行返回就OKString str = currentPath.substring(currentPath.lastIndexOf("/")+1);System.out.println(str);int preIndex = children.indexOf(str)-1;String prePath = parent+"/"+children.get(preIndex);
//监听上一个节点,如果上一个节点被删除,把秒表设置为 0 (cdl.countDown();),那么当前节点取消等待(cdl.await();)重新获取锁client.subscribeDataChanges(prePath, new IZkDataListener() {@Overridepublic void handleDataChange(String dataPath, Object data) throws Exception {}@Overridepublic void handleDataDeleted(String dataPath) throws Exception {//让他-1 变为零cdl.countDown();}});//一直等待,直到自己变成第一个节点try {cdl.await();} catch (InterruptedException e) {e.printStackTrace();}return false;}}
}
2.3.4 zk分布式锁 curator框架实现
  • 导入jar
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.1.0</version>
</dependency>
  • 配置

    @Configuration
    public class ZkCuratorConfig {//初始化方法start@Bean(initMethod = "start",destroyMethod = "close") //bean声明周期  构造  初始化initMethod  使用  销毁destroyMethodpublic CuratorFramework curatorFramework(){//重试策略RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);//创建客户端CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);return client;}
    }
  • 代码

@Autowiredprivate CuratorFramework framework;@Overridepublic void updateNum(Map<String,Object> params) {Long goodsId = (Long) params.get("id");Integer num = (Integer) params.get("num");String resourceName = "/goods_"+goodsId;InterProcessMutex mutex = null;try{mutex = new InterProcessMutex(framework, resourceName); //1个信号量mutex.acquire(3, TimeUnit.SECONDS); //获取一个,  自旋(拿不到一直循环)   适应性自选(拿几秒就算乐 )System.out.println(Thread.currentThread().getName()+" get lock!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTimeMillis());//通过商品id获取商品  3Goods goods = goodsMapper.laodById(goodsId);System.out.println(goods);System.out.println(goods.getCount()+":"+num);if (goods.getCount()>=num){goodsMapper.updateNum(params);System.out.println(Thread.currentThread().getName()+"buy "+num+"!");}}catch (Exception e){e.printStackTrace();}finally {if (mutex != null) {try {mutex.release();} catch (Exception e) {e.printStackTrace();}}}}

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

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

相关文章

预训练是什么?

预训练是什么&#xff1f; 图像领域的预训练 在介绍图像领域的预训练之前&#xff0c;我们首先介绍下卷积神经网络&#xff08;CNN&#xff09;&#xff0c;CNN 一般用于图片分类任务&#xff0c;并且CNN 由多个层级结构组成&#xff0c;不同层学到的图像特征也不同&#xff…

【C++】Cmake入门|掌握cmake的基本操作

前言&#xff1a; CMake是开源、跨平台的构建工具&#xff0c;可以让我们通过编写简单的配置文件去生成本地的Makefile&#xff0c;这个配置文件是独立于运行平台和编译器的&#xff0c;这样就不用亲自去编写Makefile了&#xff0c;而且配置文件可以直接拿到其它平台上使用&am…

基于动力学的六自由度机器人阻抗恒力跟踪控制

1.整个代码的控制流程图如下&#xff1a; 2.正逆运动学计算 略 3.动力学模型 采用拉格朗日法计算机械臂的动力学模型&#xff0c;其输入的是机械臂的关节角度、角速度和角加速度&#xff1b;其中M、C、G本别是计算的惯性力、科式力和重力项&#xff0c;相关部分如下&#xf…

JavaScript的学习之运算符

目录 一、运算符的介绍 二、算数运算符 三、一元运算符 一、运算符的介绍 运算符也称操作符&#xff0c;通过运算符可以对一个或者多个值进行运算&#xff0c;并获得结果 比如&#xff1a;typeof就是运算符&#xff0c;可以获得一个值的类型&#xff0c;它会将改值的类型以字…

安卓中使用ttf字体文件

官方文档中提供的方法要设备能访问google&#xff1f; 官方方法 直接下载字体的fft文件 我要使用的是lexend 需要的格式可以在里面搜索 使用下载的ttf文件 解压出来 可以单独使用static里面的&#xff0c;里面是直接的lexend的各种格式 但是我这里直接使用Lexend-Vari…

odoo的采购询价单,默认情况下显示‘draft‘,‘sent‘,‘purchase‘,请问什么情况下才会显示‘to approve‘?

odoo的采购询价单&#xff0c;默认情况下显示’draft’,‘sent’,‘purchase’&#xff0c;请问什么情况下才会显示’to approve’? 见下图&#xff1a; 这与操作人员的角色是相关的&#xff1a; 当操作人员是群组 “采购 / 用户”时&#xff0c;点击“confirm order/确认订…

Day59 代码随想录打卡|二叉树篇---把二叉搜索树转换为累加树

题目&#xff08;leecode T538&#xff09;&#xff1a; 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。…

vuex的深入学习[基于vuex3]----篇(二)

store对象的创建 store的传递图 创建语句索引 创建vuex的语句为new Vuex.Store({…})Vuex的入口文件是index.js,store是index.js导出的store类store类是store.js文件中定义的。 Store的构造函数constructor 判断vuex是否被注入&#xff0c;就是将vue挂载在window对象上&am…

【算法】二叉树 - 理论基础

1.种类 1.1 满二叉树 只有度为0和2的节点&#xff0c;且度为0的节点都都在同一层。深度为k&#xff0c;有2^k-1个节点。 1.2 完全二叉树 在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都…

常说的云VR是什么意思?与传统vr的区别

虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;是一种利用计算机技术模拟产生一个三维空间的虚拟世界&#xff0c;让用户通过视觉、听觉、触觉等感官&#xff0c;获得与现实世界类似或超越的体验。VR技术发展历程可追溯至上世纪&#xff0c;经历概念提出、…

某大厂程序员吐槽:离职交接时,新人被工作量吓退,领导却污蔑我故意劝退新人,我怒晒工作短信反击证明,新人看了后也决定走人了!

一位知名大公司的程序员分享了他离职时的遭遇&#xff1a;在交接工作时&#xff0c;新进的同事因工作量过大而感到压力&#xff0c;但出乎意料的是&#xff0c;他们的领导却指责我故意吓唬新人。为了证明自己的清白&#xff0c;我晒出了工作短信作为反击&#xff0c;结果连新人…

Linux系统编程--软/硬连接

真正找到磁盘上文件的并不是文件名&#xff0c;而是inode。 其实在linux中可以让多个文件名对应于同一个inode。 命令&#xff1a; 软连接&#xff1a;ln -s 原文件名 新文件名 硬链接&#xff1a;ln 原文件名 新文件名 删除链接文件&#xff1a;unlink 文件名执行上面两条命令…

matplotlib之常见图像种类

Matplotlib 是一个用于绘制图表和数据可视化的 Python 库。它支持多种不同类型的图形&#xff0c;以满足各种数据可视化需求。以下是一些 Matplotlib 支持的主要图形种类&#xff1a; 折线图&#xff08;Line Plot&#xff09;&#xff1a; 用于显示数据随时间或其他连续变量的…

Git使用过程中涉及的几个区域

一. 简介 Git 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理&#xff0c;也是 Linus Torvalds 为了帮助管理 Linux内核开发而开发的一个开放源码的版本控制软件。 本文简单了解一下 git涉及的几个部分&#xff0c;以及git 常…

vue登陆密码加密,java后端解密

前端 安装crypto-js npm install crypto-js加密 //引入crypto-js import CryptoJS from crypto-js;/** ---密码加密 start--- */ const SECRET_KEY CryptoJS.enc.Utf8.parse("a15q8f6s5s1a2v3s"); const SECRET_IV CryptoJS.enc.Utf8.parse("a3c6g5h4v9sss…

基于网络搜索的长篇问答能力优化:FoRAG

检索增强生成&#xff08;RAG&#xff0c;Retrieval Augmented Generation&#xff09;可以利用搜索引擎检索技术来提升长篇问答质量&#xff0c;因而在 QA 任务中广受欢迎。尽管有多种开源方法和网络增强的商业系统如 Bing Chat 出现&#xff0c;但生成长篇答案的事实性和逻辑…

ruoyi添加自己的菜单

先把自己自定义的view填写好 在菜单管理模块 因为我已经新增过&#xff0c;所以就看看我填的啥就行了 我发现一个问题&#xff0c;路由地址可以填index2或者scooldemo/index2都可以&#xff08;这个包含了文件夹路径&#xff09;&#xff0c;反正组件路径一定要填对就可以了。 …

【UML用户指南】-21-对基本行为建模-活动图

目录 1、概念 2、组成结构 2.1、动作 2.2、活动节点 2.3、控制流 2.4、分支 2.5、分岔和汇合 2.6、泳道 2.7、对象流 2.8、扩展区域 3、一般用法 3.1、对工作流建模 3.2、对操作建模 一个活动图从本质上说是一个流程图&#xff0c;展现从活动到活动的控制流 活动图…

MySQL命名规范(自用)

MtySQL命名规范 基本通用规范 1.【推荐】关键字必须大写 所有关键字必须大写&#xff0c;如&#xff1a;INSERT、UPDATE、DELETE、SELECT及其子句&#xff0c;IF……ELSE、CASE、DECLARE等 2.【强制】字段和建表必须写备注 COMMENT写备注 3.【强制】字母数字下划线 采用26个英…

国产MCU芯片(2):东软MCU概览及触控MCU

前言: 国产芯片替代的一个主战场之一就是mcu,可以说很多国内芯片设计公司都打算或者已经在设计甚至有了一款或多款的量产产品了,这也是国际大背景决定的。过去的家电市场、过去的汽车电子市场,的确国产芯片的身影不是很常见,如今不同了,很多fabless投身这个行业,一种是…