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

Redisson 分布式锁实战应用解析

文章目录

  • 前言
  • 一、Redisson介绍
  • 二、Redisson的使用
    • 1.1 引入依赖
    • 1.2 编写配置
    • 1.3 示例测试_01
    • 1.4 示例测试_02
  • 三、Redisson源码分析
    • 2.1 加锁源码
    • 2.2 看门狗机制


前言

分布式锁主要是解决分布式系统下数据一致性的问题。在单机的环境下,应用是在同一进程下的,只需要保证单进程多线程环境中的线程安全性,通过 Java 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。

一、Redisson介绍

Redisson 是一个基于 Redis 的分布式 Java 客户端。它提供了丰富的功能和工具,帮助开发者在分布式系统中解决数据共享、并发控制和任务调度等问题。通过使用Redisson,开发者可以轻松地操作 Redis 的分布式对象(如集合、映射、队列等),实现可靠的分布式锁机制,以及管理和调度分布式环境中的任务和服务。

Redisson 的分布式锁的特点

● 线程安全:分布式锁可以确保在多线程和多进程环境下的数据一致性和可靠性。
● 可重入性: 同一个线程可以多次获取同一个锁,避免死锁的问题。
● 锁超时: 支持设置锁的有效期,防止锁被长时间占用而导致系统出现问题。
● 阻塞式获取锁: 当某个线程尝试获取锁时,如果锁已经被其他线程占用,则该线程可以选择等待直到锁释放。
● 无阻塞式获取锁: 当某个线程尝试获取锁时,如果锁已经被其他线程占用,则该线程不会等待,而是立即返回获取锁失败的信息。

redisson 实现分布式官网文档:https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers

二、Redisson的使用

Redisson 支持单点模式、主从模式、哨兵模式、集群模式,本文以单点模式为例说明。

1.1 引入依赖

<!-- redisson -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version>
</dependency>
<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.1.21.RELEASE</version>
</dependency>

1.2 编写配置

spring:redis:host: 192.168.57.129port: 6379
@Configuration
public class RedissonConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;/*** 对所有redisson的使用都是通过redissonClient对象* @return*/@Bean(destroyMethod = "shutdown")public RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://" + host + ":" + port);return Redisson.create(config);}
}

1.3 示例测试_01

@Autowired
private RedissonClient redissonClient;// redisson分布式锁的key
private static final String LOCK_TEST_KEY = "redisson_lock";// redisson分布式锁的key
private static int TICKET_NUMBER = 10;/*** 分布式锁测试用例* 模拟开启11个用户去抢车票*/
@Test
public void lockTest() {// 利用循环+多线程 模仿高并发请求for (int i = 0; i < 11; i++) {CompletableFuture.runAsync(() -> {if (TICKET_NUMBER > 0) {// 这里获取公平锁,遵循先进先出原则,方便测试RLock fairLock = redissonClient.getFairLock(LOCK_TEST_KEY);try {// 尝试加锁// waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败// leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)boolean lock = fairLock.tryLock(3000, 30, TimeUnit.MILLISECONDS);if (lock){log.info("线程:" + Thread.currentThread().getName() + "获得了锁");log.info("车票剩余数量:{}", --TICKET_NUMBER);}} catch (InterruptedException e) {e.printStackTrace();} finally {log.info("线程:" + Thread.currentThread().getName() + "准备释放锁");//注意: 无论出现任何情况都要主动解锁fairLock.unlock();}}else {log.info("车票已售罄!");}});try {// ->_-> 这里使当前方法占用的线程休息10秒,不要立即结束Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}}
}

日志信息输出

2023-11-18 15:27:00.834  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:00.835  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:9
2023-11-18 15:27:00.835  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:03.749  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:03.749  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:8
2023-11-18 15:27:03.749  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:06.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:06.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:7
2023-11-18 15:27:06.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:09.749  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:09.750  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:6
2023-11-18 15:27:09.750  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:12.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:12.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:5
2023-11-18 15:27:12.759  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:15.752  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:15.752  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:4
2023-11-18 15:27:15.752  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:18.762  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:18.762  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:3
2023-11-18 15:27:18.762  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:21.754  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:21.754  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:2
2023-11-18 15:27:21.754  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:24.763  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:24.763  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:1
2023-11-18 15:27:24.763  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:27.757  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6获得了锁
2023-11-18 15:27:27.757  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票剩余数量:0
2023-11-18 15:27:27.757  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 线程:ForkJoinPool.commonPool-worker-6准备释放锁
2023-11-18 15:27:30.753  INFO 5800 --- [onPool-worker-6] c.a.g.p.GulimallProductApplicationTests  : 车票已售罄!

1.4 示例测试_02

@ResponseBody
@GetMapping("/hello")
public String hello(){//1、获取一把锁,只要锁的名字一样,就是同一把锁String lockKey = "my-lock";RLock lock = redissonClient.getLock(lockKey);//2、加锁阻塞式等待,默认加的锁都是30s。lock.lock();//10秒自动解锁,自动解锁时间一定要大于业务的执行时间。问题:在锁时间到了以后,不会自动续期。//lock.lock(10, TimeUnit.SECONDS);//最佳实战:省掉了整个续期操作。手动解锁//1)、lock.lock(30, TimeUnit.SECONDS);try {log.info("加锁成功,执行业务ing, 线程ID = {}", Thread.currentThread().getId());Thread.sleep(10000);}catch (Exception e){e.printStackTrace();}finally {//3、解锁 假设解锁代码没有运行,redisson会不会出现死锁log.info("释放锁, 线程ID = {}", Thread.currentThread().getId());lock.unlock();}return "hello";
}

浏览器执行两个 hello 请求,只有当第一个请求业务执行完,第二个才能正常执行,不然第二个处于阻塞式等待状态。

在这里插入图片描述

控制台打印日志信息

2023-11-18 16:01:00.784  INFO 3916 --- [io-10000-exec-4] c.a.g.product.web.IndexController        : 加锁成功,执行业务ing, 线程ID = 116
2023-11-18 16:01:10.785  INFO 3916 --- [io-10000-exec-4] c.a.g.product.web.IndexController        : 释放锁, 线程ID = 116
2023-11-18 16:01:10.794  INFO 3916 --- [io-10000-exec-2] c.a.g.product.web.IndexController        : 加锁成功,执行业务ing, 线程ID = 114
2023-11-18 16:01:20.794  INFO 3916 --- [io-10000-exec-2] c.a.g.product.web.IndexController        : 释放锁, 线程ID = 114

redisson 实现分布式锁解决了 redis 实现分布式锁的两个问题

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

三、Redisson源码分析

redisson 这个框架的实现依赖了 Lua 脚本和 Netty,以及各种 Future 及FutureListener 的异步、同步操作转换,加锁和解锁过程中还巧妙地利用了 redis 的发布订阅功能。

2.1 加锁源码

无参加锁方法

@Override
public void lock() {try {lock(-1, null, false);} catch (InterruptedException e) {throw new IllegalStateException();}
}

有参加锁方法

@Override
public void lock(long leaseTime, TimeUnit unit) {try {lock(leaseTime, unit, false);} catch (InterruptedException e) {throw new IllegalStateException();}
}

2.2 看门狗机制

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {long threadId = Thread.currentThread().getId();//尝试获取锁Long ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {return;}RFuture<RedissonLockEntry> future = subscribe(threadId);if (interruptibly) {commandExecutor.syncSubscriptionInterrupted(future);} else {commandExecutor.syncSubscription(future);}try {while (true) {ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {break;}// waiting for messageif (ttl >= 0) {try {future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {if (interruptibly) {throw e;}future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);}} else {if (interruptibly) {future.getNow().getLatch().acquire();} else {future.getNow().getLatch().acquireUninterruptibly();}}}} finally {unsubscribe(future, threadId);}
}
//尝试获取锁
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {return get(tryAcquireAsync(leaseTime, unit, threadId));
}

异步的方式尝试获取锁

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {if (leaseTime != -1) {//如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间。return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);}//如果我们未指定锁的超时时间,就使用30*1000【LockWatchdogTimeout看门狗的默认时间】RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);//占锁成功ttlRemainingFuture.onComplete((ttlRemaining, e) -> {//发生异常直接返回,若无异常执行下面逻辑if (e != null) {return;}// lock acquiredif (ttlRemaining == null) {scheduleExpirationRenewal(threadId);}});return ttlRemainingFuture;
}
//默认自动续期时间30s,看门狗时间
private long lockWatchdogTimeout = 30 * 1000;
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {internalLockLeaseTime = unit.toMillis(leaseTime);return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"return redis.call('pttl', KEYS[1]);",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

重新设置超时时间

private void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);renewExpiration();}
}

开启定时任务,发送 LUA 脚本,锁的超时时间达到1/3就重新设为30s

private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;}Long threadId = ent.getFirstThreadId();if (threadId == null) {return;}RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {// reschedule itselfrenewExpiration();}});}//只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动再次续期,续成30s internalLockLeaseTime【看门狗时间】 / 3,10s}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);
}

重新设置超时时间 LUA 脚本

protected RFuture<Boolean> renewExpirationAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0;",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

相关文章:

Redisson 分布式锁实战应用解析

文章目录 前言一、Redisson介绍二、Redisson的使用1.1 引入依赖1.2 编写配置1.3 示例测试_011.4 示例测试_02 三、Redisson源码分析2.1 加锁源码2.2 看门狗机制 前言 分布式锁主要是解决分布式系统下数据一致性的问题。在单机的环境下&#xff0c;应用是在同一进程下的&#x…...

【机器学习】对比学习(contrastive learning)

对比学习是一种机器学习技术&#xff0c;算法学习区分相似和不相似的数据点。对比学习的目标是学习数据的表示&#xff0c;以捕捉不同数据点之间的基本结构和关系。 在对比学习中&#xff0c;算法被训练最大化相似数据点之间的相似度&#xff0c;并最小化不相似数据点之间的相似…...

开源和闭源的优劣势比较

开源与闭源软件之争一直是技术领域一个备受关注的话题&#xff0c;而在近期特斯拉CEO马斯克的表态中&#xff0c;关于开源的讨论更是引发了广泛的关注。以下是一些关于开源和闭源的优劣势以及对未来大模型发展的一些见解&#xff1a; 开源软件的优势&#xff1a; 创新与合作&a…...

html手势密码解锁插件(附源码)

文章目录 1.设计来源1.1 界面效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134534785 html手势密码解锁插件(附源码)&#xff0c;仿手机手势密码&#xff0c;拖动九…...

Jetson JetPack-5.1.2-L4T-R35.4.1 修复deskew algorithm的问题

1. 前言 官方Orin Nano开发套件 版本: JetPack 5.1.2 2. 问题描述 使用的是带有Orin Nano EVK的GMSL相机,但我无法看到MIPI帧。 这里是媒体设备信息: Media controller API version 5.10.120Media device information ------------------------ driver tegra-…...

Doris的分区表和分桶表

1 列定义 以AGGREGATE KEY数据模型为例进行说明。更多数据模型参阅Doris数据模型。 列的基本类型,可以通过在mysql-client中执行HELP CREATE TABLE; 查看。 AGGREGATE KEY数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为Key列。而其余则为Value列。 定义…...

华为HCIE技术(HCIP、HCIE)汇总

我所在的岗位是网络运维&#xff0c;路由与交换用的比较多&#xff0c;外网技术比较少。所以把经常用的写的比较详细。大家多多包涵。 理论部分 网络基础&#xff1a;ISO网络七层模型 交换机部分 首次Console口登陆配置Telnet访问 VLAN VLAN的介绍、单交换机vlan划分实验…...

React结合antd5实现整个表格编辑

通过react hooks 结合antd的table实现整个表格新增编辑。 引入组件依赖 import React, { useState } from react; import { Table, InputNumber, Button, Space, Input } from antd;定义数据 const originData [{ key: 1, name: 白银会员, value: 0, equity: 0, reward: 0…...

【C++进阶之路】第八篇:智能指针

文章目录 一、为什么需要智能指针&#xff1f;二、内存泄漏1.什么是内存泄漏&#xff0c;内存泄漏的危害2.内存泄漏分类&#xff08;了解&#xff09;3.如何检测内存泄漏&#xff08;了解&#xff09;4.如何避免内存泄漏 三、智能指针的使用及原理1.RAII2.智能指针的原理3.std:…...

保护服务器免受攻击:解析攻击情境与解决之道

在数字化时代&#xff0c;服务器安全问题日益突出&#xff0c;因为它们是企业和个人网络活动的核心。服务器被攻击可能引发一系列问题&#xff0c;理解攻击的不同情境以及采取相应的解决方法变得至关重要。 DDoS 攻击&#xff08;分布式拒绝服务攻击&#xff09; 情境&#xff…...

Python 获取两个数组中各个坐标点对之间最短的欧氏距离

Python 获取两个数组中各个坐标点对之间最短的欧氏距离 代码如下: import numpy as np from scipy import spatialxy1 = np.array([[243, 3173],[525, 2997]]...

假ArrayList导致的线上事故......

假ArrayList导致的线上事故… 线上事故回顾 晚饭时&#xff0c;当我正沉迷于排骨煲肉质鲜嫩&#xff0c;汤汁浓郁时&#xff0c;产研沟通群内发出一条消息&#xff0c;显示用户存在可用劵&#xff0c;但进去劵列表却什么也没有&#xff0c;并附含了一个视频。于是我一边吃了排…...

K8S精进之路-控制器Deployment-(1)

在K8S中&#xff0c;最小运行单位为POD,它是一个逻辑概念&#xff0c;其实是一组共享了某些资源的容器组。POD是能运行多个容器的&#xff0c;Pod 里的所有容器&#xff0c;共享的是同一个 Network Namespace&#xff0c;并且可以声明共享同一个 Volume。在POD中能够hold住网络…...

flutter iOS 视频mov格式转MP4格式

flutter iOS 视频mov格式转MP4格式 前言一、使用video_compress压缩视频总结 前言 今天在写项目的时候&#xff0c;突然发现iOS 里面的有些视频格式是mov的格式&#xff0c;这就导致在视频播放组件无法播放的问题&#xff0c;期间试过替换视频格式&#xff0c;但是又不想存储文…...

datafilecopy header validation failure for file +DATA/orcl/datafile/file1.ora

环境 linux 6.8 oracle 11.2.0.4 rac 未打补丁 现象&#xff1a; 数据库日志中显示 datafilecopy header validation failure for file DATA/orcl/datafile/file1.ora原因 用rman的backup as copy 对数据文件进行了copy或者叫位置更改时候&#xff0c;在sqlplus中对文件名进行…...

[机缘参悟-119] :一个IT人的反思:反者道之动;弱者,道之用 VS 恒者恒强,弱者恒弱的马太效应

目录 前言&#xff1a; 一、道家的核心思想 二、恒者恒强&#xff0c;弱者恒弱的马太效应 三、马太效应与道家思想的统一 3.1 大多数的理解 3.2 个人的理解 四、矛盾的对立统一 前言&#xff1a; 马太效应和强弱互转的道家思想&#xff0c;都反应了自然规律和社会规律&…...

CentOS用nginx搭建文件下载服务器

Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器&#xff0c;而且支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&#xff0c;即使运行几个月也不需要重新启动。在工作中&#xff0c;我们经常会用到需要搭建文件服务器的情况&#xff0c;这里就以在linux下搭…...

Ubuntu下载离线安装包

旧版Ubuntu下载地址 https://old-releases.ubuntu.com/releases/ 下载离线包 sudo apt-get --download-only -odir::cache/ncayu install net-tools下载snmp离线安装包 sudo apt-get --download-only -odir::cache/root/snmp install snmp snmpd snmp-mibs-downloadersudo a…...

【UE】线框材质

一、方式1 新建一个材质&#xff0c;混合模式设置为“已遮罩”&#xff0c;勾选“双面” 勾选“线框” 然后可以随便给一个自发光颜色&#xff0c;这样最基本的线框材质就完成了 二、方式2 新建一个材质&#xff0c;混合模式设置为“已遮罩”&#xff0c;勾选“双面”&#x…...

物联网网关在工业行业的应用与优势

物联网网关在工业行业的应用与优势 随着科技的发展&#xff0c;物联网&#xff08;IoT&#xff09;在各个行业得到了广泛应用。工业行业是其中的一个重要领域。工业行业涉及到许多生产和运输环节&#xff0c;需要不断地监测和控制各个环节的数据和参数。物联网网关&#xff08…...

为Claude Code配置Taotoken密钥与聚合地址避免封号困扰

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为Claude Code配置Taotoken密钥与聚合地址避免封号困扰 如果你正在使用Claude Code作为编程助手&#xff0c;可能会遇到官方渠道的…...

如何快速集成Deep Learning with Python到Web应用:Flask与FastAPI完整指南

如何快速集成Deep Learning with Python到Web应用&#xff1a;Flask与FastAPI完整指南 【免费下载链接】deep-learning-with-python-notebooks Jupyter notebooks for the code samples of the book "Deep Learning with Python" 项目地址: https://gitcode.com/gh…...

中文商业报告Markdown模板:提升效率与专业度的结构化写作框架

1. 项目概述&#xff1a;一份开箱即用的中文商业报告模板最近在整理团队季度复盘材料&#xff0c;发现一个挺普遍的问题&#xff1a;大家花在数据整理和格式调整上的时间&#xff0c;远比分析业务本身要多。一份商业报告&#xff0c;从数据清洗、图表制作到排版成文&#xff0c…...

别再被量纲搞晕了!Fluent表达式入门保姆级教程(附3个避坑案例)

别再被量纲搞晕了&#xff01;Fluent表达式入门保姆级教程&#xff08;附3个避坑案例&#xff09; 刚接触Fluent表达式的工程师们&#xff0c;十有八九会在量纲问题上栽跟头。明明公式逻辑正确&#xff0c;却频频遭遇报错&#xff1b;看似简单的三角函数输入&#xff0c;却因为…...

从啤酒酿造到FPGA设计:工程师的极致工艺与分层迭代思维

1. 项目概述&#xff1a;从一杯“战术核企鹅”啤酒聊起的工程师日常前几天收到一封朋友发来的、语焉不详的邮件&#xff0c;告诉我有一只“企鹅”正从海外运来&#xff0c;即将抵达我的“欢乐宫”——也就是我的办公室。这让我有点慌&#xff0c;毕竟自打“那件事”之后&#x…...

3分钟快速激活Windows和Office:KMS_VL_ALL_AIO终极完整指南

3分钟快速激活Windows和Office&#xff1a;KMS_VL_ALL_AIO终极完整指南 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统激活提示而烦恼&#xff1f;Office突然变成只读模式让你…...

5分钟掌握英雄联盟个性化美化:R3nzSkin国服换肤完全指南

5分钟掌握英雄联盟个性化美化&#xff1a;R3nzSkin国服换肤完全指南 【免费下载链接】R3nzSkin-For-China-Server Skin changer for League of Legends (LOL) 项目地址: https://gitcode.com/gh_mirrors/r3/R3nzSkin-For-China-Server 还在为英雄联盟国服的昂贵皮肤而犹…...

RoboMaster备赛避坑指南:为什么你的裁判系统服务器总连不上?从网络到MySQL的常见故障排查

RoboMaster裁判系统服务器连接故障全解析&#xff1a;从网络配置到MySQL优化的实战排错手册 当比赛倒计时的滴答声越来越清晰&#xff0c;裁判系统服务器却突然"罢工"——这可能是每个RoboMaster参赛队伍最不愿面对的噩梦。不同于常规的搭建教程&#xff0c;本文将直…...

mysql如何实现读写分离的权限分配_不同用户分别赋予权限

读用户仅授SELECT权限&#xff0c;写用户授SELECT/INSERT/UPDATE/DELETE&#xff0c;禁用系统库权限&#xff0c;REPLICATION SLAVE仅用于从库同步&#xff0c;权限变更需重连生效&#xff0c;跨库查询须显式授权。只给读用户 SELECT 权限&#xff0c;别碰 INSERT/UPDATE/DELET…...

从独立芯片到CPU集成:Thunderbolt 3如何通过技术重构实现普及

1. 从独立控制器到片上集成&#xff1a;Thunderbolt 3的破局之路在2017年&#xff0c;当我在评测一款高端笔记本时&#xff0c;发现它侧面的那个USB-C接口旁边印着一个闪电标志。我心想&#xff0c;这玩意儿不就是个快充口吗&#xff1f;直到我试着用它连接了一块高速固态硬盘&…...