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

Redis之分布式锁

面试切入点
在这里插入图片描述
在这里插入图片描述

锁的分类

  • 单机版同一个JVM虚拟机内,synchronized或者Lock接口
  • 分布式多个不同JVM虚拟机,单机的线程锁不再起作用,资源类在不同的服务器之间共享了

一个靠谱分布式锁需要具备的条件与刚需

  • 独占性:onlyOne,任何时刻只能有且仅有一个线程持有
  • 高可用:若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况。高并发请求下,依旧性能OK好使
  • 防死锁:杜绝死锁,必须有超时控制机制或者撤销机制操作,有个兜底终止跳出方案。
  • 不乱抢:防止张冠李戴,不能私下unlock别人的锁,只能自己加锁,自己释放锁,自己的锁含着泪也要自己去解
  • 重入性:同一个节点的同一个线程如果获得锁后,它也可以再次获取这个锁。

分布式锁
在这里插入图片描述
setnx key value
在这里插入图片描述
set key value [EX seconds] [PX milliseconds] [NX|XX]

案例演示扣减库存

V1版本:JVM可重入锁的版本

 private ReentrantLock lock = new ReentrantLock();//V1.0 基础版本public String saleV1(){String retMessage="";lock.lock();try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}}finally {lock.unlock();}return retMessage+"\t"+"服务端口号"+port;}

swagger结果
在这里插入图片描述
V2版本:分布式部署,将V1版本copy一份,端口为8888,同时用nginx路由转发
在这里插入图片描述
docker部署nginx
本地做好nginx.conf与宿主机的映射
nginx.conf的默认配置

#
#user  nobody;
worker_processes  1;#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;#pid        logs/nginx.pid;events {worker_connections  1024;
}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;server {listen       80;server_name  localhost;#charset koi8-r;#access_log  logs/host.access.log  main;location / {root   html;index  index.html index.htm;}#error_page  404              /404.html;# redirect server error pages to the static page /50x.html#error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}# proxy the PHP scripts to Apache listening on 127.0.0.1:80##location ~ \.php$ {#    proxy_pass   http://127.0.0.1;#}# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000##location ~ \.php$ {#    root           html;#    fastcgi_pass   127.0.0.1:9000;#    fastcgi_index  index.php;#    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;#    include        fastcgi_params;#}# deny access to .htaccess files, if Apache's document root# concurs with nginx's one##location ~ /\.ht {#    deny  all;#}}# another virtual host using mix of IP-, name-, and port-based configuration##server {#    listen       8000;#    listen       somename:8080;#    server_name  somename  alias  another.alias;#    location / {#        root   html;#        index  index.html index.htm;#    }#}# HTTPS server##server {#    listen       443 ssl;#    server_name  localhost;#    ssl_certificate      cert.pem;#    ssl_certificate_key  cert.key;#    ssl_session_cache    shared:SSL:1m;#    ssl_session_timeout  5m;#    ssl_ciphers  HIGH:!aNULL:!MD5;#    ssl_prefer_server_ciphers  on;#    location / {#        root   html;#        index  index.html index.htm;#    }#}
}

在这里插入图片描述
docker 启动命令

docker run -d --name nginx  -p 80:80  -v /home/run/nginx/conf/nginx.conf:/etc/nginx/nginx.conf   -v /home/run/nginx/html:/etc/nginx/html  docker.1ms.run/library/nginx

nginx验证
在这里插入图片描述
修改nginx的配置加上负载均衡+反方向代理
在这里插入图片描述

负载均衡的效果
在这里插入图片描述

手工点击是OK的,模拟高并发100个请求
在这里插入图片描述
redis中还有多少数据
在这里插入图片描述
重复下单数据,出现了超卖现象
在这里插入图片描述
为什么加了 synchronized 或者 Lock 还是没有控制住?
在这里插入图片描述
分布式锁的出现

  • 跨进程+跨服务
  • 解决超卖
  • 防止缓存击穿

redis分布式锁V1版本

 //V2版本:public String saleV2(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();//分布式锁的设置Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);//抢不到的线程继续重试if(!flag){//暂停20毫秒,递归重试try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}saleV2();}else{//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {// 释放分布式锁stringRedisTemplate.delete(key);}}return retMessage+"\t"+"服务端口号"+port;}

结果
在这里插入图片描述
扣减库存为0
在这里插入图片描述
存在的问题
测试手工OK,测试Jmeter压测5000OK
递归是一种思想没错,但是容易导致StackOverflowError,不太推荐,进一步完善
多线程判断想想JUC里面说过的虚假唤醒,用while替代if
用自旋替代递归重试

redis分布式锁V2版本:用while替换if,用自旋替换递归

 //V3版本:public String saleV3(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();//分布式锁的设置Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);// 用自旋替代递归、用while替换ifwhile (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){//暂停20毫秒try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {// 释放分布式锁stringRedisTemplate.delete(key);}return retMessage+"\t"+"服务端口号"+port;}

上面版本存在的问题:
部署了微服务的Java程序机器挂了,代码层面根本没有走到finally这块,
没办法保证解锁(无过期时间该key一直存在),这个key没有被删除,需要加入一个过期时间限定key

redis分布式锁版本3.0:宕机与过期+防止死锁

//V4版本:public String saleV4(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();// 用自旋替代递归、用while替换ifwhile (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){//暂停20毫秒try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}//添加过期时间stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {// 释放分布式锁stringRedisTemplate.delete(key);}return retMessage+"\t"+"服务端口号"+port;}

存在的问题
设置key+过期时间分开了,必须要合并成一行具备原子性

redis 分布式锁版本3.1

 public String saleV4(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();// 用自旋替代递归、用while替换ifwhile (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){//暂停20毫秒try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}//添加过期时间//stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {// 释放分布式锁stringRedisTemplate.delete(key);}return retMessage+"\t"+"服务端口号"+port;}

结论:加锁与过期时间必须同一行,保证原子性

redis分布式锁版本4:防止key误删的问题
实际业务处理时间如果超过了默认设置key的过期时间??尴尬 ̄□ ̄||
在这里插入图片描述

张冠李戴,删除了别人的锁

解决: 只能自己删除自己的,不许动别人的

//V5版本:public String saleV5(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();// 用自旋替代递归、用while替换ifwhile (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){//暂停20毫秒try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}//添加过期时间//stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {// 改进点:只能删除属于自己的key,不能删除别人的if(stringRedisTemplate.opsForValue().get(key).equals(uuidValue)){stringRedisTemplate.delete(key);}}return retMessage+"\t"+"服务端口号"+port;}

redis分布式锁5.0版本:Lua脚本保证原子性
上个版本,finally块的判断+del删除操作不是原子性的
Lua脚本
在这里插入图片描述
官方脚本
在这里插入图片描述
Redis调用Lua脚本通过eval命令保证代码执行的原子性,直接用return返回脚本执行后的结果。

eval "redis.call('set', 'k1', 'v1') redis.call('expire', 'k1', '30')  return  redis.call('get', 'k1')" 0

在这里插入图片描述

eval "return redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 k1 k2 lua1 lua2

在这里插入图片描述
redis get+del命令Lua脚本的原子操作

 eval "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 redisLock 12345

在这里插入图片描述
Lua脚本条件判断分支
在这里插入图片描述
在这里插入图片描述

redis分布式锁V5版本

//V6版本:public String saleV6(){String retMessage="";String key="redisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();// 用自旋替代递归、用while替换ifwhile (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){//暂停20毫秒try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}//添加过期时间//stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);//抢锁成功的线程继续进行正常的业务逻辑操作 扣减库存try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}} finally {//改进点,修改为Lua脚本的redis分布式锁调用,必须保证原子性,参考官网脚本案例String luaScript ="if redis.call('get',KEYS[1]) == ARGV[1] then " +"return redis.call('del',KEYS[1]) " +"else " +"return 0 " +"end";stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Boolean.class), Arrays.asList(key),uuidValue);}return retMessage+"\t"+"服务端口号"+port;}

redis分布式锁V6:可重入锁+设计模式
上一个版本中while判断并自旋重试获取锁+setnx含自然过期+Lua脚本官网删除锁的命令。
存在的问题:如何兼顾锁的可重入性问题
写好一个锁的条件与规约
在这里插入图片描述
可重入锁(又名递归锁)
可重入锁又名递归锁。是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
”可重入锁“这四个字分开来解释:
可(可以)重(再次)入(进入)锁(同步锁)
进入什么?进入同步域(即同步代码块/方法或显式锁锁定的代码)
一句话:一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁。
可重入锁的分类(隐式锁与显式锁)
隐式锁:也就是synchronized关键字使用的锁,默认是可重入锁。指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面的三种情况出现的锁中锁,如果没有可重入性(持有同一把锁)就会产生死锁了

同步块

public class ReEntryLockDemo
{public static void main(String[] args){final Object objectLockA = new Object();new Thread(() -> {synchronized (objectLockA){System.out.println("-----外层调用");synchronized (objectLockA){System.out.println("-----中层调用");synchronized (objectLockA){System.out.println("-----内层调用");}}}},"a").start();}
}

同步方法

/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEntryLockDemo
{public synchronized void m1(){System.out.println("-----m1");m2();}public synchronized void m2(){System.out.println("-----m2");m3();}public synchronized void m3(){System.out.println("-----m3");}public static void main(String[] args){ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();reEntryLockDemo.m1();}
}

Synchronized锁重入的实现原理
在这里插入图片描述
显式锁(即Lock) 也有ReentrantLock这样的可重入锁

/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEntryLockDemo
{static Lock lock = new ReentrantLock();public static void main(String[] args){new Thread(() -> {lock.lock();try{System.out.println("----外层调用lock");lock.lock();try{System.out.println("----内层调用lock");}finally {// 这里故意注释,实现加锁次数和释放次数不一样// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。lock.unlock(); // 正常情况,加锁几次就要解锁几次}}finally {lock.unlock();}},"a").start();new Thread(() -> {lock.lock();try{System.out.println("b thread----外层调用lock");}finally {lock.unlock();}},"b").start();}
}

切记:一般而言,lock了几次就要unlock几次

思考:上面可重入锁的计数问题,redis中的哪个数据类型可以代替?
K,K,V
Map<String,Map<Object,Object>>
在这里插入图片描述
案例
hincrby 加了几次1 最后再减去几次1,直到为0。也就是可重入性的lock几次再unlock几次。
在这里插入图片描述

小总结:
setnx只能解决无的问题,够用但不够完美。hset,不但可以解决有无,还可以解决可重入性的问题。
设计重点(两条线)
目前有2条支线,目的是保证同一个时候只能有一个线程持有锁进去redis做扣减库存的动作。
2个分分支

  • 保证加锁、解锁(lock\unlock)
    在这里插入图片描述

  • 扣减库存redis命令的原子性
    在这里插入图片描述

在这里插入图片描述
Lua脚本实现Lock与Unlock的操作
加锁lua脚本lock
先判断redis分布式锁这个key是否存在。
Exists Key 返回0 说明不存在,hset新建当前线程属于自己的锁BY UUID:ThreadId.
Exists Key返回1 说明已有锁,需要进一步判断是不是当前线程自己的。HEXISTS key uuid:ThreadID 返回0 说明不是自己的,返回1说明是自己的锁,自增1次表示重入。
V1版本的Lua脚本

if redis.call('exists','key') == 0 thenredis.call('hset','key','uuid:threadid',1)redis.call('expire','key',30)return 1elseif redis.call('hexists','key','uuid:threadid') == 1 thenredis.call('hincrby','key','uuid:threadid',1)redis.call('expire','key',30)return 1elsereturn 0end

相同部分是否可以替换处理???
hincrby命令可否替代为hset命令??
V2版本

if redis.call('exists','key') == 0 or redis.call('hexists','key','uuid:threadid') == 1 thenredis.call('hincrby','key','uuid:threadid',1)redis.call('expire','key',30)return 1
elsereturn 0
end

KEYS[1]与ARGV[1]的参数化提取与处理

if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 elsereturn 0
end

在这里插入图片描述
测试

EVAL "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end" 1 redisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 30

在这里插入图片描述
解锁lua脚本unlock
设计思路:有锁且还是自己的锁
Hexists key uuid:ThreadId 返回0,说明根本没有锁,程序块返回nil。不是0,说明有锁且是自己的锁,直接调用HINCRBY 负1,表示每次减个1,解锁1次。直到它变为0表示可以删除该锁key,del 锁key
在这里插入图片描述
在这里插入图片描述

V1版本


if redis.call('HEXISTS',lock,uuid:threadID) == 0 thenreturn nil
elseif redis.call('HINCRBY',lock,uuid:threadID,-1) == 0 thenreturn redis.call('del',lock)
else return 0
end

V2版本参数化处理

if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 thenreturn nilelseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 thenreturn redis.call('del',KEYS[1])elsereturn 0end

测试

eval "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then return redis.call('del',KEYS[1]) else return 0 end" 1 redisLock 2f586ae740a94736894ab9d51880ed9d:1

在这里插入图片描述
在这里插入图片描述
整合到微服务代码中
RedisLock实现Lock接口

package com.atguigu.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;
import java.util.PrimitiveIterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 自研分布式锁,实现Lock接口*/
public class RedisLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName;private String uuidValue;private long expireTime;public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue= IdUtil.simpleUUID()+":"+Thread.currentThread().getId();this.expireTime = 50L;}@Overridepublic void lock() {tryLock();}@Overridepublic void unlock() {String script="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then" +" return nil " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +"return redis.call('del',KEYS[1])" +" else " +"return 0" +" end";// nil ==false 1==true 0==falseLong executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));if(null==executeFlag){throw new RuntimeException("this lock doesnt exists");}}@Overridepublic boolean tryLock() {try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if(time==-1L){String script="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +" redis.call('hincrby',KEYS[1],ARGV[1],1) " +"redis.call('expire',KEYS[1],ARGV[2])" +" return 1 " +"else" +" return 0 end";Boolean executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));while (!executeFlag){//60 ms后再重试TimeUnit.MILLISECONDS.sleep(60);executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));}return  true;}return false;}@Overridepublic Condition newCondition() {return null;}@Overridepublic void lockInterruptibly() throws InterruptedException {}
}

调用redisLock的lock与unlock方法

  private Lock redisLock = new RedisLock(stringRedisTemplate,"redisLock");//V7版本public String saleV7(){String retMessage="";redisLock.lock();try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}}finally {redisLock.unlock();}return retMessage+"\t"+"服务端口号"+port;}

利用工厂模式进行优化

package com.atguigu.redislock.mylock;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.locks.Lock;@Component
public class DistributeLockFactory {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;public Lock getDistributeLock(String lockType) {if(lockType==null){return null;}if(lockType.equalsIgnoreCase("REDIS")){this.lockName = "redisLock";return new RedisLock(stringRedisTemplate,lockName);}else if (lockType.equalsIgnoreCase("zookeeper")){this.lockName = "zookeeperLock";//TODOreturn null;}else if (lockType.equalsIgnoreCase("mysql")){this.lockName = "mysqlLock";//TODOreturn null;}return null;}
}
    @Autowiredprivate DistributeLockFactory distributeLockFactory;//V7版本public String saleV7(){String retMessage="";Lock redisLock= distributeLockFactory.getDistributeLock("redis");redisLock.lock();try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);}else{retMessage="商品卖完了";}}finally {redisLock.unlock();}return retMessage+"\t"+"服务端口号"+port;}

压测+验证
在这里插入图片描述
redis中的库存
在这里插入图片描述
后台日志
在这里插入图片描述
在这里插入图片描述
可重入性验证代码

//V7版本public String saleV7(){String retMessage="";Lock redisLock= distributeLockFactory.getDistributeLock("redis");redisLock.lock();try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);testReentry();}else{retMessage="商品卖完了";}}finally {redisLock.unlock();}return retMessage+"\t"+"服务端口号"+port;}private void testReentry() {Lock redisLock= distributeLockFactory.getDistributeLock("redis");redisLock.lock();try {System.out.println("==========测试可重入锁==================");}finally {redisLock.unlock();}}

出现的问题
在这里插入图片描述
线程Id一致,但是uuid不一致
问题修复

package com.atguigu.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.locks.Lock;@Component
public class DistributeLockFactory {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;private String uuid;public DistributeLockFactory(){this.uuid = IdUtil.simpleUUID();}public Lock getDistributeLock(String lockType) {if(lockType==null){return null;}if(lockType.equalsIgnoreCase("REDIS")){this.lockName = "redisLock";return new RedisLock(stringRedisTemplate,lockName,uuid);}else if (lockType.equalsIgnoreCase("zookeeper")){this.lockName = "zookeeperLock";//TODOreturn null;}else if (lockType.equalsIgnoreCase("mysql")){this.lockName = "mysqlLock";//TODOreturn null;}return null;}
}
package com.atguigu.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;
import java.util.PrimitiveIterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 自研分布式锁,实现Lock接口*/
public class RedisLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName;private String uuidValue;private long expireTime;public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue= IdUtil.simpleUUID()+":"+Thread.currentThread().getId();this.expireTime = 25L;}public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue= uuid;this.expireTime = 25L;}@Overridepublic void lock() {tryLock();}@Overridepublic void unlock() {String script="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then" +" return nil " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +"return redis.call('del',KEYS[1])" +" else " +"return 0" +" end";// nil ==false 1==true 0==falseSystem.out.println("unlock lockName:"+lockName+"\t"+"uuidValue:"+uuidValue+"\t");Long executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));if(null==executeFlag){throw new RuntimeException("this lock doesnt exists");}}@Overridepublic boolean tryLock() {try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if(time==-1L){String script="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +" redis.call('hincrby',KEYS[1],ARGV[1],1) " +"redis.call('expire',KEYS[1],ARGV[2])" +" return 1 " +"else" +" return 0 end";System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue+"\t");Boolean executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));while (!executeFlag){//60 ms后再重试TimeUnit.MILLISECONDS.sleep(60);executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));}return  true;}return false;}@Overridepublic Condition newCondition() {return null;}@Overridepublic void lockInterruptibly() throws InterruptedException {}
}

测试验证
在这里插入图片描述
在这里插入图片描述
自动续期

确保redisLock的过期时间大于业务执行时间的问题。redis分布式锁如何续期??
CAP

  • redis集群是AP

    redis异步复制造成的锁丢失
    比如:主节点没来的及把刚刚set进来这条数据给从节点,master就挂了,从机上位但从机上无该数据

  • zookeeper集群是CP
    在这里插入图片描述
    故障
    在这里插入图片描述

  • Eureka集群是AP
    在这里插入图片描述

  • Nacos集群是AP
    在这里插入图片描述

加个钟 Lua脚本
在这里插入图片描述
redis分布式锁加锁成功后续期
在这里插入图片描述

package com.atguigu.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;
import java.util.PrimitiveIterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 自研分布式锁,实现Lock接口*/
public class RedisLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName;private String uuidValue;private long expireTime;public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue= IdUtil.simpleUUID()+":"+Thread.currentThread().getId();this.expireTime = 25L;}public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue= uuid;this.expireTime = 30L;}@Overridepublic void lock() {tryLock();}@Overridepublic void unlock() {String script="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then" +" return nil " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +"return redis.call('del',KEYS[1])" +" else " +"return 0" +" end";// nil ==false 1==true 0==falseSystem.out.println("unlock lockName:"+lockName+"\t"+"uuidValue:"+uuidValue+"\t");Long executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));if(null==executeFlag){throw new RuntimeException("this lock doesnt exists");}}@Overridepublic boolean tryLock() {try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if(time==-1L){String script="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +" redis.call('hincrby',KEYS[1],ARGV[1],1) " +"redis.call('expire',KEYS[1],ARGV[2])" +" return 1 " +"else" +" return 0 end";System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue+"\t");Boolean executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));while (!executeFlag){//60 ms后再重试TimeUnit.MILLISECONDS.sleep(60);executeFlag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));}// 新建一个扫描程序,监控对应key的ttl是否到规定的1/3 进行续期resetExpireTime();return  true;}return false;}private void resetExpireTime() {String script ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +"return redis.call('expire',KEYS[1],ARGV[2]) " +"else " +"return 0 " +"end";// time调度方法new Timer().schedule(new TimerTask(){@Overridepublic void run(){if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {resetExpireTime();}}},(this.expireTime * 1000)/3);}@Overridepublic Condition newCondition() {return null;}@Overridepublic void lockInterruptibly() throws InterruptedException {}
}
//V8版本public String saleV8(){String retMessage="";Lock redisLock= distributeLockFactory.getDistributeLock("redis");redisLock.lock();try {//查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//判断库存是否足够Integer inventory =result==null?0: Integer.valueOf(result);//扣减库存if(inventory>0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventory));retMessage="成功卖出一个商品,库存剩余:"+inventory;System.out.println(retMessage+"\t"+"服务端口号"+port);try {TimeUnit.SECONDS.sleep(120);} catch (InterruptedException e) {throw new RuntimeException(e);}}else{retMessage="商品卖完了";}}finally {redisLock.unlock();}return retMessage+"\t"+"服务端口号"+port;}

验证续期成功
在这里插入图片描述
redis分布式锁总结
在这里插入图片描述
在这里插入图片描述
视频链接
Redis分布式锁

相关文章:

Redis之分布式锁

面试切入点 锁的分类 单机版同一个JVM虚拟机内&#xff0c;synchronized或者Lock接口分布式多个不同JVM虚拟机&#xff0c;单机的线程锁不再起作用&#xff0c;资源类在不同的服务器之间共享了 一个靠谱分布式锁需要具备的条件与刚需 独占性&#xff1a;onlyOne&#xff…...

AF3 ProteinDataset类的__getitem__方法解读

AlphaFold3 protein_dataset 模块 ProteinDataset 类 __getitem__ 方法用于从数据集中获取一个条目,并根据配置对数据进行处理。 源代码: def __getitem__(self, idx):"""Return an entry from the dataset.If a clusters file is provided, then the idx i…...

NLP 梳理02 — 标点符号和大小写

文章目录 一、说明二、为什么文本预处理中需要小写2.1 为什么小写在文本预处理中至关重要&#xff1f;2.2 区分大小写对 NLP 任务的影响 三、删除标点符号及其对 NLP 任务的影响3.1 什么是标点符号&#xff1f;3.2 为什么在文本预处理中删除标点符号&#xff1f;3.3 删除标点符…...

HarmonyOS中的多线程并发机制

目录 多线程并发1. 多线程并发概述2 多线程并发模型3 TaskPool简介4 Worker简介4.1 Woker注意事项4.2 Woker基本用法示例 5. TaskPool和Worker的对比5.1 实现特点对比5.2 适用场景对比 多线程并发 1. 多线程并发概述 并发模型是用来实现不同应用场景中并发任务的编程模型&…...

游戏引擎学习第221天:(实现多层次过场动画)

资产: intro_art.hha 已发布 在下载页面&#xff0c;你会看到一个新的艺术包。你将需要这个艺术包来进行接下来的开发工作。这个艺术包是由一位艺术家精心制作并打包成我们设计的格式&#xff0c;旨在将这些艺术资源直接应用到游戏中。它包含了许多我们会在接下来的直播中使用…...

Python | 在Pandas中按照中值对箱形图排序

箱形图是可视化数据分布的强大工具&#xff0c;因为它们提供了对数据集内的散布、四分位数和离群值的洞察。然而&#xff0c;当处理多个组或类别时&#xff0c;通过特定的测量&#xff08;如中位数&#xff09;对箱形图进行排序可以提高清晰度并有助于揭示模式。在本文中&#…...

openapi + knife4j的使用

一、依赖作用与关系 1. springdoc-openapi-starter-webmvc-api • 核心功能&#xff1a; 基于 OpenAPI 3 规范&#xff0c;自动生成 API 文档元数据&#xff08;JSON 格式&#xff09;&#xff0c;并集成 Spring MVC。 提供Tag Operation、Schema 等注解&#xff0c;支持通过…...

数据结构*包装类泛型

包装类 什么是包装类 在讲基本数据类型的时候&#xff0c;有提到过包装类。 基本数据类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean 我们知道&#xff1a;基本数据类型并不是对象&#xff0c;没有对象所具有的方法和属…...

Azure Synapse Dedicated SQL pool里大型表对大型表分批合并数据的策略

Azure Synapse Dedicated SQL pool中大型表的数据通过MERGE INTO语句合并到另一张大型表的时间很长&#xff0c;容易造成运行超时&#xff0c;而有的时候超时的时间是管理设置&#xff0c;由客户控制&#xff0c;无法修改。这种时候为了确保操作可以运行成功&#xff0c;需要将…...

Day81 | 灵神 | 快慢指针 链表的中间结点 环形链表

Day81 | 灵神 | 快慢指针 链表的中间结点 环形链表 876.链表的中间结点 876. 链表的中间结点 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 设置两个指针&#xff0c;一个快指针r一个慢指针l 初始都是头结点 我们要求的是中间节点 所以快指针走两步&#x…...

【DDR 内存学习专栏 1.2 -- DDR Channel 介绍】

文章目录 1. DDR中的通道&#xff08;Channel&#xff09;概念1.1 DDR Channel 与 DDRC1.2 DIMM 内存插槽1.3 物理通道的定义1.3.1 多通道的作用 1.4 通道的硬件实现1.5 多核系统的DDR通道分配策略 1. DDR中的通道&#xff08;Channel&#xff09;概念 关于 DDR 通道&#xff…...

深入解析xDeepFM:结合压缩交互网络与深度神经网络的推荐系统新突破

今天是周日&#xff0c;我来解读一篇有趣的文章——xDeepFM。这篇文章由 Mao et al. 发表在SIGIR 2019会议。文章提出了一个新的网络模型——压缩交互网络&#xff08;CIN&#xff09;&#xff0c;用于显式地学习高阶特征交互。通过结合 CIN 和传统的深度神经网络&#xff08;D…...

Mybatis 中 <mappers> 标签四种配置方式

在MyBatis中,我们可以通过四种不同的方式来配置Mappers标签 : 1. 使用 <package name=""> 批量扫描包 这种方式通过指定一个包名,MyBatis 会自动扫描该包下的所有接口并注册为映射器。 <mappers><package name="com.example.mapper"/&…...

科技赋能记忆共生-郑州

故事背景 故事发生在中国河南郑州的现代城市环境中&#xff0c;这里描绘了人与科技的交融与共生。多样的场景展示了人与自然、历史与未来的互动&#xff0c;通过各种科技手段与古老文化相结合&#xff0c;展现出未来城市的独特魅力。 故事内容 在中国河南郑州&#xff0c;一座科…...

根据开始日期和结束日志统计共有多少天和每天的营业额

controller 重点&#xff1a;根据时间格式接受时间类型参数 DateTimeFormat(pattern "yyyy-MM-dd") LocalDateTime begin, DateTimeFormat(pattern "yyyy-MM-dd") LocalDateTime end) RestController RequestMapping("/admin/report") Slf4…...

LLMs之Agent之A2A:A2A的简介、安装和使用方法、案例应用之详细攻略

LLMs之Agent之A2A&#xff1a;A2A的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之Agent之A2A&#xff1a;《Announcing the Agent2Agent Protocol (A2A)》翻译与解读 LLMs之Agent之A2A&#xff1a;A2A的简介、安装和使用方法、案例应用之详细攻略 A2A协议…...

深入学习OpenCV:第一章简介

本专栏为零基础开发者打造&#xff0c;聚焦OpenCV在Python中的高效应用&#xff0c;用100%代码实践带你玩转图像处理&#xff01; 从 环境配置到实战项目&#xff0c;内容涵盖&#xff1a; 1️⃣ 基础篇&#xff1a;图像读写、阈值处理、色彩空间转换 2️⃣ 进阶篇&#xff…...

汉诺塔问题——用贪心算法解决

目录 一&#xff1a;起源 二&#xff1a;问题描述 三&#xff1a;规律 三&#xff1a;解决方案 递归算法 四&#xff1a;代码实现 复杂度分析 一&#xff1a;起源 汉诺塔&#xff08;Tower of Hanoi&#xff09;问题起源于一个印度的古老传说。在世界中心贝拿勒斯&#…...

【双指针】专题:LeetCode 283题解——移动零

移动零 一、题目链接二、题目三、题目解析四、算法原理两个指针的作用以及三个区间总结 五、与快速排序的联系六、编写代码七、时间复杂度、空间复杂度 一、题目链接 移动零 二、题目 三、题目解析 “保持非零元素的相对顺序”&#xff0c;比如&#xff0c;示例1中非零元素1…...

2025-4-12-C++ 学习 XOR 三元组 异或 急转弯问题

C的学习必须更加精进一些&#xff0c;对于好多的函数和库的了解必须深入一些。 文章目录 3513. 不同 XOR 三元组的数目 I题解代码 3514. 不同 XOR 三元组的数目 II题解代码 晚上&#xff0c;10点半&#xff0c;参加了LC的竞赛&#xff0c;ok了一道&#xff0c;哈哈~   第二道…...

[MySQL] 索引

索引 1.为什么有索引&#xff1f;2.MySQL的存储&#xff08;MySQL与磁盘交互的基本单位&#xff09;3.小总结4.索引的进一步理解4.1测试案例4.2 理解单个page4.3 理解多个page页目录单页情况多页情况 4.4 B树 VS B树4.5 聚簇索引 VS 非聚簇索引1.非聚簇索引2.聚簇索引 5.索引操…...

软考高级--案例分析

架构风格 重点 交互方式数据结构控制结构扩展方法 分类 管道-过滤器风格 数据流 数据仓储风格 星型结构以数据为中心&#xff0c;其他构件围绕数据进行交互 企业服务总线esb 定义 以一个服务总线充当中间件的角色&#xff0c;把各方服务对接起来&#xff0c;所有服务…...

Go - 内存逃逸

概念 每个函数都有自己的内存区域来存放自己的局部变量、返回地址等&#xff0c;这个内存区域在栈中进行分配。当函数结束时&#xff0c;这段内存区域会进行释放。 但有些变量&#xff0c;我们想在函数结束后仍然使用它&#xff0c;那么就要把这个变量在堆上分配&#xff0c;这…...

【数字电路】第四章 组合逻辑电路

一、组合逻辑电路的概述 1.逻辑电路的分类 2.逻辑功能的描述 二、组合逻辑电路的分析方法 根据输出可以粗略判断输入的数值的大小。 三、组合逻辑电路的基本设计方法 1.进行逻辑抽象 2.写出逻辑函数式 3.逻辑函数的化简或变换 4.画出逻辑电路图 5.设计验证与工艺设计 转换为…...

提权实战!

就是提升权限&#xff0c;当我们拿到一个shell权限较低&#xff0c;当满足MySQL提权的要求时&#xff0c;就可以进行这个提权。 MySQL数据库提权&#xff08;Privilege Escalation&#xff09;是指攻击者通过技术手段&#xff0c;从低权限的数据库用户提升到更高权限&#xff…...

单双线程的理解 和 lua基础语法

1.什么是单进程 &#xff0c;什么是多进程 当一个程序开始运行时&#xff0c;它就是一个进程&#xff0c;进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由单个或多个线程所组成的。 1.1 像apache nginx 这类 服务器中间件就是多进程的软件 &#xff0…...

深度学习(对抗)

数据预处理&#xff1a;像素标记与归一化 在 GAN 里&#xff0c;图像的确会被分解成一个个像素点来处理。在你的代码里&#xff0c;transform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) 这部分对图像进行了预处理&#xff1a; tra…...

【NLP】 18. Tokenlisation 分词 BPE, WordPiece, Unigram/SentencePiece

1. 翻译系统性能评价方法 在机器翻译系统性能评估中&#xff0c;通常既有人工评价也有自动评价方法&#xff1a; 1.1 人工评价 人工评价主要关注以下几点&#xff1a; 流利度&#xff08;Fluency&#xff09;&#xff1a; 判断翻译结果是否符合目标语言的语法和习惯。充分性…...

详解MYSQL表空间

目录 表空间文件 表空间文件结构 行格式 Compact 行格式 变长字段列表 NULL值列表 记录头信息 列数据 溢出页 数据页 当我们使用MYSQL存储数据时&#xff0c;数据是如何被组织起来的&#xff1f;索引又是如何组织的&#xff1f;在本文我们将会解答这些问题。 表空间文…...

lwip移植基于freertos(w5500以太网芯片)

目录 一、背景二、lwip移植基于w5500&#xff08;MACPHY&#xff0c;数据链路层和物理层&#xff09;1.移植需要的相关文件2、协议栈层级调用3、w5500关键初始化说明 三、附录 一、背景 1.OSI七层模型 图片来自网络 lwip协议栈工作在应用层、传输层、网络层&#xff1b; 网卡…...

【TI MSPM0】IQMath库学习

一、与DSP库的区别 二、IQMath库详解 RTS是靠纯软件实现的&#xff0c;而MathACL是靠硬件加速&#xff0c;速度更快 三、工程详解 1.导入工程 2.样例详解 使用一系列的运算来展示IQMath库&#xff0c;使用的是MathACL实现版本的IQMath库 编译加载运行&#xff0c;结果变量叫…...

51单片机 光敏电阻5506与ADC0832驱动程序

电路图 5506光敏电阻光强增加电阻值减小 以上电路实测无光时电压1.5v 有光且较亮时电压2.7v。 转换程序和ADC0832程序如下 // ADC0832引脚定义 sbit ADC_CS P1^2; // 片选信号 sbit ADC_CLK P1^0; // 时钟信号 sbit ADC_DIO P1^1; // 数据线// 获取电压值 - 返回c…...

【Linux】进程创建、进程终止、进程等待

Linux 1.进程创建1.fork 函数2.写时拷贝3.为什么要有写时拷贝&#xff1f; 2.进程终止1.进程退出场景2.退出码3.进程常见退出方法1.main函数return2.exit库函数3._exit系统调用 3.进程等待1.概念2.必要性3.方法1.wait2.waitpid3.参数status4.参数option5.非阻塞轮询 1.进程创建…...

ReliefF 的原理

&#x1f31f; ReliefF 是什么&#xff1f; ReliefF 是一种“基于邻居差异”的特征选择方法&#xff0c;用来评估每个特征对分类任务的贡献大小。 它的核心问题是&#xff1a; “我怎么知道某个特征是不是重要&#xff1f;是不是有能力把不同类别的数据区分开&#xff1f;” 而…...

C++ 数据结构之图:从理论到实践

一、图的基本概念 1.1 图的定义与组成 图&#xff08;Graph&#xff09;由顶点&#xff08;Vertex&#xff09;和边&#xff08;Edge&#xff09;组成&#xff0c;形式化定义为&#xff1a; G (V, E) 顶点集合 V&#xff1a;表示实体&#xff08;如城市、用户&#xff09; …...

机器学习(5)——支持向量机

1. 支持向量机&#xff08;SVM&#xff09;是什么&#xff1f; 支持向量机&#xff08;SVM&#xff0c;Support Vector Machine&#xff09;是一种监督学习算法&#xff0c;广泛应用于分类和回归问题&#xff0c;尤其适用于高维数据的分类。其核心思想是寻找最优分类超平面&am…...

C++学习之使用OPENSSL加解密

目录 1.知识点概述 2.哈希的特点和常用哈希算法散列值长度 3.Linux下openss相关的安装问题 4.md5 api 5.其他哈希算法使用 6.sha1测试 7.哈希值的封装 8.非对称加密特点和应用场景 9.生成密钥对-rsa 10.在内存中生成rsa密钥对-代码 11.将密钥对写入磁盘 12.使用bio方…...

markdown导出PDF,PDF生成目录

1、vscode中安装markdown插件&#xff0c;将编辑的文件导出PDF。 2、安装PDF Guru Anki软件 百度网盘&#xff1a;通过网盘分享的文件&#xff1a;PDFGuruAnki 链接: https://pan.baidu.com/s/1nU6avM7NUowhEn1FNZQKkA 提取码: aues PDF中不同的标题需要通过矩形框标注差异&a…...

Node.js中Stream模块详解

Node.js 中 Stream 模块全部 API 详解 一、Stream 基础概念 const { Stream } require(stream);// 1. Stream 类型 // - Readable: 可读流 // - Writable: 可写流 // - Duplex: 双工流 // - Transform: 转换流// 2. Stream 事件 // - data: 数据可读时触发 // - end: 数据读…...

Swift的学习笔记(一)

Swift的学习笔记&#xff08;一&#xff09; 文章目录 Swift的学习笔记&#xff08;一&#xff09;元组基本语法1. **创建元组**2. **访问元组的值**3. **命名的元组**4. **解构元组**5. **忽略某些值** 可选值类型定义 OptionalOptional 的基本使用1. **给 Optional 赋值和取值…...

3.4 函数单调性与曲线的凹凸性

1.函数单调性的定义 1.1.判别法 2.函数凹凸性 2.1 判别法...

随机森林优化 —— 理论、案例与交互式 GUI 实现

目录 随机森林优化 —— 理论、案例与交互式 GUI 实现一、引言二、随机森林基本原理与超参数介绍2.1 随机森林概述2.2 随机森林中的关键超参数 三、随机森林优化的必要性与挑战3.1 优化的重要性3.2 调优方法的挑战 四、常见的随机森林优化策略4.1 网格搜索&#xff08;Grid Sea…...

Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(一)

Pytorch深度学习框架60天进阶学习计划 - 第41天&#xff1a;生成对抗网络进阶&#xff08;一&#xff09; 今天我们将深入探讨生成对抗网络(GAN)的进阶内容&#xff0c;特别是Wasserstein GAN&#xff08;WGAN&#xff09;的梯度惩罚机制&#xff0c;以及条件生成与无监督生成…...

62. 不同路径

前言 本篇文章来自leedcode&#xff0c;是博主的学习算法的笔记心得。 如果觉得对你有帮助&#xff0c;可以点点关注&#xff0c;点点赞&#xff0c;谢谢你&#xff01; 题目链接 62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 题目描述 思路 1.如果m1或者n1就只…...

使用Apache POI实现Java操作Office文件:从Excel、Word到PPT模板写入

在企业级开发中&#xff0c;自动化处理Office文件&#xff08;如Excel报表生成、Word文档模板填充、PPT批量制作&#xff09;是常见需求。Apache POI作为Java领域最成熟的Office文件操作库&#xff0c;提供了一套完整的解决方案。本文将通过实战代码&#xff0c;详细讲解如何使…...

基于 RabbitMQ 优先级队列的订阅推送服务详细设计方案

基于 RabbitMQ 优先级队列的订阅推送服务详细设计方案 一、架构设计 分层架构: 订阅管理层(Spring Boot)消息分发层(RabbitMQ Cluster)推送执行层(Spring Cloud Stream)数据存储层(Redis + MySQL)核心组件: +-------------------+ +-------------------+ …...

设计模式(8)——SOLID原则之依赖倒置原则

设计模式&#xff08;7&#xff09;——SOLID原则之依赖倒置原则 概念使用示例 概念 高层次的类不应该依赖于低层次的类。两者都应该依赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。 底层次类&#xff1a;实现基础操作的类&#xff08;如磁盘操作…...

oracle COUNT(1) 和 COUNT(*)

在 Oracle 数据库中&#xff0c;COUNT(1) 和 COUNT(*) 都用于统计表中的行数&#xff0c;但它们的语义和性能表现存在一些细微区别。 1. 语义区别 COUNT(*) 统计表中所有行的数量&#xff0c;包括所有列值为 NULL 的行。它直接针对表的行进行计数&#xff0c;不关心具体列的值…...

理想汽车MindVLA自动驾驶架构核心技术梳理

理想汽车于2025年3月发布的MindVLA自动驾驶架构&#xff0c;通过整合视觉、语言与行为智能&#xff0c;重新定义了自动驾驶系统的技术范式。以下是其核心技术实现的详细梳理&#xff1a; 一、架构设计&#xff1a;三位一体的智能融合 VLA统一模型架构 MindVLA并非简单的端到端模…...

基于FPGA的智能垃圾桶设计-超声波测距模块-人体感应模块-舵机模块 仿真通过

基于FPGA的智能垃圾桶设计 前言一、整体方案二、仿真波形总结 前言 在FPGA开发平台中搭建完整的硬件控制系统&#xff0c;集成超声波测距模块、人体感应电路、舵机驱动模块及报警单元。在感知层配置阶段&#xff0c;优化超声波回波信号调理电路与人体感应防误触逻辑&#xff0…...