Redis分布式锁系列

1.压力测试出的内存泄漏及解决(可跳过)

使用jmeter对查询产品分类列表接口进行压力测试,出现了堆外内存溢出异常。
在这里插入图片描述
我们设置的虚拟机堆内存100m,并不是堆外内存100m
在这里插入图片描述
产生堆外内存溢出:OutOfDirectMemoryError
原因是因为:
springboot2.0以后默认使用lettuce作为操作redis的客户端。它使用netty进行网络通信。
lettuce的bug导致netty堆外内存溢出,-Xmx300m, netty如果没有指定堆外内存,默认使用-Xmx300m作为堆外内存。
由于在高并发的时候,获取的数据量非常大,高并发一进来,由于数据在传输期间都要占内存,导致内存分配不足,堆外内存溢出。

解决方案:不能使用-Dio.netty.maxDirectoryMemory只去调大堆外内存(只是延缓了问题的出现)
1)、升级lettuce客户端, 2)、切换使用Jedis
lettuce,jedis操作redis的底层客户端,spring再次封装redisTemplate

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

2.高并发下缓存失效的问题

2.1.缓存穿透

缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是 数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不 存在的数据每次请求都要到存储层去查询,失去了缓存的意义
在这里插入图片描述

风险: 利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃

解决: null结果缓存,并加入短暂过期时间

2.2.缓存雪崩

缓存雪崩: 缓存雪崩是指在我们设置缓存时key采用了相同的过期时间, 导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时 压力过重雪崩。
在这里插入图片描述

解决: 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这 样每一个缓存的过期时间的重复率就会降低,就很难引发集体 失效的事件。

2.3.缓存击穿

缓存穿透:

  • 对于一些设置了过期时间的key,如果这些key可能会在某些 时间点被超高并发地访问,是一种非常“热点”的数据。
  • 如果这个key在大量请求同时进来前正好失效,那么所有对 这个key的数据查询都落到db,我们称为缓存击穿。
    在这里插入图片描述

解决: 加锁大量并发只让一个去查,其他人等待,查到以后释放锁,其他 人获取到锁,先查缓存,就会有数据,不用去db

 * 1.空结果缓存,解决缓存穿透
 * 2.设置过期时间(加随机值),解决缓存雪崩
 * 3.加锁,解决缓存击穿

前两个都好解决,对于加锁的问题,如果锁没加好,又会有新的问题

直接加synchronized锁

// 只要是同一把锁,就能锁住需要这个锁的所有线程
// 1.synchronized(this):,springboot所有组件(这里是CategoryServiceImpl)在容器中都是单例的
// TODO 本地锁,syncrhonized, JUC(Lock) 分布式情况下,想要锁住所有,必须使用分布式锁
synchronized (this) {
   return getDataFromDb();
}

得到锁以后,应该再去缓存中确定一次,如果没有才需要继续查询。

String catelogJSON = stringRedisTemplate.opsForValue().get("catelogJSON");
if (!StringUtils.isEmpty(catelogJSON)) {
    // 如果缓存不为空,直接返回
    Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
    return result;
}

分布式下如何加锁
在这里插入图片描述
TODO 本地锁,syncrhonized, JUC(Lock) 分布式情况下,想要锁住所有,必须使用分布式锁

注意锁的时序问题
在这里插入图片描述
要保证查数据库和把结果放入缓存是原子操作,是在同一把锁内进行的,否则就会导致释放锁的时序问题,导致多次查询数据库。
开启多个服务的时候只能锁住本地服务
在这里插入图片描述

3.分布式锁的原理与使用

分布式锁演进-基本原理

在这里插入图片描述
我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。 “占坑”可以去redis,可以去数据库,可以去任何大家都能访问的地方。 等待可以自旋的方式。

占坑在redis中通常使用set命令,set的介绍如下:
https://www.cnblogs.com/zhouj850/p/10949359.html
SET key value [EX seconds] [PX milliseconds] [NX|XX]
设置k-v值
EX过期时间,PX毫秒,NX指的是Not Exist,不存在才给里面放。
所以使用SETNX命令可以进行占坑操作。

分布式锁演进-阶段一

在这里插入图片描述
代码如下:
在这里插入图片描述
setnx占好了位,业务代码异常或者程序在页面过程 中宕机。没有执行删除锁逻辑,这就造成了死锁
放在finally里面,也不行,机器宕机也会导致锁无法释放。
解决:设置锁的自动过期,即使没有删除,会自动删除

分布式锁演进-阶段二

在这里插入图片描述

代码如下:
在这里插入图片描述
问题: 1、setnx设置好,正要去设置过期时间,宕机。又死锁了。
解决: 设置过期时间和占位必须是原子的。redis支持使用setnx ex 命令

redis设置锁的过期时间的命令:
set lock 1111 EX 1000 NX

查询锁的过期时间命令:
ttl lock

设置过期时间,必须和加锁是同步的、原子的

分布式锁演进-阶段三

在这里插入图片描述
代码如下:
在这里插入图片描述
问题:
1、删除锁直接删除??? 如果由于业务时间很长,锁自己过期了,我们 直接删除,有可能把别人正在持有的锁删除了
解决: 占锁的时候,值指定为uuid,每个人匹配是自己 的锁才删除。

分布式锁演进-阶段四

在这里插入图片描述

问题:
1、如果正好判断是当前值,正要删除锁的时候,锁已经过期, 别人已经设置到了新的值。那么我们删除的是别人的锁

获取值对比+对比成功删除锁,一定要是原子操作。

解决: 删除锁必须保证原子性。使用redis+Lua脚本完成

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

在这里插入图片描述
**保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。 **

所有代码如下:

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        // 1.占分布式锁,去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("Lock", uuid, 300, TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb;
        if (lock) {
            System.out.println("获取分布式锁成功...");
            try {
                // 加锁成功。。。执行业务
                // 设置过期时间,必须和加锁是同步的、原子的
                //stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
                dataFromDb = getDataFromDb();
            } finally {
                // 获取值对比+对比成功删除锁,一定要是原子操作。
//            String lockValue = stringRedisTemplate.opsForValue().get("lock");
//            if (uuid.equals(lockValue)) {
//                // 删除自己的锁
//                stringRedisTemplate.delete("lock");
//            }
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                // 删除锁
                Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;

        } else {
            // 加锁失败。。。重试synchronized(), 重试自旋
            // 休眠100ms重试
            System.out.println("获取分布锁失败,,,");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            
            return getCatalogJsonFromDbWithRedisLock(); // 自旋的方式
        }
    }

更难的事情,锁的自动续期

Redis简介-整合

分布式场景下,还需要用到读写锁、信号量等机制,使用上述方案只能实现简单的分布式锁,因此我们需要借助Redissson框架。完成我们所有的分布式功能。

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
参考文档:https://github.com/redisson/redisson/wiki/1.-%E6%A6%82%E8%BF%B0

        <!--以后使用redisson作为所有分布式锁,分布式对象功能框架-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

将redisson作为分布式锁等功能框架

  •  1)、引入依赖
    
  •  2)、配置redisson
    

Redisson的配置

@Configuration
public class MyRedissonConfig {
    /**
     * 所有对redissson的使用都是通过RedissonClient对象
     * @Bean给容器中放一个组件对象
     * **/
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
        // 创建配置(单节点配置)
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        // 根据config创建出redissonClient示例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }

}

Redisson-lock锁

简介

可重入锁(Reentrant Lock)
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口(和本地锁一样的使用方式)。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RLock lock = redisson.getLock(“anyLock”);
// 最常见的使用方法
lock.lock();
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}



测试

Redisson的优点:
1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删
2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除

lock.lock(100, TimeUnit.SECONDS); //10秒自动解锁,自动解锁的时间一定要大于业务的执行时间。
问题,lock.lock(10, TimeUnit.SECONDS),在锁时间到了以后,不会自动续期。
1.如果我们传递了锁的超时时间,发送给redis执行脚本,进行占锁,默认超时就是我们指定时间
2.如果我们未指定锁的超时时间,就使用30*1000【LockWatchDogTimeout看门狗的默认时间】

只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期,续期满30s
internalLockLeaseTime【看门狗时间】/ 1/3, 10s

测试代码

@ResponseBody
    @GetMapping("/lock")
    public String lock() {
        // 1.获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
        // Redisson的优点:
        // 1)、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删
        // 2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除。
//        lock.lock();            // 阻塞式等待,默认加的锁都是30s
        lock.lock(100, TimeUnit.SECONDS);    //10秒自动解锁,自动解锁的时间一定要大于业务的执行时间。
        // 问题,lock.lock(10, TimeUnit.SECONDS),在锁时间到了以后,不会自动续期。
        // 1.如果我们传递了锁的超时时间,发送给redis执行脚本,进行占锁,默认超时就是我们指定时间
        // 2.如果我们未指定锁的超时时间,就使用30*1000【LockWatchDogTimeout看门狗的默认时间】
        //      只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期,续期满30s
        //      internalLockLeaseTime【看门狗时间】/ 1/3, 10s
        // 最佳实战,
        // 1).lock.lock(10,TimeUnit.SECONDS), 省掉了续期操作,手动解锁
        try{
            System.out.println("加锁成功,执行业务" + Thread.currentThread().getId());
            Thread.sleep(30000);
        } catch (Exception e) {

        } finally {
            // 3.解锁  假设代码没有运行,redisson会不会出现死锁
            System.out.println("释放锁..." + Thread.currentThread().getId());
            lock.unlock();
        }
        return "hello";
    }

lock.lock(10,TimeUnit.SECONDS), 省掉了续期操作,手动解锁

读写锁(ReadWriteLock)

简介:

基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。

分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

测试

保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁, 独享锁),读锁是一个共享锁
写锁没释放读就必须等待

  • 读 + 读 相当于无锁,并发读,只会在redis记录好,所有当前的读锁,他们都会同时加锁成功
  • 写 + 读,等待写锁释放
  • 写 + 写,阻塞方式
  • 读 + 写,有读锁,写也需要等待

只要有写的存在,都必须等待

测试代码如下:

@GetMapping("/write")
    @ResponseBody
    public String writeValue() {
        String s = "";
        RReadWriteLock lock = redisson.getReadWriteLock("rw_lock");
        RLock rLock = lock.writeLock();
        try {
            rLock.lock();
            // 改数据加写锁,读数据加读锁
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue", s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return s;
    }

    @GetMapping("/read")
    @ResponseBody
    public String readValue() {
        // 读写锁还是同一把读写锁
        RReadWriteLock lock = redisson.getReadWriteLock("rw_lock");
        // 加读锁
        RLock rLock = lock.readLock();
        // 加读锁
        String s = "";
        try {
            rLock.lock();
            s = redisTemplate.opsForValue().get("writeValue");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        return s;
    }

闭锁(CountDownLatch)

简介

基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();

// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();

测试

/**
 * 放假,锁门
 * 5个半全部走完,我们可以锁大门
 * **/
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);
    door.await(); //等待闭锁都完成
    return "放假了...";
}


@GetMapping("/gogogo/{id}")
public String gogogo(@PathVariable("id") Long id) {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown();  // 计数减1
    return id + "班的人都走了....";
}

信号量(Semaphore)

简介

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();

测试

    /**
     * 车库停车
     * 3车位
     * 信号量可以用来做分布式限流
     * **/
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
//        park.acquire(); // 获取一个信号,获取一个值,占一个车位
        boolean b = park.tryAcquire();
        if (b) {
            // 执行业务
        } else {

        }
        return "ok";
    }

    @GetMapping("/go")
    @ResponseBody
    public String go() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.release(); // 释放一个车位
        return "ok";
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

某大厂面试题:说一说Java、Spring、Dubbo三者SPI机制的原理和区别

大家好&#xff0c;我是三友~~ 今天来跟大家聊一聊Java、Spring、Dubbo三者SPI机制的原理和区别。 其实我之前写过一篇类似的文章&#xff0c;但是这篇文章主要是剖析dubbo的SPI机制的源码&#xff0c;中间只是简单地介绍了一下Java、Spring的SPI机制&#xff0c;并没有进行深…

SQL——数据查询DQL

基本语句、时间查询&#xff08;当天、本周&#xff0c;本月&#xff0c;上一个月&#xff0c;近半年的数据&#xff09;。 目录 1 查询语句基本结构 2 where 子句 3 条件关系运算符 4 条件逻辑运算符 5 like 子句 6 计算列 7 as 字段取别名 8 distinct 清除重复行 9 …

linux mysql

安装 下载包 wget https://cdn.mysql.com/archives/mysql-8.0/mysql-8.0.31-1.el8.x86_64.rpm-bundle.tar解压 tar -zxvf mysql-8.0.31-1.el8.x86_64.rpm-bundle.tar -C /usr/local/mysql安装openssl-devel插件 yum install openssl-devel安装rpm包 使用rpm -ivh安装图中r…

【Unity项目实战】从零手戳一个背包系统

首先我们下载我们的人物和背景资源,因为主要是背包系统,所以人物的移动和场景的搭建这里我们就不多讲了,我这里直接提供基础项目源码给大家去使用就行 基础项目下载地址: 链接: https://pan.baidu.com/s/1o7_RW_QQ1rrAbDzT69ApRw 提取码: 8s95 顺带说一下,这里用到了uni…

AttributeError: module transformers has no attribute LLaMATokenizer解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

AES加密

来源&#xff1a;链接: b站up主可厉害的土豆 &#xff08;据评论区说图片中有计算错误&#xff0c;但是过程是对的。只是了解过程问题不大&#xff0c;专门研究这一块的话自己可以看视频算一下&#xff09; 准备 首先&#xff0c;明文是固定长度 16字节 128位。 密钥长度可以…

TCP协议一

TCP数据报格式 TCP通信时序 下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。 在这个例子中&#xff0c;首先客户端主动发起连接、发送请求&#xff0c;然后服务器端响应请求&#xff0c;然后客户端主动关闭连接。两条竖线表示通讯的两端&…

houjie-cpp面向对象

houjie 面向对象 面向对象&#xff08;上&#xff09; const 在一个函数后面放const&#xff0c;这个只能修饰成员函数&#xff0c;告诉编译器这个成员函数不会改数据 const还是属于函数签名的一部分。 引用计数&#xff1a;涉及到共享的东东&#xff0c;然后当某个修改的时候&…

Mysql的学习与巩固:一条SQL查询语句是如何执行的?

前提 我们经常说&#xff0c;看一个事儿千万不要直接陷入细节里&#xff0c;你应该先鸟瞰其全貌&#xff0c;这样能够帮助你从高维度理解问题。同样&#xff0c;对于MySQL的学习也是这样。平时我们使用数据库&#xff0c;看到的通常都是一个整体。比如&#xff0c;你有个最简单…

【Paper】2019_Resilient Consensus Through Asynchronous Event-based Communication

Wang Y, Ishii H. Resilient consensus through asynchronous event-based communication[C]//2019 American Control Conference (ACC). IEEE, 2019: 1842-1847. 文章目录I. INTRODUCTIONII. EVENT-BASED RESILIENT CONSENSUS PROBLEMA. Preliminaries on graphsB. Event-base…

基于Java+ SpringBoot+Vue 的网上图书商城管理系统(毕业设计,附源码,教程)

您好&#xff0c;我是程序员徐师兄&#xff0c;今天为大家带来的是 基于Java SpringBootVue 的网上图书商城管理系统&#xff08;毕业设计&#xff0c;附源码&#xff0c;教程&#xff09;。 &#x1f601; 1.Java 毕业设计专栏&#xff0c;毕业季咱们不慌忙&#xff0c;几百款…

电脑桌面图标间距突然变大怎么恢复

1. WindowsR打开 > 输入regedit 按住WindowsR打开运行&#xff0c;输入regedit并点击确定。 2. 双击Control Panel 双击展开HKEY_CURRENT_USER&#xff0c;双击展开Control Panel&#xff0c;双击展开Desktop。 3. 更改间距 点击打开WindowMetrics&#xff0c; 双击打开…

两年外包生涯,给我后面入职字节跳动奠定了基础.....

我是一位软件测试工程师。从大学毕业后&#xff0c;我进入了一家外包公司&#xff0c;在那里工作了两年时间。虽然我在公司中得到了不少锻炼和经验&#xff0c;但是我一直渴望能够进入一家更加专业的公司&#xff0c;接触更高端、更有挑战性的项目。 于是&#xff0c;我开始主…

Keil 4 安装教程及简单使用【嵌入式系统】

Keil 4 安装教程及简单使用【嵌入式系统】前言推荐说明Keil 4 for Arm安装教程1.安装MDK2.激活mdkkeil 4 for arm 的简单使用1建立新工程2在工程下创建新文件3.设置工程属性4.中文注释5.编辑代码6.build7.debug8. 调试窗口简介keil 4 for C51安装教程1.前期准备2.开始keil4 for…

记录-VueJs中如何使用Teleport组件

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 在DOM结构相对比较复杂,层级嵌套比较深的组件内,需要根据相对应的模块业务处理一些逻辑,该逻辑属于当前组件 但是从整个页面应用的视图上看,它在DOM中应该被渲染在整个vue应用外部的其他地方,不能影响…

[架构之路-159]-《软考-系统分析师》-10-系统分析-6-现有业务流程分析, 系统分析最核心的任务

目录 第 10章 现有系统 分 析 1 0 . 6 现有业务流程分析 10.6.1 业务流程分析槪述 1 . 业务流程分析的步骤 2 . 业务流程分析的方法 10.6.2 业务-流程图TFD 1. T F D 的基本符号 2. TFD的绘制 10.6.3 业务 - 活动图 10.6.4 业务流程建模BPM 1. B P M 概述 2 . 标杆…

计算机视觉基础__图像特征

计算机视觉基础__图像特征 本篇目录&#xff1a; 一、前言 二、位图和矢量图概念 三、图像的颜色特征 四、RGB 颜色空间 五、HSV 颜色空间 六、HLS 颜色空间 七、实例代码 八、参考资料 一、前言 传统图像处理&#xff0c;需要找出图片中的关键特征&#xff0c;然后对这…

window端口占用如何杀死进程

1、输入命令&#xff1a;netstat -ano|findstr “8099” 2、杀死命令 taskkill /PID 2980 -T -F

Python机器学习:朴素贝叶斯

前两天不知道把书放哪去了&#xff0c;就停更了一下&#xff0c;昨天晚上发现被我放在书包夹层里面了&#xff0c;所以今天继续开始学习。 首先明确一下啊&#xff0c;朴素贝叶斯是什么&#xff1a;朴素贝叶斯分类器是一种有监督的统计学过滤器&#xff0c;在垃圾邮件过滤、信…

服务 API 设计之 ——API 参数规范

【强制】字段名称用小驼峰风格 【强制】Service API 返回值必须使用 Response 包装 Service API 返回值强制要求进行通用包装&#xff0c;例如&#xff1a;Response。Response 的作用&#xff1a; 统一方法表示 API 调用是否成功API 调用失败时&#xff0c;统一格式反馈错误 C…