分布式锁的几种实现
前几天看一个面试视频,提到了分布式锁一直想写写,但奈何考试太多,直到今天才有时间。好啦,开始今天的文章吧。
一.定义
分布式锁:当多个进程不在同一个系统中(比如分布式系统中控制共享资源访问),用分布式锁控制多个进程对资源的访问。
二.实现
- 基于数据库实现分布式锁 主要时依赖数据库支持的锁实现的
全局锁,表锁(排他锁,共享锁) ,行锁(记录锁,间隙锁,临键锁),意向锁,悲观锁,乐观锁
基于数据库表 可以通过唯一索引实现,我给一个唯一索引的字段插入数据时,其他线程是无法给这个字段继续插入数据(唯一索引特性),从而保证安全与一直性。
同样也可以通过主键索引实现(唯一+非空约束) 同上插入数据时相当于加锁,删除数据时相当于释放锁。
乐观锁(基于版本号)
如果存在一条数据插入时,另一条数据也要操作,就会检查版本号有没有更新,更新就再次获取锁,没有就插入数据。(如下为转账操作)
CREATE TABLE account (id INT PRIMARY KEY,balance DECIMAL(10,2),version INT NOT NULL
);
public boolean transferWithOptimisticLock(int id, double amount) throws SQLException {Connection conn = dataSource.getConnection();conn.setAutoCommit(false);try {// 查询当前余额与版本号String selectSql = "SELECT balance, version FROM account WHERE id = ?";PreparedStatement selectStmt = conn.prepareStatement(selectSql);selectStmt.setInt(1, id);ResultSet rs = selectStmt.executeQuery();if (!rs.next()) return false;double currentBalance = rs.getDouble("balance");int currentVersion = rs.getInt("version");if (currentBalance < amount) {throw new RuntimeException("余额不足");}double newBalance = currentBalance - amount;int newVersion = currentVersion + 1;// 更新并检查版本号String updateSql = "UPDATE account SET balance = ?, version = ? WHERE id = ? AND version = ?";PreparedStatement updateStmt = conn.prepareStatement(updateSql);updateStmt.setDouble(1, newBalance);updateStmt.setInt(2, newVersion);updateStmt.setInt(3, id);updateStmt.setInt(4, currentVersion);int rowsAffected = updateStmt.executeUpdate();if (rowsAffected == 0) {// 版本不一致,说明其他人已修改System.out.println("乐观锁失败:数据已被修改,请重试");return false;}conn.commit();return true;} catch (SQLException e) {conn.rollback();throw e;} finally {conn.setAutoCommit(true);}
}
悲观锁(基于排它锁)
一条数据插入时就直接加锁,然后等到数据插入完成就释放锁,持有锁期间不允许又任何读写操作作用于该数据。
CREATE TABLE account (id INT PRIMARY KEY,balance DECIMAL(10,2)
);
public void transferWithPessimisticLock(int id, double amount) throws SQLException {Connection conn = dataSource.getConnection();conn.setAutoCommit(false);try {// 加锁查询(FOR UPDATE)String lockSql = "SELECT balance FROM account WHERE id = ? FOR UPDATE";PreparedStatement lockStmt = conn.prepareStatement(lockSql);lockStmt.setInt(1, id);ResultSet rs = lockStmt.executeQuery();if (!rs.next()) throw new RuntimeException("账户不存在");double currentBalance = rs.getDouble("balance");if (currentBalance < amount) {throw new RuntimeException("余额不足");}double newBalance = currentBalance - amount;// 执行更新String updateSql = "UPDATE account SET balance = ? WHERE id = ?";PreparedStatement updateStmt = conn.prepareStatement(updateSql);updateStmt.setDouble(1, newBalance);updateStmt.setInt(2, id);updateStmt.executeUpdate();conn.commit();} catch (SQLException e) {conn.rollback();throw e;} finally {conn.setAutoCommit(true);}
}
- 基于 redis 实现分布式锁:(常用)
单个Redis实例:setnx(key,当前时间+过期时间) + Lua脚本
这个太常见了,黑马外卖不就是通过Redis结合防重Token和Lua脚本来实现幂等性校验。lua脚本保证了读取用户token与删除token是一步完成的。
Maven导入:
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.0.1</version>
</dependency>
代码实现:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;import java.util.Collections;
import java.util.UUID;public class RedisDistributedLock {private final Jedis jedis;private final String lockKey;private final String requestId;private final int expireTime; // 毫秒public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {this.jedis = jedis;this.lockKey = lockKey;this.expireTime = expireTime;this.requestId = UUID.randomUUID().toString(); // 唯一标识}/*** 获取锁(带自动过期)*/public boolean acquire() {SetParams params = new SetParams();params.nx(); // 仅当 key 不存在时才设置params.px(expireTime); // 设置过期时间(毫秒)String result = jedis.set(lockKey, requestId, params);return "OK".equals(result);}/*** 释放锁(通过 Lua 脚本保证原子性)*/public boolean release() {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));return "1".equals(result.toString());}public static void main(String[] args) {try (Jedis jedis = new Jedis("localhost", 6379)) {RedisDistributedLock lock = new RedisDistributedLock(jedis, "my:lock:key", 30000);if (lock.acquire()) {System.out.println("【线程:" + Thread.currentThread().getName() + "】获取锁成功");try {// 模拟业务逻辑Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock.release()) {System.out.println("【线程:" + Thread.currentThread().getName() + "】释放锁成功");} else {System.out.println("【线程:" + Thread.currentThread().getName() + "】释放锁失败,可能已被他人释放");}}} else {System.out.println("【线程:" + Thread.currentThread().getName() + "】获取锁失败");}} catch (Exception e) {e.printStackTrace();}}
}
Redis集群模式:Redlock(红锁,存在争议)
原理:
- 使用 N 个独立的 Redis 节点 (推荐为奇数个,如 5 个)
- 每个节点都尝试获取相同的锁
- 客户端通过多数节点加锁成功来判断是否获得锁
4. 关键问题与争议
问题 | 描述 |
---|---|
时间依赖 | Redlock 假设时间是同步的,但现实中 NTP 或虚拟机暂停会导致时间跳跃 |
锁安全性 | Martin Kleppmann 指出,在某些异常场景下可能多个客户端同时持有锁 |
实用性 | Antirez 强调 Redlock 更适合工程实践而非理论完美性 |
Maven导入:
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.20.1</version>
</dependency>
代码实现(基于Redission):
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;import java.util.concurrent.TimeUnit;public class RedlockDemo {public static void main(String[] args) {// 配置多个 Redis 节点(模拟集群)Config config1 = new Config();config1.useSingleServer().setAddress("redis://127.0.0.1:6379");Config config2 = new Config();config2.useSingleServer().setAddress("redis://127.0.0.1:6380");Config config3 = new Config();config3.useSingleServer().setAddress("redis://127.0.0.1:6381");// 创建三个 Redisson 客户端实例RedissonClient redisson1 = Redisson.create(config1);RedissonClient redisson2 = Redisson.create(config2);RedissonClient redisson3 = Redisson.create(config3);// 获取每个节点上的锁对象RLock lock1 = redisson1.getLock("redlock:key");RLock lock2 = redisson2.getLock("redlock:key");RLock lock3 = redisson3.getLock("redlock:key");// 创建 Redlock 对象RLock redLock = redisson1.getRedLock(lock1, lock2, lock3);boolean isLocked = false;try {// 尝试加锁,等待最多 100 秒,上锁后 30 秒自动解锁isLocked = redLock.tryLock(100, 30, TimeUnit.SECONDS);if (isLocked) {System.out.println("【加锁成功】当前线程:" + Thread.currentThread().getName());// 执行业务逻辑Thread.sleep(5000);} else {System.out.println("【加锁失败】");}} catch (InterruptedException e) {e.printStackTrace();} finally {if (isLocked) {redLock.unlock(); // 释放锁System.out.println("【锁已释放】");}// 关闭客户端redisson1.shutdown();redisson2.shutdown();redisson3.shutdown();}}
}
- 基于 zookeeper实现分布式锁(zookeeper相对与redis而言舍弃了部分高性能而保证了强一致性)
临时有序节点来实现的分布式锁,Curator
核心思想:
- 每个客户端在 ZooKeeper 中尝试创建一个临时有序子节点 ,如:
/locks/my_lock/lock_0000000001
- 获取当前所有子节点并排序,判断自己是否是最小序号的节点:
- 如果是 → 成功获取锁;
- 如果不是 → 监听前一个节点(Watch),等待它被删除;
- 执行完业务逻辑后,删除自己的节点 → 释放锁;
- 利用 ZooKeeper 的 Watcher 机制自动通知下一个节点尝试加锁;
String ourPath = zk.createEphemeralSequential(root + "/lock_", data);
List<String> children = zk.getChildren(root);
Collections.sort(children); // 排序所有子节点
//伪代码
if (ourPath is the first in sorted list) {return true; // 成功获取锁
} else {String prevNode = findPrevNode(ourPath, children);watch(prevNode); // 监听前一个节点waitUntilPrevNodeDeleted(); // 等待删除后再次尝试
}
Maven导入:
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.7.0</version>
</dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.7.0</version>
</dependency>
代码实现:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ZookeeperDistributedLockExample {// ZooKeeper 地址(单机模式)private static final String ZK_ADDRESS = "localhost:2181";// 锁路径(可以理解为资源名)private static final String LOCK_PATH = "/distributed_lock";public static void main(String[] args) {// 创建 ZooKeeper 客户端CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_ADDRESS,new ExponentialBackoffRetry(1000, 3));client.start();// 创建分布式锁对象InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);// 模拟多个线程并发请求锁ExecutorService executor = Executors.newFixedThreadPool(5);for (int i = 0; i < 5; i++) {final int threadNum = i;executor.submit(() -> {try {System.out.println("线程 " + threadNum + " 正在尝试获取锁...");if (lock.acquire(10, TimeUnit.SECONDS)) { // 等待最多10秒try {System.out.println("线程 " + threadNum + " 成功获得锁");Thread.sleep(3000); // 模拟业务处理} finally {lock.release(); // 释放锁System.out.println("线程 " + threadNum + " 已释放锁");}} else {System.out.println("线程 " + threadNum + " 获取锁失败");}} catch (Exception e) {e.printStackTrace();}});}executor.shutdown();}
}
- 基于 Consul 实现分布式锁
核心思想:
- 每个客户端尝试在 Consul KV 中创建一个键,并带上当前会话(Session);
- Consul 使用
acquire
原子操作保证只有第一个成功设置该 key 的客户端才能获得锁; - 如果 key 已存在且关联了一个未过期的 session,则其他客户端无法获取锁;
- 当持有锁的客户端释放锁或 session 失效时,key 被清除,其他客户端可以重新竞争;
注:以下代码需要实现启动 Consul Agent
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;public class ConsulDistributedLock {private static final String CONSUL_URL = "http://localhost:8500/v1/kv/";private final String lockKey; // 锁路径,如 "/locks/my_resource"private final String sessionId; // 当前会话 IDprivate final String serviceName; // 客户端标识public ConsulDistributedLock(String lockKey, String serviceName) throws Exception {this.lockKey = lockKey;this.serviceName = serviceName;this.sessionId = createSession();}/*** 创建一个 Session*/private String createSession() throws Exception {String sessionJson = String.format("{\"Name\":\"%s\",\"TTL\":\"15s\"}", serviceName);String response = sendPost("http://localhost:8500/v1/session/create", sessionJson);return response.split("\"")[3]; // 简单提取 session ID}/*** 尝试获取锁*/public boolean acquire() throws IOException {String urlStr = CONSUL_URL + lockKey + "?acquire=" + sessionId;String payload = "locked-by:" + serviceName;String result = sendPut(urlStr, payload);return Boolean.parseBoolean(result);}/*** 释放锁*/public boolean release() throws IOException {String urlStr = CONSUL_URL + lockKey + "?release=" + sessionId;String payload = "locked-by:" + serviceName;String result = sendPut(urlStr, payload);return Boolean.parseBoolean(result);}/*** 心跳保持 Session 活跃*/public void renewSession() throws IOException {String urlStr = "http://localhost:8500/v1/session/renew/" + sessionId;sendPut(urlStr, "");}/*** 发送 PUT 请求*/private String sendPut(String urlStr, String body) throws IOException {return sendRequest(urlStr, "PUT", body);}/*** 发送 POST 请求*/private String sendPost(String urlStr, String body) throws IOException {return sendRequest(urlStr, "POST", body);}/*** 发送 HTTP 请求通用方法*/private String sendRequest(String urlStr, String method, String body) throws IOException {URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod(method);conn.setDoOutput(true);try (OutputStream os = conn.getOutputStream()) {byte[] input = body.getBytes(StandardCharsets.UTF_8);os.write(input, 0, input.length);}StringBuilder response = new StringBuilder();try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {String responseLine;while ((responseLine = br.readLine()) != null) {response.append(responseLine.trim());}}return response.toString();}public static void main(String[] args) throws Exception {ConsulDistributedLock lock = new ConsulDistributedLock("/locks/my_lock", "service-A");System.out.println("尝试获取锁...");if (lock.acquire()) {try {System.out.println("【线程:" + Thread.currentThread().getName() + "】获取锁成功");// 模拟业务逻辑执行for (int i = 0; i < 10; i++) {lock.renewSession(); // 保持心跳Thread.sleep(5000);}} finally {lock.release();System.out.println("锁已释放");}} else {System.out.println("获取锁失败");}}
}
感谢你看到这里,喜欢的可以点点关注哦。
相关文章:
分布式锁的几种实现
前几天看一个面试视频,提到了分布式锁一直想写写,但奈何考试太多,直到今天才有时间。好啦,开始今天的文章吧。 一.定义 分布式锁:当多个进程不在同一个系统中(比如分布式系统中控制共享资源访问),用分布式…...
Android 解绑服务问题:java.lang.IllegalArgumentException: Service not registered
问题与处理策略 问题描述 在 Android 项目中,解绑(unbindService())一个服务(Service)时,报如下错误 java.lang.IllegalArgumentException: Service not registered问题原因 错误表明在解绑服务时&…...
注册登录页面项目
关系型数据库地址:C:\Users\ASUS\AppData\Local\Temp\HuaweiDevEcoStudioDatabases\rdb #注册页面register.ets import dataRdb from ohos.data.rdbconst STORE_CONFIG {name: weather4.db } const TABLE_NAME weather_info const SQL_CREATE_TABLE CREATE TAB…...
从 Python 基础到 Django 实战 —— 数据类型驱动的 Web 开发之旅
主题简介: 本主题以 Python 基础数据类型为核心,结合 Django 框架的开发流程,系统讲解如何通过掌握数字、字符串、列表、元组、字典等基础类型,快速构建功能完善的 Web 应用。通过理论与实践结合,帮助学员从零基础 Py…...
数字智慧方案5971丨智慧农业大数据平台解决方案(59页PPT)(文末有下载方式)
详细资料请看本解读文章的最后内容。 资料解读:智慧农业大数据平台解决方案 在现代农业发展进程中,智慧农业大数据平台解决方案正成为推动农业变革的关键力量。这一方案从项目简介到大数据展示,各个环节紧密相连,致力于为农业发展…...
MOOS-ivp使用(一)——水下机器人系统的入门与使用
MOOS-ivp使用(一)——水下机器人系统的入门与使用 MOOS-ivp(Marine Operational Oceanographic System for Intelligent Vehicle Planning)是专为水下机器人(如AUV)设计的开源框架。类似于ROS,…...
【网络服务器】——回声服务器(echo)
作用 实现回声服务器的客户端/服务器程序,客户端通过网络连接到服务器,并发送任意一串英文信息,服务器端接收信息后,执行数据处理函数:将每个字符转换为大写并回送给客户端显示。 客户端:发送字符信息 服…...
IDEA在项目中添加模块出现Error adding module to project: null(向项目添加模块时出错: null)的解决方法
解决方法 (1)打开当前项目的结构...
(34)VTK C++开发示例 ---将图片映射到平面
文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容👉内容导航 👈👉VTK开发 👈 1. 概述 演示如何将图片作为纹理贴图到一个平面上。 这段代码的功能是使用 VTK(Visualization Toolkit࿰…...
微软与Meta大幅增加人工智能基础设施投入
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
华为云服务器VoceChat在线聊天室部署
目录 1. 项目介绍2. 准备条件3. Docker环境部署3.1 安装Docker(CentOS 7)3.2 安装Docker Compose3.3 Docker常用命令 4. 创建配置文件4.1 创建工作目录4.2 创建docker-compose.yml文件4.3 保存配置文件 5. 部署运行5.1 启动服务5.2 检查服务状态5.3 防火…...
ERP系统(技术面)知识积累
本文为本人在准备某公司信息技术类岗位的面试时所作的笔记,该公司有技术面,此岗位入职后负责的是ERP系统的运行和维护,所以可能会问ERP系统相关的问题。故我写此文以做准备。 ERP简介 ERP,全称Enterprise Resource Planning&…...
Python学习笔记(第三部分)
接续 Python.md 文件的第三部分 类 类的创建的基本使用 创建一个类 class Dog(): 文档字符串:这是一次模拟小狗的简单尝试 def __init__(self,name,age):self.name nameself.age agedef sit(self):print(self.name.title() " is now sitting.")def ro…...
【浅尝Java】Java简介第一个Java程序(含JDK、JRE与JVM关系、javcdoc的使用)
🍞自我激励:每天努力一点点,技术变化看得见 文章目录 Java语言概述Java是什么Java语言的重要性Java语言发展简史Java语言特性 第一个Java程序main方法示例运行Java程序JDK、JRE、JVM之间的关系注释基本规则注释规范 标识符关键字 Java语言概述…...
【FreeRTOS-列表和列表项】
参照正点原子以及以下gitee笔记整理本博客,并将实验结果附在文末。 https://gitee.com/xrbin/FreeRTOS_learning/tree/master 一、列表和列表项的简介(熟悉) 1、什么是列表 答:列表是FreeRTOS中的一个数据结构,概念上和链表有点类似&#…...
22.2Linux的I2C驱动实验(编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 这里我们用到的是stm32mp157的板子,所以我们看一下I2C用到的引脚。 1、硬件原理图分析 可以看到在这块板子上面用的SDA和SCL总线是PA11,PA12。所以要修改设备树和镜像文件&…...
socket-IO复用技术
五个I/O模型 1、阻塞I/O 2、非阻塞I/O 3、I/O复用(select和poll) 4、信号驱动I/O 5、异步I/O I/O复用 是一种在单线程或单进程环境下,同时监听多个 I/O 事件的技术。它允许程序高效地处理多个输入输出流(如网络套接字、文件描…...
上位机知识篇---二进制操作
文章目录 前言接收数据示例:0xAA 0x12 0x34 0x55合并高/低字节数据RGB565颜色值:0xF800(红色)Python中负数右移接收帧:01 03 02 12 34 CRC前言 本文简单对单片机、上位机中的映射(Mapping)和位移操作符(Bit Shifting)等相关知识进行了简单介绍. 一、单片机与上位机中…...
openEuler 22.03 安装 Mysql 5.7,TAR离线安装
目录 一、检查系统是否安装其他版本Mariadb数据库二、环境检查2.1 必要环境检查2.2 在线安装(有网络)2.3 离线安装(无网络) 二、下载Mysql2.1 在线下载2.2 离线下载 三、安装Mysql四、配置Mysql五、开放防火墙端口六、数据备份七、…...
《排序算法总结》
引言: 编程学到现在,我们已经接触了很多种排序算法,这篇文章我就对常见的几种排序算法进行一个小结。 一: 排序算法分类: 二: 插入排序: 直接插入排序: 1. 概念: 直…...
【Java学习笔记】递归
递归(recursion) 思想:把一个复杂的问题拆分成一个简单问题和子问题,子问题又是更小规模的复杂问题,循环往复 本质:栈的使用 递归的注意事项 (1)需要有递归出口,否者就…...
体系学习1:C语言与指针1——预定义、进制打印、传参为数组
1、不对一段代码进行编译 #if 0 statement #endif2、输出地址 int d[3]{1,2,3}; printf("%p",(void*)d);//p期待的是void*类型的数据3、不同进制的打印 int data 1200; char hed[9];//为\0预留位置!!! sprintf(hed,"%08X&…...
使用Java正则表达式进行分组与匹配文本提取
在Java开发中,正则表达式(Regex)是处理字符串的强大工具,广泛应用于数据验证、文本解析和格式转换等场景。通过正则表达式的分组功能,开发者可以精确地提取匹配模式的子部分,而不仅仅是整个匹配内容。Java的…...
RAGFlow上传3M是excel表格到知识库,提示上传的文件总大小过大
环境: Ragflowv0.17.2 问题描述: RAGFlow上传3M是excel表格到知识库,提示上传的文件总大小过大 解决方案: 定位问题: 1.查询Nginx 日志 Nginx 日志 检查 Nginx 配置中日志路径是否正确,确保日志文件有…...
2025年4月文章一览
2025年4月编程人总共更新了30篇文章: 1.2025年3月文章一览 2.《Operating System Concepts》阅读笔记:p528-p544 3.《Operating System Concepts》阅读笔记:p545-p551 4.《Operating System Concepts》阅读笔记:p552-p579 5.…...
2025大模型微调视频课程全套(附下载)
2025大模型微调视频课程全套,共10课。主要内容如下: 1、大模型的发展 2、Transformer & LLMs 3、大模型微调预览&Lora微调&Alpaca模型微调 4、Alpaca&AdaLoRA&QLoRA模型微调 5、Efficient Fine-tuning&Efficient Inference&…...
【Python Web开发】04-Cookie和Session
文章目录 1. Cookie1.1 定义1.2 工作原理1.3 用途1.4 优缺点 2. Session2.1 定义2.2 工作原理2.3 用途2.4 优缺点 3. Cookie 与 Session 的关系4. 安全性考量5. Python 中使用 Cookie 和 Session 在 HTTP 协议里,Cookie 和 Session 是用于管理客户端与服务器之间会话…...
从股指到期指,哪些因素影响基差?
当我们谈论股指期货(简称“期指”)与股票现货指数(简称“股指”)的基差时,其实是在探讨期货价格与现货价格之间的“差价”。这个差价受多种因素影响,时而扩大,时而缩小,甚至可能“翻…...
n8n 中文系列教程_15. 【工具篇】n8n中文版与汉化指南:从原理到实践
n8n 作为一款强大的开源自动化工具,目前尚未推出官方中文版,但社区提供了汉化方案。不过,对于技术用户,我们更推荐使用英文原版,以便更好地查阅文档和解决问题。如果你仍希望尝试汉化,本文将详细介绍如何通…...
3D版同步帧游戏
以下是实现一个3D版同步帧游戏的详细步骤与完整代码示例。我们将以第一人称射击游戏(FPS)为原型,重点讲解3D空间中的同步机制优化。 项目升级:3D版核心改动 1. 3D坐标系与消息结构 // common/messages.go type Vector3 struct {X float32 `json:"x"`Y float32 `…...
C语言中数字转化为字符串的方法
C语言中数字转化为字符串的方法 1. 使用 sprintf 函数 这是 stdio.h 头文件中的标准库函数 ,功能类似于 printf ,但不是输出到控制台,而是将格式化后的内容输出到字符数组(字符串)中。 示例代码: c #inc…...
使用MGeo模型高精度实现文本中地址识别
一、功能与安装 1、模型地址 模型是阿里开发的门址高精度识别模型。 https://modelscope.cn/models/iic/mgeo_geographic_elements_tagging_chinese_base/summary 注意:不能自己安装包,没法解决依赖问题,直接按照官方要求安装下面的包&am…...
OpenGL-ES 学习(15) ----纹理
目录 纹理简介纹理映射纹理映射流程示例代码:纹理的环绕和过滤方式纹理的过滤方式 纹理简介 现实生活中,纹理(Texture) 类似于游戏中皮肤的概念,最通常的作用是装饰 3D 物体,它像贴纸一样贴在物体的表面,丰富物体的表…...
类成员函数编译链接的过程
1.静态成员函数和普通成员函数 源文件编译成目标文件,静态成员函数和普通成员函数在目标文件代码段,函数添加进了符号表,地址是在代码段的相对地址,这个地址只是一个临时地址因为后面链接时还要合并代码段,函数地址还…...
PostgreSQL:pgAdmin 4 使用教程
pgAdmin 4 是一个用于管理和维护 PostgreSQL 数据库的强大工具。它提供了一个图形化界面,使用户能够轻松地连接到数据库、创建表、运行 SQL 语句以及执行其他数据库管理任务。 安装和使用 安装 pgAdmin 4 安装 pgAdmin 4 非常简单。下载并运行安装程序࿰…...
*(解引用运算符)与 ++(自增运算符)的优先级
在 C 和 C 等编程语言里,*(解引用运算符)与 (自增运算符)的执行优先级高低,要依据 是前缀形式还是后缀形式来确定。下面为你详细分析: 1. 后缀 运算符 后缀 运算符的优先级比 *(…...
二叉搜索树中的搜索(递归解决)
700. 二叉搜索树中的搜索 - 力扣(LeetCode) 二叉搜索树(BST):以任意节点为根节点的数值大于其左子树所有节点的值,小于右子树所有节点的值。 查找二叉搜索树中的值,要利用节点之间的大小关系。…...
idea安装
1.卸载 2.安装 3.ssh...
在ASP.NET MVC中使用Repeater指南
虽然ASP.NET MVC框架本身不包含Web Forms中的Repeater控件,但您可以通过几种方式实现类似的功能。以下是几种在MVC中实现Repeater效果的方法: 1. 使用foreach循环 最简单的方法是直接在视图中使用Razor的foreach循环: csharp model IEnumer…...
【C语言常用字符串解析】
总结一下在 C 语言中用于字符串解析(特别是从文件中读取行并提取数据)的常用函数、 核心任务: 通常是从文件中读取一行文本(一个字符串),然后从这个字符串中提取出需要的数据(比如数字、单词等…...
基于深度学习农作物叶部病害实时检测系统研究(源码+定制+开发)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
『MCP』初体验
『MCP』初体验 介绍 MCP 其实就是 Function Calling 的一个统一接口协议,网上介绍会有很多,所以这里不就重复介绍,这里主要是想记录说明一下 MCP 使用体验,可以帮助新人入门一下 安装 VSCode 以及 MCP client VSCode 自行安装…...
前端面试宝典---webpack原理解析,并有简化版源码
前言 先看一下webpack打包后的bundle.js,前边的直接扫一眼就过,可以发现这个立即执行函数的形参就是一个,key为引入文件路径,value为该模块代码的函数。 所以比较重要的就是通过webpack的配置文件中的entry的入口文件,…...
负载均衡深度实践:基于Nginx+Keepalived的高可用方案与Zabbix监控设计
目录 综合实践-部署负载均衡 1 环境准备 2 zabbix监控nginx和keeplive 2.1 nginx安装 2.2 安装keepalived 2.3 部署vue 2.4 安装agent 2.5 zabbix监控nginx配置 2.6 zabbix监控keeplived 3 zabbix监控jar 3.1 安装agent 3.2 安装jdk 3.3 部署jar包 3.4 配置web 4…...
深度学习基础--目标检测入门简介
博主简介:努力学习的22级本科生一枚 🌟 博客主页:羊小猪~~-CSDN博客 内容简介:探索AI算法,C,go语言的世界;在迷茫中寻找光芒🌸 往期回顾:yolov5基础–一步一步教…...
Redis ⑧-RESP | 渐进式遍历 | 数据库管理
Redis data-types 除了之前学习的 string、hash、list、set、Zset 五种数据结构之外,Redis 还提供了 bitmap、bitfield、 hyperloglog、geospatial、stream 等数据结构。 另外的一些数据结构,都是在某些特定环境下才会使用,使用频率不高&…...
【Android】四大组件之ContentProvider
目录 一、什么是 ContentProvider 二、创建和使用 ContentProvider 三、跨应用权限控制 四、数据变更通知 五、多表关联与视图 六、异步处理 你手机里的通讯录,存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App,就可以通过ContentP…...
Qwen3 发布:优化编码与代理能力,强化 MCP 支持引领 AI 新潮流
人工智能领域的每一次重大突破都如同璀璨星辰,照亮了人类前行的道路。2025 年 4 月 29 日凌晨,阿里巴巴旗下的 Qwen 官方团队正式发布了最新一代大语言模型 ——Qwen3,犹如一颗重磅炸弹,在 AI 领域掀起了惊涛骇浪。此次发布&#…...
LEETERS题解
【题目描述】 给出一个rowcolrowcol的大写字母矩阵,一开始的位置为左上角,你可以向上下左右四个方向移动,并且不能移向曾经经过的字母。问最多可以经过几个字母。 【输入】 第一行,输入字母矩阵行数RR和列数SS,1≤R,S≤…...
图像加密算法概述
版本: 1.0 日期: 2025年5月1日 目录 引言 1.1 什么是图像加密?1.2 为什么需要图像加密?1.3 图像数据的特点与加密挑战加密基础概念 2.1 明文与密文2.2 加密与解密2.3 密钥2.4 对称加密与非对称加密为什么传统文本加密算法不完全适用于图像? 3.1 数据量巨大3.2 高度冗余性…...