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

Java基于数据库的分布式可重入锁(带等待时间和过期时间)

文章目录

  • 技术背景介绍
  • 代码实现
    • 数据库表结构
    • 尝试获取锁
    • 续约
    • 阻塞式获取锁
    • 解锁
    • 检查锁是否过期或者释放
  • 使用示例
  • 优化方案

项目代码

技术背景介绍

一般分布式锁使用最方便的就是使用redis实现,因为他自带超时过期机制、发布订阅模式、高吞吐高性能的优势,但是有些项目里只有mysql数据库,很多数据库都是没有数据超时过期机制和发布订阅模式的,当然也不是所有的,这里我只针对mysql数据库作为基础组件。

代码实现

数据库表结构

DROP TABLE IF EXISTS `distributed_lock`;
CREATE TABLE `distributed_lock` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',`lock_name` varchar(255) NOT NULL COMMENT '锁名',`machine_id` varchar(255) DEFAULT NULL COMMENT '服务器id',`expire_time` datetime DEFAULT NULL COMMENT '过期时间,服务里会有一个看门狗续期,如果过期了就说明服务挂了,解锁会设置为空',`is_locked` tinyint(4) NOT NULL DEFAULT '0' COMMENT '当前是否锁定状态',`state` int(11) NOT NULL DEFAULT '0' COMMENT '锁标记位 类似次数',`thread_id` varchar(255) DEFAULT NULL COMMENT '当前获得锁的线程id',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',`is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `idx_lock_name` (`lock_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

尝试获取锁

使用乐观锁模式更新锁记录。如果获取失败,则加入订阅列表中,等待被唤醒或者到达超时时间自动唤醒,待获取到锁后再从订阅列表中移除。他的具体等待时间取决于用户输入的等待时间和锁超时过期的时间,这里使用JUC的Semaphore来实现等待功能。

public boolean tryLock(String lockName, Long waitTime, Long leaseTime, TimeUnit timeUnit) {long startTime = System.currentTimeMillis();String threadId = getCurrentThreadId();Long ttl = tryAcquire(lockName, leaseTime, timeUnit);// lock acquiredif (ttl == null) {return true;}long time = timeUnit.toMillis(waitTime);if (waitTime != -1 && System.currentTimeMillis() - startTime < time) {//没有获取到锁,也没到等待时长,执行订阅释放锁的任务LockEntry lockEntry = subscribe(lockName, threadId, () -> {});try {while (true) {ttl = tryAcquire(lockName, leaseTime, timeUnit);// lock acquiredif (ttl == null) {return true;}long remainTtl = time - System.currentTimeMillis() + startTime;if (remainTtl < 0) {return false;}// waiting for messagelockEntry.getLatch().tryAcquire(ttl >= 0 && ttl < remainTtl ? ttl : remainTtl, TimeUnit.MILLISECONDS);}} catch (InterruptedException e) {log.error("thread interrupted", e);throw new RuntimeException(e);} finally {unsubscribe(lockEntry, lockName);}} else {return false;}}private Long tryAcquire(String lockName, long leaseTime, TimeUnit unit) {String currentThreadId = getCurrentThreadId();//设定了自动释放锁的时间if (leaseTime != -1) {return tryLockInner(leaseTime, unit, lockName, currentThreadId);}//没有设置自动过期时间,就需要在获取到之后使用看门狗续期Long remainTtl = tryLockInner(internalLockLeaseTime, TimeUnit.MILLISECONDS, lockName, currentThreadId);// lock acquiredif (remainTtl == null) {scheduleExpirationRenewal(lockName, currentThreadId);}return remainTtl;}/*** 加锁成功返回null,否则返回锁的过期时间** @param leaseTime* @param unit* @param lockName* @param threadId* @return*/private Long tryLockInner(long leaseTime, TimeUnit unit, String lockName, String threadId) {long internalLockLeaseTime = unit.toMillis(leaseTime);//查询是否存在锁LockObject existLock = lockRepository.queryLock(lockName);LockObject lockObject = new LockObject();lockObject.setLockName(lockName);lockObject.setThreadId(threadId);lockObject.setMachineId(machineId);lockObject.setIsLocked(true);lockObject.setExpireTime(new Date(System.currentTimeMillis() + internalLockLeaseTime));if (existLock == null) {//保存锁lockObject.setState(1);try {lockRepository.save(lockObject);} catch (Exception e) {//抛出数据重复异常,说明被其他线程锁定了//返回需要等待的时间log.error("lock other thread occupy", e);return reCheckTtl(leaseTime, unit, lockName, threadId);}} else {//存在的锁会判断是否是当前线程的,如果是也允许加锁成功,支持可重入//如果正好其他锁释放了,那也会抢锁,具体是否公平由各数据库的内部锁决定int updateNum = lockRepository.reentrantLock(lockObject);if (updateNum == 0) {//返回需要等待的时间return reCheckTtl(leaseTime, unit, lockName, threadId);}}//加锁成功return null;}private Long reCheckTtl(long leaseTime, TimeUnit unit, String lockName, String threadId) {Long ttl = queryLockTtl(lockName);if (ttl == null) {//如果返回null,那就是获取锁的时候失败了,但是执行查询锁的过期时间的时候释放了//就需要重新执行上锁逻辑return tryLockInner(leaseTime, unit, lockName, threadId);} else {return ttl;}}/*** 获取锁的释放时间,单位毫秒,* 如果锁不存在 或者 未上锁 或者 已过期 则返回null** @param lockName* @return*/private Long queryLockTtl(String lockName) {LockObject lockObject = lockRepository.queryLock(lockName);if (lockObject != null && lockObject.getExpireTime() != null) {long intervalTime = lockObject.getExpireTime().getTime() - System.currentTimeMillis();if (intervalTime > 0) {return intervalTime;}}return null;}
<update id="updateReentrantLock">update distributed_lock<set>is_locked   = true,machine_id   = #{machineId,jdbcType=VARCHAR},thread_id   = #{threadId,jdbcType=VARCHAR},state       = if(expire_time &lt; NOW(), 1, state + 1),expire_time = #{expireTime,jdbcType=TIMESTAMP}</set>where is_deleted = 0and lock_name = #{lockName,jdbcType=VARCHAR}and (expire_time &lt; NOW()or is_locked = falseor (machine_id = #{machineId,jdbcType=VARCHAR}and thread_id = #{threadId,jdbcType=VARCHAR}))</update>

续约

如果锁没有设置过期时间,那么就需要设置自动续期,使用过期和续期的目的也是为了防止服务宕机导致锁无法释放的问题。如果续期失败说明锁已经释放了,那么会自动停止锁的续约任务。

private void scheduleExpirationRenewal(String lockName, String threadId) {ExpirationEntry entry = new ExpirationEntry(lockName, threadId);ExpirationEntry oldEntry = expirationRenewalMap.putIfAbsent(expirationRenewalKey(lockName, threadId), entry);if (oldEntry != null) {oldEntry.addCount();} else {//只对第一次获取锁的线程续约,后面的属于重入renewExpiration(lockName, threadId);}}private void renewExpiration(String lockName, String threadId) {String keyName = expirationRenewalKey(lockName, threadId);ExpirationEntry ee = expirationRenewalMap.get(keyName);if (ee == null) {return;}//获取到锁后过1/3时间开启续约任务scheduledExecutor.schedule(() -> {ExpirationEntry ent = expirationRenewalMap.get(keyName);if (ent == null) {return;}boolean renewResult = renewExpirationLock(lockName, ent.getThreadId());if (!renewResult) {//更新失败说明锁被释放了log.error("Can't update lock " + lockName + " expiration");expirationRenewalMap.remove(keyName);return;}// reschedule itselfrenewExpiration(lockName, threadId);}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);}private void cancelExpirationRenewal(String lockName, String threadId) {String keyName = expirationRenewalKey(lockName, threadId);ExpirationEntry task = expirationRenewalMap.get(keyName);if (task == null) {return;}Integer count = task.reduceCount();if (count == 0) {expirationRenewalMap.remove(keyName);}}private String expirationRenewalKey(String lockName, String threadId) {return lockName + "_" + threadId;}/*** 续期** @param lockName* @param threadId*/private boolean renewExpirationLock(String lockName, String threadId) {LockObject lockObject = new LockObject();lockObject.setLockName(lockName);lockObject.setThreadId(threadId);lockObject.setMachineId(machineId);lockObject.setExpireTime(new Date(System.currentTimeMillis() + internalLockLeaseTime));int updateNum = lockRepository.renewExpirationLock(lockObject);return updateNum != 0;}
<update id="updateRenewExpirationLock">update distributed_lockset expire_time = #{expireTime,jdbcType=TIMESTAMP}where is_deleted = 0and is_locked = trueand lock_name = #{lockName,jdbcType=VARCHAR}and machine_id   = #{machineId,jdbcType=VARCHAR}and thread_id   = #{threadId,jdbcType=VARCHAR}and expire_time &gt; NOW()</update>

阻塞式获取锁

阻塞式获取锁和非阻塞的区别就是等待锁释放的过程,没有获取到锁的线程会一直等待下去。

public void lock(String lockName, long leaseTime, TimeUnit unit) {LockEntry lockEntry = null;try {while (true) {// 尝试获取锁Long ttl = tryAcquire(lockName, leaseTime, unit);if (ttl == null) {// 成功获取到锁,直接退出break;}// 未获取到锁,订阅锁释放通知(如果还没订阅)if (lockEntry == null) {lockEntry = subscribe(lockName, getCurrentThreadId(), () -> {});}// 等待锁释放通知,直到TTL时间结束try {lockEntry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {// 恢复线程的中断状态Thread.currentThread().interrupt();throw new RuntimeException("Thread was interrupted while waiting for the lock", e);}}} finally {// 确保在退出时释放锁并取消订阅if (lockEntry != null) {unsubscribe(lockEntry, getCurrentThreadId());}}}

解锁

获取锁的线程释放锁的时候,state会减1,直到减到0,锁才会真正的释放。这里需要移除锁续约的任务,并且唤醒等待当前锁的线程

public void unlock(String lockName) {if (releaseLock(lockName)) {//释放锁成功后去除看门狗的续期//如果解锁失败,比如自己获取到锁过期了,然后又去释放锁,因为他没有续约任务所以不需要移除cancelExpirationRenewal(lockName, getCurrentThreadId());//发送锁释放的通知// 这里只处理本机维护的等待锁的线程,其他的机器数据库没法主动发出通知,需要轮训或者由获取锁的线程下次获取锁时自行处理LockEntry lockEntry = subscribeMap.get(lockName);//要判空,因为如果没有阻塞中的线程,那么lockEntry会为空if (lockEntry != null) {Semaphore semaphore = lockEntry.getLatch();if (semaphore.hasQueuedThreads()) {semaphore.release();}}}}
<update id="updateReleaseLock">update distributed_lock<set>state       = state - 1,expire_time = if(state=0, null, expire_time),is_locked   = if(state=0, false, true),machine_id   = if(state=0, null, machine_id),thread_id   = if(state=0, null, thread_id),</set>where is_deleted = 0and lock_name = #{lockName,jdbcType=VARCHAR}and machine_id   = #{machineId,jdbcType=VARCHAR}and thread_id   = #{threadId,jdbcType=VARCHAR}and expire_time &gt; NOW()and is_locked = true</update>

检查锁是否过期或者释放

因为mysql数据库没有发布订阅的功能,所以这里采用了定时查询的模式检查锁的状态。如果检测到锁释放了,会发起唤醒等待锁线程的通知,让等待的线程重新尝试获取锁。

public void process() {scheduledExecutor.scheduleAtFixedRate(() -> {//执行本机订阅这把锁的检查任务List<String> needCheckLockNameList = subscribeMap.entrySet().stream().filter(entry -> entry.getValue().getCounter().get() != 0).map(entry -> entry.getKey()).collect(Collectors.toList());//查询已经过期或者释放的锁List<String> lockNameList = lockRepository.queryAllowObtainLockList(needCheckLockNameList);//执行对应锁的唤醒操作lockNameList.forEach(lockName -> {LockEntry lockEntry = subscribeMap.get(lockName);if (lockEntry != null) {//这里最多多唤醒一次,无非就是让等待线程多抢占一次,没什么关系,这种场景发生在tryAcquire正好过期,定时任务正好运行//多一次判断可以大幅度减少冲突时多释放的信号Semaphore semaphore = lockEntry.getLatch();if (semaphore.hasQueuedThreads()) {semaphore.release();log.info("定时任务发起唤醒等待锁的通知");}}});}, 0, 1, TimeUnit.SECONDS);}
<select id="queryAllowObtainLockList" resultType="java.lang.String">select lock_namefrom distributed_lockwhere is_deleted = 0and lock_name in<foreach collection="list" item="lockName" open="(" close=")" separator=",">#{lockName,jdbcType=VARCHAR}</foreach>and (is_locked = falseor expire_time &lt; NOW())</select>

使用示例

public static void main(String[] args) {// 第一个Spring容器,加载配置类 Config1ApplicationContext context1 = new AnnotationConfigApplicationContext(MybatisPlusConfig.class);// 第二个Spring容器,加载配置类 Config2ApplicationContext context2 = new AnnotationConfigApplicationContext(MybatisPlusConfig.class);DatabaseDistributedLock server1 = context1.getBean(DatabaseDistributedLock.class);DatabaseDistributedLock server2 = context2.getBean(DatabaseDistributedLock.class);server1.lock("test");new Thread(() -> {ThreadUtil.sleep(1, TimeUnit.SECONDS);if (server2.tryLock("test", 17L, TimeUnit.SECONDS)) {System.out.println("我执行了1");ThreadUtil.sleep(5, TimeUnit.SECONDS);server2.unlock("test");}}).start();new Thread(() -> {ThreadUtil.sleep(2, TimeUnit.SECONDS);if (server1.tryLock("test", 17L, TimeUnit.SECONDS)) {System.out.println("我执行了2");ThreadUtil.sleep(5, TimeUnit.SECONDS);server1.unlock("test");}}).start();System.out.println("我获取到了锁");ThreadUtil.sleep(15, TimeUnit.SECONDS);server1.unlock("test");ThreadUtil.sleep(100, TimeUnit.SECONDS);}

优化方案

订阅通知如果有消息队列的话,可以借助用来实现发布订阅锁通知

相关文章:

Java基于数据库的分布式可重入锁(带等待时间和过期时间)

文章目录 技术背景介绍代码实现数据库表结构尝试获取锁续约阻塞式获取锁解锁检查锁是否过期或者释放 使用示例优化方案 项目代码 技术背景介绍 一般分布式锁使用最方便的就是使用redis实现&#xff0c;因为他自带超时过期机制、发布订阅模式、高吞吐高性能的优势&#xff0c;…...

国家信息安全水平考试(NISP一级)最新题库-第十七章

目录 另外免费为大家准备了刷题小程序和docx文档&#xff0c;有需要的可以私信获取 1 受到了ARP欺骗的计算机&#xff0c;发出的数据包&#xff0c;     地址是错误的&#xff08;&#xff09; A.源IP&#xff1b;B.目的IP&#xff1b;C.源MAC&#xff1b;D.目的MAC 正…...

Java 8 新特性概览

Java 8 是 Java 语言发展史上的一个重要里程碑&#xff0c;它引入了许多革命性的特性&#xff0c;极大地提高了开发效率和程序性能。以下是 Java 8 的一些关键新特性&#xff1a; 1. Lambda 表达式 Lambda 表达式是 Java 8 中最引人注目的特性之一。它允许你以简洁的语法编写…...

pyspark==堆叠

安装环境 docker pull jupyter/all-spark-notebook 方式一 from pyspark.sql import SparkSession from pyspark.sql.functions import expr, col# 创建SparkSession spark SparkSession.builder.appName("StudentScores").getOrCreate()# 创建示例数据 data [(…...

Zypher Network Layer3 主网上线,不容错过的“宝藏方舟”活动

前言 随着 Zytron Layer3 主网的上线&#xff0c;Zypher Network 联合 Linea 共同推出了“宝藏方舟”活动&#xff0c;用户可通过参与活动&#xff0c;获得包括代币、积分、SBT 等系列奖励。 Zypher Network 是一个以 ZK 方案为核心的游戏底层堆栈&#xff0c;其提供了一个具备…...

【小白学机器学习21】 理解假设检验的关键:反证法

目录 理解假设检验的关键&#xff1a;反证法 1 假设的检验的出发点&#xff1a;H1假设&#xff0c; 1.1 为什么我们不去直接证明H1是否正确&#xff1f; 2 故意设立一个假设H1的否命题为H0 3 设定显著度α 4 总结假设检验的整个思路就是反证法 5 两类错误的关系 理解假…...

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的&#xff1f;在文章开头我们提出这个疑问&#xff0c;带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容&#xff0c;在日常App 中非常常见&#xff0c;比如微博的发布与展示&#xff0c;朋友圈的发布与展示&#xff0c;都在使用富文…...

Python Q-learning 算法详解与应用案例

目录 Python Q-learning 算法详解与应用案例引言一、Q-learning 的基本原理1.1 强化学习基础1.2 Q值及其更新1.3 Q-learning 的特性 二、Python 中 Q-learning 的面向对象实现2.1 QTable 类的实现2.2 Environment 类的实现2.3 Agent 类的实现 三、案例分析3.1 简单环境中的 Q-l…...

解决:如何在opencv中得到与matlab立体标定一样的矫正图?(python版opencv)

目的&#xff1a;采用一样的标定参数&#xff0c;matlab中和opencv中的立体矫正图像是一样的吗&#xff1f;不一样的话怎么让它们一样&#xff1f; 结论&#xff1a;不一样。后文为解决方案。 原因&#xff1a;注意matlab的标定结果在matlab中的用法和在opencv中的用法不一样&a…...

gin入门教程(4):路由与处理器

路由与处理器 在 Gin 框架中&#xff0c;路由和处理器是核心组成部分&#xff0c;负责将 HTTP 请求映射到相应的处理逻辑。 1. 定义路由 在 cmd/main.go 中&#xff0c;您可以定义不同的路由&#xff0c;例如&#xff1a; r.GET("/ping", func(c *gin.Context) {…...

【python+Redis】hash修改

文章目录 前请详解一、关于Update1. 语法2. 代码示例 二、完整代码 前请详解 Redis库数据 keyvalue1{“id”: 1, “name”: “xxx”, “age”: “18”, “sex”: “\u7537”}2{“id”: 2, “name”: “xxx”, “age”: “18”, “sex”: “\u5973”}3{“id”: 3, “name”: “…...

MAVlink协议 部分通用消息集解析

文章目录 MAVLink是一种非常轻量级的消息传输协议, 用于地面控制终端&#xff08;地面站&#xff09;与无人机之间 (以及机载无人机组件之间) 进行通信&#xff0c; 为一种设计用于资源受限系统及带宽受限链路的二进制遥测协议。 HEARTBEAT 检测信号消息显示系统或组件存在并正…...

c++实现跳表

原理 跳表&#xff08;Skip List&#xff09; 是一种随机化数据结构&#xff0c;用于高效查找、插入和删除&#xff0c;尤其适用于有序数据集合。相比链表&#xff0c;跳表通过多层索引结构加速查找&#xff0c;期望时间复杂度接近 O(log⁡n)。跳表的主要思想是&#xff1a; …...

新探索研究生英语读写教程pdf答案(基础级)

《新探索研究生英语读写教程》的设计和编写充分考虑国内研究生人才培养目标和研究生公共英语的教学需求&#xff0c; 教学内容符合研究生认知水平&#xff0c; 学术特征突出&#xff1b;教学设计紧密围绕学术阅读、学术写作和学术研究能力培养&#xff1b;教学资源立体多元&…...

管道与共享内存

一&#xff0c;命名管道 管道的限制就是他只能在有血缘关系&#xff08;父子进程&#xff09;的进程中&#xff0c;允许互相访问&#xff0c;这是有局限性的&#xff0c;所以我们想在毫无关系的进程中允许他们相互访问&#xff0c;这就是命名管道的定义。 总结&#xff1a;命名…...

ES 自定义排序方式

es默认score是根据query的相关度进行打分的&#xff0c;具体打分机制可以参见&#xff1a;官方文档。如果召回时既希望有相关性又能根据其他信息进行排序。 例如小红书搜索的时候&#xff0c;可能既希望有召回相关度又能根据热度信息&#xff08;如果喜欢、收藏等等参数去进行召…...

在vue中,编写一个li标签同时使用v-for和v-if,谁的优先级更高

在 Vue 中&#xff0c;v-if 和 v-for 是两个常用的指令&#xff0c;但它们的优先级不同。当二者一起使用时&#xff0c;v-for 的优先级高于 v-if。这意味着&#xff0c;v-for 会先执行&#xff0c;即使列表中的某些元素不满足 v-if 条件&#xff0c;它们仍会被遍历和渲染。 由…...

Java 后端开发面试题及其答案

以下是一些常见的 Java 后端开发面试题及其答案&#xff0c;涵盖了 Java 基础、面向对象、并发、多线程、框架等多个方面&#xff1a; 1. Java 中的基本数据类型有哪些&#xff1f; 答案&#xff1a; Java 中的基本数据类型有 8 种&#xff1a; int&#xff1a;32 位整数lon…...

C++,STL 045(24.10.24)

内容 1.对set容器的大小进行操作。 2.set容器的交换操作。 运行代码 #include <iostream> #include <set>using namespace std;void printSet(set<int> &s) {for (set<int>::iterator it s.begin(); it ! s.end(); it){cout << *it <…...

二叉树习题其五【力扣】【算法学习day.12】

前言 书接上篇文章二叉树习题其四&#xff0c;这篇文章我们将基础拓展 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...