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

分布式锁实现方案-基于Redis实现的分布式锁

目录

一、基于Lua+看门狗实现

1.1 缓存实体

1.2 延迟队列存储实体

1.3 分布式锁RedisDistributedLockWithDog

1.4 看门狗线程续期

1.5 测试类

1.6 测试结果

1.7 总结

二、RedLock分布式锁

2.1 Redlock分布式锁简介

2.2 RedLock测试例子

2.3 RedLock 加锁核心源码分析

2.4 RedLock的问题与解决方案

2.4.1 问题

2.4.1.1 时钟偏移

2.4.1.2 单点故障

2.4.1.3 性能瓶颈

2.4.2 解决方案

2.4.2.1 引入重试机制

2.4.2.2 时钟校准

2.4.2.3 引入冗余节点

2.5 总结


一、基于Lua+看门狗实现

1.1 缓存实体

package com.ningzhaosheng.distributelock.redis;/*** @author ningzhaosheng* @date 2024/4/18 15:37:16* @description 类说明:Redis的key-value结构*/
public class LockItem {private final String key;private final String value;public LockItem(String key, String value) {this.key = key;this.value = value;}public String getKey() {return key;}public String getValue() {return value;}
}

1.2 延迟队列存储实体

package com.ningzhaosheng.distributelock.redis;import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;/*** @author ningzhaosheng* @date 2024/4/18 15:35:43* @description 说明:存放到延迟队列的元素,比标准的delay的实现要提前一点时间*/
public class ItemVo<T> implements Delayed {/*到期时刻  20:00:35,234*/private long activeTime;/*业务数据,泛型*/private T data;/*传入的数值代表过期的时长,单位毫秒,需要乘1000转换为毫秒和到期时间* 同时提前100毫秒续期,具体的时间可以自己决定*/public ItemVo(long expirationTime, T data) {super();this.activeTime = expirationTime + System.currentTimeMillis() - 100;this.data = data;}public long getActiveTime() {return activeTime;}public T getData() {return data;}/*** 返回元素到激活时刻的剩余时长*/public long getDelay(TimeUnit unit) {long d = unit.convert(this.activeTime- System.currentTimeMillis(), unit);return d;}/*** 按剩余时长排序*/public int compareTo(Delayed o) {long d = (getDelay(TimeUnit.MILLISECONDS)- o.getDelay(TimeUnit.MILLISECONDS));if (d == 0) {return 0;} else {if (d < 0) {return -1;} else {return 1;}}}
}

1.3 分布式锁RedisDistributedLockWithDog

package com.ningzhaosheng.distributelock.redis;import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** @author ningzhaosheng* @date 2024/4/18 15:38:07* @description Redis分布式锁,附带看门狗线程的实现:加锁,保持锁1秒*/
public class RedisDistributedLockWithDog implements Lock {private final static int LOCK_TIME = 1 * 1000;private final static String RS_DISTLOCK_NS = "tdln2:";/*if redis.call('get',KEYS[1])==ARGV[1] thenreturn redis.call('del', KEYS[1])else return 0 end*/private final static String RELEASE_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +"        return redis.call('del', KEYS[1])\n" +"    else return 0 end";/*还有并发问题,考虑ThreadLocal*/private ThreadLocal<String> lockerId = new ThreadLocal<>();private Thread ownerThread;private String lockName = "lock";private Jedis jedis = null;// 看门狗线程private Thread expireThread;private WatchDogThead watchDogThead;public String getLockName() {return lockName;}public void setLockName(String lockName) {this.lockName = lockName;}public Thread getOwnerThread() {return ownerThread;}public void setOwnerThread(Thread ownerThread) {this.ownerThread = ownerThread;}public RedisDistributedLockWithDog(Jedis jedis) {this.jedis = jedis;watchDogThead = new WatchDogThead(jedis);closeExpireThread();}@Overridepublic void lock() {while (!tryLock()) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void lockInterruptibly() throws InterruptedException {throw new UnsupportedOperationException("不支持可中断获取锁!");}@Overridepublic boolean tryLock() {Thread t = Thread.currentThread();/*说明本线程正在持有锁*/if (ownerThread == t) {return true;} else if (ownerThread != null) {/*说明本进程中有别的线程正在持有分布式锁*/return false;}try {/*每一个锁的持有人都分配一个唯一的id,也可采用snowflake算法*/String id = UUID.randomUUID().toString();SetParams params = new SetParams();params.px(LOCK_TIME); //加锁时间1sparams.nx();synchronized (this) {if ((ownerThread == null) &&"OK".equals(jedis.set(RS_DISTLOCK_NS + lockName, id, params))) {lockerId.set(id);setOwnerThread(t);if (expireThread == null) {//看门狗线程启动expireThread = new Thread(watchDogThead, "expireThread");expireThread.setDaemon(true);expireThread.start();}//往延迟阻塞队列中加入元素(让看门口可以在过期之前一点点的时间去做锁的续期)watchDogThead.addDelayDog(new ItemVo<>((int) LOCK_TIME, new LockItem(lockName, id)));System.out.println(Thread.currentThread().getName() + "已获得锁----");return true;} else {System.out.println(Thread.currentThread().getName() + "无法获得锁----");return false;}}} catch (Exception e) {throw new RuntimeException("分布式锁尝试加锁失败!", e);}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new UnsupportedOperationException("不支持等待尝试获取锁!");}@Overridepublic void unlock() {Thread t = Thread.currentThread();if (ownerThread != t) {throw new RuntimeException("试图释放无所有权的锁!");}try {Long result = (Long) jedis.eval(RELEASE_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS + lockName),Arrays.asList(lockerId.get()));System.out.println(result);if (result.longValue() != 0L) {System.out.println(t.getName()+" Redis上的锁已释放!");} else {System.out.println(t.getName()+" Redis上的锁释放失败!");}} catch (Exception e) {throw new RuntimeException("释放锁失败!", e);} finally {lockerId.remove();setOwnerThread(null);// 关闭看门狗closeExpireThread();// 关闭jedis连接jedis.close();}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("不支持等待通知操作!");}/*** 中断看门狗线程*/public void closeExpireThread() {if (null != expireThread) {expireThread.interrupt();}}
}

1.4 看门狗线程续期

package com.ningzhaosheng.distributelock.redis;import redis.clients.jedis.Jedis;import java.util.Arrays;
import java.util.concurrent.DelayQueue;/*** @author ningzhaosheng* @date 2024/4/18 16:18:10* @description 看门狗线程*/
public class WatchDogThead implements Runnable {private final static int LOCK_TIME = 1 * 1000;private final static String LOCK_TIME_STR = String.valueOf(LOCK_TIME);private final static String RS_DISTLOCK_NS = "tdln2:";/*看门狗线程*/private Thread expireThread;//通过delayDog 避免无谓的轮询,减少看门狗线程的轮序次数   阻塞延迟队列   刷1  没有刷2private static DelayQueue<ItemVo<LockItem>> delayDog = new DelayQueue<>();//续锁逻辑:判断是持有锁的线程才能续锁private final static String DELAY_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +"        return redis.call('pexpire', KEYS[1],ARGV[2])\n" +"    else return 0 end";// 客户端Jedis jedis = null;public WatchDogThead(Jedis jedis) {this.jedis = jedis;}public void addDelayDog(ItemVo<LockItem> itemItemVo) {delayDog.add(itemItemVo);}@Overridepublic void run() {System.out.println("看门狗线程已启动......");while (!Thread.currentThread().isInterrupted()) {try {LockItem lockItem = delayDog.take().getData();//只有元素快到期了才能take到  0.9stry {Long result = (Long) jedis.eval(DELAY_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS + lockItem.getKey()),Arrays.asList(lockItem.getValue(), LOCK_TIME_STR));if (result.longValue() == 0L) {System.out.println("Redis上的锁已释放,无需续期!");} else {delayDog.add(new ItemVo<>((int) LOCK_TIME,new LockItem(lockItem.getKey(), lockItem.getValue())));System.out.println("Redis上的锁已续期:" + LOCK_TIME);}} catch (Exception e) {throw new RuntimeException("锁续期失败!", e);}} catch (InterruptedException e) {System.out.println("看门狗线程被中断");break;}}System.out.println("看门狗线程准备关闭......");}}

1.5 测试类

package com.ningzhaosheng.distributelock.redis;import com.ningzhaosheng.distributelock.zookeeper.OrderServiceHandle;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;/*** @author ningzhaosheng* @date 2024/4/19 8:07:38* @description redis看门狗分布式锁测试类*/
public class TestRedisDistributedLockWithDog {private static final String REDIS_ADDRESS1 = "192.168.31.167:26379";private static final String REDIS_ADDRESS2 = "192.168.31.215:26379";private static final String REDIS_ADDRESS3 = "192.168.31.154:26379";public static void main(String[] args) {Set<String> sentinels = new HashSet<String>();sentinels.add(REDIS_ADDRESS1);sentinels.add(REDIS_ADDRESS2);sentinels.add(REDIS_ADDRESS3);// Redis主服务器的名字String masterName = "mymaster";// Redis服务器的密码String password = "xiaoning";try {int NUM = 10;CountDownLatch cdl = new CountDownLatch(NUM);for (int i = 1; i <= NUM; i++) {// 创建JedisSentinelPoolJedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels, password);// 从池中获取Jedis实例Jedis jedis  = pool.getResource();// 按照线程数迭代实例化线程Lock lock = new RedisDistributedLockWithDog(jedis);new Thread(new OrderServiceHandle(cdl, lock)).start();// 创建一个线程,倒计数器减1cdl.countDown();pool.close();}} catch (Exception e) {e.printStackTrace();}}
}

1.6 测试结果

1.7 总结

以上基于看门狗机制实现的Redis锁在对高并发要求不高的场景下可以使用,但是其实它还是有问题的,在主从架构模式下,可能会导致两个线程同时获取到锁得问题出现:

  •  线程A从主redis中请求一个分布式锁,获取锁成功;
  • 从redis准备从主redis同步锁相关信息时,主redis突然发生宕机,锁丢失了;
  • 触发从redis升级为新的主redis;线程B从继任主redis的从redis上申请一个分布式锁,此时也能获取锁成功;
  • 导致,同一个分布式锁,被两个客户端同时获取,没有保证独占使用特性;

当对高可用有要求的场景,这种方式就不合适了,那么在高并发、高可靠场景下,我们该如何基于Redis 实现分布式锁呢?得接着往下看!

二、RedLock分布式锁

2.1 Redlock分布式锁简介

Redlock是由Redis的作者Salvatore Sanfilippo提出的一种分布式锁算法,它利用Redis的特性来实现分布式锁。Redlock算法的核心思想是通过在多个Redis实例上创建相同的分布式锁,以保证锁的可靠性。Redlock算法通过在Redis中设置一个唯一的键值对来表示锁的存在,其他线程或进程需要通过竞争来获取这个锁。

官网wiki:8. 分布式锁和同步器 · redisson/redisson Wiki · GitHub

2.2 RedLock测试例子

package com.ningzhaosheng.distributelock.redis.redisson;import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** @author ningzhaosheng* @date 2024/6/22 15:57:17* @description*/
public class TestRedLockByRedisson {public static void main(String[] args) {String RS_DISTLOCK_NS = "tdln2:";String lockName = "lock";Config config = new Config();config.useSentinelServers().addSentinelAddress("redis://192.168.31.215:26379","redis://192.168.31.167:26379","redis://192.168.31.154:26379").setMasterName("mymaster").setPassword("xiaoning");RedissonClient redisson = Redisson.create(config);try {for (int i = 1; i <= 10; i++) {// 生成8位随机数Random random = new Random();// 创建RedLockRLock rLock = redisson.getLock(RS_DISTLOCK_NS+lockName+ random.nextInt(99999999));RedissonRedLock rdLock = new RedissonRedLock(rLock);boolean islock = rdLock.tryLock(500,3000,TimeUnit.MILLISECONDS);if(islock){System.out.println("执行业务啦!======================");}else{System.out.println("获取锁失败!======================");}rdLock.unlock();}} catch (Exception e) {e.printStackTrace();}redisson.shutdown();}
}

测试结果:

2.3 RedLock 加锁核心源码分析

这里我就以tryLock()为例,分析下RedLock 的源码:

 public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long newLeaseTime = -1L;if (leaseTime != -1L) {if (waitTime == -1L) {newLeaseTime = unit.toMillis(leaseTime);} else {newLeaseTime = unit.toMillis(waitTime) * 2L;}}long time = System.currentTimeMillis();long remainTime = -1L;if (waitTime != -1L) {remainTime = unit.toMillis(waitTime);}long lockWaitTime = this.calcLockWaitTime(remainTime);// 允许加锁失败的节点数量(N-(N/2+1))int failedLocksLimit = this.failedLocksLimit();List<RLock> acquiredLocks = new ArrayList(this.locks.size());ListIterator iterator = this.locks.listIterator();// 遍历所有节点,通过EVAL命令执行lua脚本加锁while(iterator.hasNext()) {// 迭代获取Redis实例RLock lock = (RLock)iterator.next();boolean lockAcquired;// 尝试加锁,调用lock.tryLock()方法try {if (waitTime == -1L && leaseTime == -1L) {lockAcquired = lock.tryLock();} else {long awaitTime = Math.min(lockWaitTime, remainTime);// 尝试加锁lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);}} catch (RedisResponseTimeoutException var21) {// 如果抛出异常,为了防止加锁成功,但是响应失败,需要解锁所有节点,同时返回加锁失败状态this.unlockInner(Arrays.asList(lock));lockAcquired = false;} catch (Exception var22) {lockAcquired = false;}if (lockAcquired) {// 如果加锁成功,把锁加到以获得锁集合中acquiredLocks.add(lock);} else {//计算已经申请锁失败的节点是否已经到达允许加锁失败节点个数限制 (即:N-(N/2+1));如果已经到达,就认定最终申请锁失败,则没有必要继续从后面的节点申请了因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功if (this.locks.size() - acquiredLocks.size() == this.failedLocksLimit()) {break;}if (failedLocksLimit == 0) {this.unlockInner(acquiredLocks);if (waitTime == -1L) {return false;}failedLocksLimit = this.failedLocksLimit();acquiredLocks.clear();while(iterator.hasPrevious()) {iterator.previous();}} else {--failedLocksLimit;}}// 计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回falseif (remainTime != -1L) {remainTime -= System.currentTimeMillis() - time;time = System.currentTimeMillis();if (remainTime <= 0L) {this.unlockInner(acquiredLocks);return false;}}}// 重置锁过期时间if (leaseTime != -1L) {acquiredLocks.stream().map((l) -> {return (RedissonLock)l;}).map((l) -> {return l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);}).forEach((f) -> {f.syncUninterruptibly();});}// 如果逻辑正常执行完则认为最终申请锁成功,返回truereturn true;}

2.4 RedLock的问题与解决方案

2.4.1 问题

2.4.1.1 时钟偏移

Redlock算法中需要使用到各个Redis实例的系统时钟来实现锁的过期时间控制。然而,不同Redis实例之间的时钟可能存在偏移,导致锁的过期时间计算错误,从而导致锁的误释放或者死锁的发生。

2.4.1.2 单点故障

Redlock算法的可靠性依赖于多个Redis实例之间的协作。如果其中一个Redis实例发生故障,可能会导致整个系统的分布式锁服务不可用。

2.4.1.3 性能瓶颈

RedLock需要访问多个实例,网络延迟可能导致某些线程获取锁的时间较长,会增加网络带宽的压力。此外,每个实例都需要对锁进行检查和定时删除操作,也会影响Redis的性能。

2.4.2 解决方案

针对上述问题,我们可以采取以下解决方案来提高Redlock分布式锁在高并发环境下的性能和可靠性:

2.4.2.1 引入重试机制

为了应对网络延迟带来的竞争问题,我们可以在获取锁失败后进行重试。通过设定适当的重试次数和重试间隔,可以减少因网络延迟导致的锁竞争失败的情况。但是该方案还是解决不了性能瓶颈问题,性能瓶颈问题是架构方案决定的。

2.4.2.2 时钟校准

为了解决时钟偏移问题,我们可以通过定期校准各个Redis实例的系统时钟来保证它们之间的一致性。可以使用NTP协议或者其他时间同步机制来实现时钟校准。

2.4.2.3 引入冗余节点

为了避免单点故障带来的问题,我们可以引入冗余节点来提高系统的可用性。通过在系统中增加多个Redis实例作为备份节点,可以在主节点故障时快速切换到备份节点,保证分布式锁服务的可用性。

2.5 总结

基于Redis实现分布式锁的方案,他的实现方式有两种,一直是基于setnx指令+lua脚本+看门狗进程,实现在Redis单实例下的分布式锁方案,这种方式在主从模式下,当主节点宕机,主从切换的时候,会有获取锁失败的情况或者会有多个客户端同时获取到锁的情况,为了提高分布式锁的可用性,则出现了改进后的RedLock锁,RedLock锁是基于多实例加锁(即:N/2+1),同时给N/2+1个实例加锁成功,才算获取锁成功。但是他的问题也很明显,具体如前文所述。所以在进行技术方案选型的时候,你要结合你的场景,明确选用分布式锁时需要用途:

  1. 如果你只考虑提升加锁效率问题,那么选用一台高性能的服务器,部署单实例,采用基于setnx指令+lua脚本+看门狗进程的方案就够了。
  2. 如果你考虑高可用,想避免单点问题,并且你的部署架构是主从的,或者集群模式的,那么你可以选择使用RedLock,但是你就不得不考虑因此带来的复杂性和各种问题。
  3. 如果你要保证绝对的数据一致性,那么不好意思,Redis实现分布式锁方案可能不适合你,因为从架构的角度来说,Redis的架构属于AP架构,它保证的是可用性;这时,你可以考虑使用Zookeeper实现分布式锁方案,因为它的架构属于CP架构,保证的是一致性;但是Zookeeper也有问题,就是它的性能没有Redis高。

那么在并发量大,可靠性高的场景下,我们最终应该怎么技术选型呢,我的建议是避免使用分布式锁,可以通过串行化来解决,比如同步串行化,异步串行化等方案。这里暂时不对串行化设计展开论述,后续有时间我会在另外的主题文章进行分享。

好了,本次内容就分享到这,欢迎关注本博主。如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

相关文章:

分布式锁实现方案-基于Redis实现的分布式锁

目录 一、基于Lua看门狗实现 1.1 缓存实体 1.2 延迟队列存储实体 1.3 分布式锁RedisDistributedLockWithDog 1.4 看门狗线程续期 1.5 测试类 1.6 测试结果 1.7 总结 二、RedLock分布式锁 2.1 Redlock分布式锁简介 2.2 RedLock测试例子 2.3 RedLock 加锁核心源码分析…...

MTK7628+MT7612 加PA定频数据

1、硬件型号TR726A5G121-DPA PC9.02.0017。如下所示&#xff1a; 2、WIFI5.8 AC模式 42&#xff08;5120MHz&#xff09;信道&#xff0c;80带宽 3、WIFI5.8 AC模式 38&#xff08;5190MHz&#xff09;信道&#xff0c;40带宽 4、WIFI5.8 AC模式 36&#xff08;5180 MHz&…...

[信号与系统]关于双线性变换

前言 本文还是前置知识 双线性变换法 双线性变换法&#xff08;Bilinear Transform&#xff09;是一种用于将模拟滤波器转换为数字滤波器的方法。它通过将模拟域中的s平面上的传递函数映射到数字域中的z平面上的传递函数来实现这一转换。双线性变换法保证了频率响应在转换过…...

763. 划分字母区间

题目&#xff1a;给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍然是 s 。返回一个表示每个字符串片段的长度的列表…...

【PostgreSQL】AUTO_EXPLAIN - 慢速查询的日志执行计划

本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载。 一、介绍 在本文中&#xff0c;我们将了解 PostgreSQL AUTO_EXPLAIN功能的工作原理&#xff0c;以及为什么应该使用它来收集在生产系统…...

讯飞星火超自然语言合成的完整Demo

依赖文件和功能 requirements.txt 该文件列出了所需的依赖包。 data.py 定义了应用的配置信息&#xff0c;如APPId&#xff0c;APIKey&#xff0c;APISecret等。包含请求数据和请求URL。 main.py 主程序&#xff0c;设置了WebSocket连接&#xff0c;定义了处理消息的各个回调函…...

封装一个上拉加载的组件(无限滚动)

一、封装 1.这个是在vue3环境下的封装 2.整体思路&#xff1a; 2.1传入一个elRef&#xff0c;其实就是一个使用页面的ref。 2.2也可以不传elRef&#xff0c;则默认滚动的是window。 import { onMounted, onUnmounted, ref } from vue; import { throttle } from underscore;ex…...

WHAT - 高性能和内存安全的 Rust(二)

目录 1. 所有权&#xff08;Ownership&#xff09;2. 借用&#xff08;Borrowing&#xff09;不可变借用可变借用 3. 可变性&#xff08;Mutability&#xff09;4. 作用域&#xff08;Scope&#xff09;综合示例 了解 Rust 的所有权&#xff08;ownership&#xff09;、借用&am…...

办理河南建筑工程乙级设计资质的流程与要点

办理河南建筑工程乙级设计资质的流程与要点 办理河南建筑工程乙级设计资质的流程与要点主要包括以下几个方面&#xff1a; 流程&#xff1a; 工商注册与资质规划&#xff1a;确保企业具有独立法人资格&#xff0c;完成工商注册&#xff0c;并明确乙级设计资质的具体要求&…...

分类算法和回归算法区别

分类算法和回归算法在机器学习中扮演着不同的角色&#xff0c;它们的主要区别体现在输出类型、应用场景以及算法目标上。以下是对两者区别和使用场景的详细分析&#xff1a; 一、区别 1.输出类型&#xff1a; 分类算法&#xff1a;输出是离散的类别标签&#xff0c;通常表示为…...

利用Frp实现内网穿透(docker实现)

文章目录 1、WSL子系统配置2、腾讯云服务器安装frps2.1、创建配置文件2.2 、创建frps容器 3、WSL2子系统Centos服务器安装frpc服务3.1、安装docker3.2、创建配置文件3.3 、创建frpc容器 4、WSL2子系统Centos服务器安装nginx服务 环境配置&#xff1a;一台公网服务器&#xff08…...

怎么用Excel生成标签打印模板,自动生成二维码

环境&#xff1a; EXCEL2021 16.0 问题描述&#xff1a; 怎么用excel生成标签打印模板自动生成二维码 解决方案&#xff1a; 在Excel中生成标签打印模板并自动生成二维码&#xff0c;可以通过以下几个步骤完成&#xff1a; 1. 准备数据 首先&#xff0c;确保你的Excel表…...

java基于ssm+jsp 美食推荐管理系统

1前台首页功能模块 美食推荐管理系统&#xff0c;在系统首页可以查看首页、热门美食、美食教程、美食店铺、美食社区、美食资讯、我的、跳转到后台等内容&#xff0c;如图1所示。 图1前台首页功能界面图 用户注册&#xff0c;在注册页面可以填写用户名、密码、姓名、联系电话等…...

数据分析:置换检验Permutation Test

欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 介绍 置换检验是一种非参数统计方法&#xff0c;它不依赖于数据的分布形态&#xff0c;因此特别适…...

【React】使用Token做路由权限控制

在components/AuthRoute/index.js中 import { getToken } from /utils import { Navigate } from react-router-domconst AuthRoute ({ children }) > {const isToken getToken()if (isToken) {return <>{children}</>} else {return <Navigate to"/…...

机器学习周记(第四十四周:Robformer)2024.6.17~2024.6.23

目录 摘要ABSTRACT1 论文信息1.1 论文标题1.2 论文摘要1.3 论文引言1.4 论文贡献 2 论文模型2.1 问题描述2.2 Robformer2.2.1 Encoder2.2.2 Decoder 2.3 鲁棒序列分解模块2.4 季节性成分调整模块 摘要 本周阅读了一篇利用改进 Transformer 进行长时间序列预测的论文。论文模型…...

JAVA学习笔记DAY10——SpringBoot基础

文章目录 SpringBoot3 介绍SpringBoot 快速入门SpringBootApplication SpringBoot 配置文件统一配置管理Yaml 配置优势tips SpringBoot 整合 SpringMVC静态资源拦截器 interceptor SpringBoot 整合 DruidSpringBoot 整合 MybatisSpringBoot 整合 tx aopSpringBoot 打包 SpringB…...

如何在Android中实现多线程与线程池?

目录 一、Android介绍二、什么是多线程三、什么是线程池四、如何在Android中实现多线程与线程池 一、Android介绍 Android是一种基于Linux内核的开源操作系统&#xff0c;由Google公司领导开发。它最初于2007年发布&#xff0c;旨在为移动设备提供一种统一、可扩展的操作系统。…...

SCI绘图【1】-不同颜色表示密度和差异--密度图

参考资料&#xff1a;密度图&#xff08;Density Plot&#xff09; - 数据可视化图表 - 数字孪生百科 密度图是快速观察变量数值分布的有效方法之一。通常情况下&#xff0c;会根据两个变量将平面绘图区域分为非常多的子区域&#xff0c;之后以不同颜色表示落在该区域上样本的…...

C语言 while循环1

在C语言里有3种循环&#xff1a;while循环 do while 循环 for循环 while语句 //while语法结构 while&#xff08;表达式&#xff09;循环语句; 比如在屏幕上打印1-10 在while循环中 break用于永久的终止循环 在while循环中&#xff0c;continue的作用是跳过本次循环 …...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

MySQL 主从同步异常处理

阅读原文&#xff1a;https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主&#xff0c;遇到的这个错误&#xff1a; Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一&#xff0c;通常表示&#xff…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...