分布式锁:Mysql实现,Redis实现,Zookeeper实现

目录

前置知识

Mysql实现分布式锁

1.get_lock函数

 Java代码实现:

2.for update尾缀

 Java代码实现:

3.自己定义锁表 

Java代码实现:

4.时间戳列实现乐观锁

Java代码实现:

Redis实现分布式锁

Zookeeper实现分布式锁:

Java代码实现:


前置知识

想要了解更多线程和锁相关的知识,可以看下面这个文章,了解线程和锁知识有助于理解本文内容

JAVA:创建线程,线程安全,线程锁,线程的生命周期,线程池icon-default.png?t=N7T8https://blog.csdn.net/dxh9231028/article/details/140676316?spm=1001.2014.3001.5501

Mysql实现分布式锁

Mysql实现分布式锁有四种方式,分别是get_lock函数,for update尾缀,定义一个锁表,手动实现锁逻辑实现悲观锁,以及通过定义时间戳的锁表实现乐观锁。

1.get_lock函数

get_lock函数是mysql中内置的函数,可以通过select get_lock(lockname,timeout)来实现锁的功能,其中lockname是锁的名字,timeout为获取锁的超时时间,当想要限制多个线程无法同时操作同一个资源时,只需要在这些线程在执行sql之前,使用相同的lockname先执行select get_lock(lockname,timeout),第一个执行线程,mysql会创建一个lockname和当前这个会话的id的对应关系,并返回1,而其他线程执行select get_lock(lockname,timeout)时则会返回0,线程在执行完对应的操作后,需要显示的调用select release_lock(lockname)来释放锁。

该方法有一个非常致命的缺陷,当一个线程由于某些原因突然失联,没有来的及执行release_lock来释放锁,那么该资源将处于长时间的不能访问状态。

 Java代码实现:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class MySQLDistributedLock {private Connection connection;//构造函数,new的时候创建连接public MySQLDistributedLock(String url, String user, String password) throws Exception {connection = DriverManager.getConnection(url, user, password);}// 获取锁public boolean acquireLock(String lockName, int timeout) throws Exception {//创建sql模板String sql = "SELECT GET_LOCK(?, ?)";//准备连接,sql模板预解析try (PreparedStatement stmt = connection.prepareStatement(sql)) {//传入锁名stmt.setString(1, lockName);stmt.setInt(2, timeout);//执行sqltry (ResultSet rs = stmt.executeQuery()) {if (rs.next()) {//如果结果为1则返回true,证明获取锁成功return rs.getInt(1) == 1;}}}catch (Exception e){//发生异常释放锁releaseLock(lockName);}return false;}// 释放锁public boolean releaseLock(String lockName) throws Exception {String sql = "SELECT RELEASE_LOCK(?)";try (PreparedStatement stmt = connection.prepareStatement(sql)) {stmt.setString(1, lockName);try (ResultSet rs = stmt.executeQuery()) {if (rs.next()) {return rs.getInt(1) == 1;}}}return false;}public static void main(String[] args) {MySQLDistributedLock lock = null;//定义锁名String lockName = "my_distributed_lock";try {//创建连接lock = new MySQLDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");//获取锁if (lock.acquireLock(lockName, 10)) {System.out.println("Lock acquired!");// 执行需要同步的业务逻辑Thread.sleep(5000);//释放锁lock.releaseLock(lockName);System.out.println("Lock released!");} else {System.out.println("Failed to acquire lock.");}} catch (Exception e) {//发生异常释放锁try{lock.releaseLock(lockName);}catch(Exception ex){ex.printStackTrace();}e.printStackTrace();}}
}

2.for update尾缀

在执行mysql语句时,在后面添加上for update,那么当前事务操作的行,在当前事务执行完之前,不允许其他事务来进行修改操作,或读操作但是sql语句后面也拼接了for update,通过这个机制,也可以实现分布式锁。

我们可以通过创建一个锁表,多个线程通过select * where lockname = lockname for update,通过这种方式,只有一个线程可以成功获取到锁表中的锁。

相较于get_lock方法,for update当会话意外关闭时,事务会马上回滚,不会造成get_lock方法那样长时间的无法获取锁,并且for update不仅可以获取锁的方法来实现多个进程不操作同一资源,也可以直接操作目标资源,不过没有定义锁表获取锁更灵活。

 Java代码实现:

/*创建如下锁表CREATE TABLE distributed_lock (lock_name VARCHAR(255) NOT NULL PRIMARY KEY,lock_value INT,lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;
*/import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class MySQLInnoDBDistributedLock {private Connection connection;public MySQLInnoDBDistributedLock(String url, String user, String password) throws Exception {connection = DriverManager.getConnection(url, user, password);connection.setAutoCommit(false); // 开启事务管理}// 获取锁public boolean acquireLock(String lockName) throws Exception {String sql = "SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE";try (PreparedStatement stmt = connection.prepareStatement(sql)) {stmt.setString(1, lockName);try (ResultSet rs = stmt.executeQuery()) {return rs.next()}}}// 释放锁public void releaseLock() throws Exception {connection.commit(); // 提交事务释放锁}//关闭数据库连接public void close() throws Exception {if (connection != null) {connection.close();}}public static void main(String[] args) {try {MySQLInnoDBDistributedLock lock = new MySQLInnoDBDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");String lockName = "my_distributed_lock";if (lock.acquireLock(lockName)) {System.out.println("Lock acquired!");// 执行需要同步的业务逻辑...lock.releaseLock();System.out.println("Lock released!");} else {System.out.println("Failed to acquire lock.");}lock.close();} catch (Exception e) {e.printStackTrace();}}
}

3.自己定义锁表 

通过自己定义一个锁表,并且定义一个不可重复的列,然后多个线程通过插入同一个数据来判断是否获取到锁,而释放锁的操作则是删除这条数据,通过自己实现这一过程,可以实现更灵活的分布式锁机制。

Java代码实现:

/*CREATE TABLE distributed_lock (lock_name VARCHAR(255) NOT NULL PRIMARY KEY,locked_by VARCHAR(255),lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
*/import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class MySQLTableDistributedLock {private Connection connection;public MySQLTableDistributedLock(String url, String user, String password) throws Exception {connection = DriverManager.getConnection(url, user, password);}// 获取锁public boolean acquireLock(String lockName, String clientId) throws Exception {String sql = "INSERT INTO distributed_lock (lock_name, locked_by) VALUES (?, ?)";try (PreparedStatement stmt = connection.prepareStatement(sql)) {stmt.setString(1, lockName);stmt.setString(2, clientId);return stmt.executeUpdate() == 1;} catch (Exception e) {// 插入失败意味着锁已被其他客户端持有return false;}}// 释放锁public boolean releaseLock(String lockName, String clientId) throws Exception {String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND locked_by = ?";try (PreparedStatement stmt = connection.prepareStatement(sql)) {stmt.setString(1, lockName);stmt.setString(2, clientId);return stmt.executeUpdate() == 1;}}public static void main(String[] args) {try {MySQLTableDistributedLock lock = new MySQLTableDistributedLock("jdbc:mysql://localhost:3306/test", "root", "password");String lockName = "my_distributed_lock";String clientId = "client_1";if (lock.acquireLock(lockName, clientId)) {System.out.println("Lock acquired!");// 执行需要同步的业务逻辑Thread.sleep(5000);lock.releaseLock(lockName, clientId);System.out.println("Lock released!");} else {System.out.println("Failed to acquire lock.");}} catch (Exception e) {e.printStackTrace();}}
}

4.时间戳列实现乐观锁

通过在需要限制多个线程同时访问的资源表中加入一个时间戳列保存最后的编辑时间,在执行修改操作之前先获取目标的时间戳,并且执行操作时在原有的条件的基础上添加一个时间戳等于之前获取的时间戳的条件,并且在修改后更新时间戳。通过这种方式,可以验证当前操作的数据是否在获取之前是否被篡改过,如果篡改过则会导致无法选择目标资源。

乐观锁在低并发情况下性能更好,但在高并发的情况下可能会导致某些线程可以更快的操作成功,有些线程则长时间无法操作成功,操作是否成功具有随机性。

Java代码实现:

/*CREATE TABLE users (id INT PRIMARY KEY,username VARCHAR(100),email VARCHAR(100),last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);
*/import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;public class OptimisticLockingExample {private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test";private static final String JDBC_USER = "root";private static final String JDBC_PASSWORD = "password";public static void main(String[] args) {Connection connection = null;try {// 1. 建立数据库连接connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);// 2. 读取记录及其时间戳int userId = 1;UserRecord userRecord = getUserRecord(connection, userId);if (userRecord == null) {System.out.println("User not found.");return;}System.out.println("Original User Record: " + userRecord);// 3. 尝试使用乐观锁更新记录boolean updateSuccess = updateUserEmail(connection, userId, "newemail@example.com", userRecord.getLastModified());if (updateSuccess) {System.out.println("Update successful.");} else {System.out.println("Update failed due to concurrent modification. Please retry.");}} catch (SQLException e) {e.printStackTrace();} finally {// 关闭数据库连接if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}// 获取用户记录及其时间戳private static UserRecord getUserRecord(Connection connection, int userId) throws SQLException {String selectSql = "SELECT id, username, email, last_modified FROM users WHERE id = ?";try (PreparedStatement stmt = connection.prepareStatement(selectSql)) {stmt.setInt(1, userId);try (ResultSet rs = stmt.executeQuery()) {if (rs.next()) {int id = rs.getInt("id");String username = rs.getString("username");String email = rs.getString("email");Timestamp lastModified = rs.getTimestamp("last_modified");return new UserRecord(id, username, email, lastModified);}}}return null;}// 使用乐观锁更新用户的邮箱地址private static boolean updateUserEmail(Connection connection, int userId, String newEmail, Timestamp lastModified) throws SQLException {String updateSql = "UPDATE users SET email = ?, last_modified = CURRENT_TIMESTAMP WHERE id = ? AND last_modified = ?";try (PreparedStatement stmt = connection.prepareStatement(updateSql)) {stmt.setString(1, newEmail);stmt.setInt(2, userId);stmt.setTimestamp(3, lastModified);int rowsAffected = stmt.executeUpdate();return rowsAffected > 0;}}// 简单的 UserRecord 类,用于存储用户记录private static class UserRecord {private int id;private String username;private String email;private Timestamp lastModified;public UserRecord(int id, String username, String email, Timestamp lastModified) {this.id = id;this.username = username;this.email = email;this.lastModified = lastModified;}public int getId() {return id;}public String getUsername() {return username;}public String getEmail() {return email;}public Timestamp getLastModified() {return lastModified;}@Overridepublic String toString() {return "UserRecord{" +"id=" + id +", username='" + username + '\'' +", email='" + email + '\'' +", lastModified=" + lastModified +'}';}}
}

Redis实现分布式锁

redis通过其内置的setnx指令实现,setnx指令在目标不存在会创建成功,存在时创建失败,这样可以让多个线程创建同一个key来实现分布式锁的功能,创建成功则为获取到锁。创建完key后还要设置过期时间,防止会话意外关闭导致无法释放锁。

redis大多以集群模式出现,当多个多个主机接收到不同的写操作时可能会造成同时写入成功,对此redis官方提出了redlock算法,其规定在获取锁时需要对每个主机都下发一条创建指令,只有当超过半数主机认为创建成功时,才会成功获取锁,不过这个算法并没有被redis实现,但是可以使用一些第三方驱动,或者手动实现也不算复杂,以下是一种实现方式:

import redis.clients.jedis.Jedis;import java.util.UUID;public class Redlock {//保存所有redis实例。private Jedis[] redisInstances;// 锁的过期时间 (10秒)private long lockTimeout = 10000; // 假设有五个实例,那么则成功数则需要达到3个private int quorum = 3; //构造函数,传入redis实例public Redlock(Jedis... redisInstances) {this.redisInstances = redisInstances;}//尝试获取传入的锁,也就是创建传入的keypublic String tryLock(String lockKey) {//获取一个唯一id作为valueString lockValue = UUID.randomUUID().toString();//获取当前时间时间戳long startTime = System.currentTimeMillis();//记录成功的数量int lockCount = 0;//开始在各个redis实例上创建key。for (Jedis jedis : redisInstances) {if (jedis.set(lockKey, lockValue, "NX", "PX", lockTimeout) != null) {lockCount++;}}// 判断是否获取锁成功(成功数量大于实力数量的一般,并且整个过程没有超过超时时间)long elapsedTime = System.currentTimeMillis() - startTime;if (lockCount >= quorum && elapsedTime < lockTimeout) {return lockValue;} else {// 获取失败,释放已获取到的锁for (Jedis jedis : redisInstances) {// 只删除value是自己的UUID的key,也就是只删除自己创建的keyif (lockValue.equals(jedis.get(lockKey))) {jedis.del(lockKey);}}return null;}}public void unlock(String lockKey, String lockValue) {for (Jedis jedis : redisInstances) {if (lockValue.equals(jedis.get(lockKey))) {jedis.del(lockKey);}}}public static void main(String[] args) {// 假设你有5个独立的 Redis 实例,实际的IP和端口是在Nacos或Zookeeper中获取Jedis redis1 = new Jedis("localhost", 6379);Jedis redis2 = new Jedis("localhost", 6380);Jedis redis3 = new Jedis("localhost", 6381);Jedis redis4 = new Jedis("localhost", 6382);Jedis redis5 = new Jedis("localhost", 6383);Redlock redlock = new Redlock(redis1, redis2, redis3, redis4, redis5);String lockKey = "myLock";String lockValue = redlock.tryLock(lockKey);if (lockValue != null) {System.out.println("Lock acquired!");// 执行需要保护的操作redlock.unlock(lockKey, lockValue);System.out.println("Lock released!");} else {System.out.println("Failed to acquire lock.");}}
}

Zookeeper实现分布式锁:

zookeeper实现分布式锁有着绝对的优势,因为其四种节点类型中有一种节点类型是临时有序的节点。在创建相同key的有序节点时,创建并不会失败,而是会在key后面加上一串数字,这个数字是递增的,而临时节点的特性是在会话关闭时临时节点会删除这个key,这就使zookeeper在实现分布式锁时可以创建临时有序节点,客户端只需要判断自己创建的key后面的序号是不是其中最小的即可,这样不仅可以做到有序的获取锁,还能让会话意外断开时自动删除key,释放锁。

实际操作中可以创建一个/lock目录,然后所有进程都创建/lock/lockname,并且在内部判断lockname后面的数字是不是最小的,如果是最小的则开始操作目标资源,操作完毕后删除自己的key。了解zookeeper知识,可以看下面这篇文章:

Zookeeper使用快速入门:基础命令,wacth监控,权限控制icon-default.png?t=N7T8https://blog.csdn.net/dxh9231028/article/details/141105185?spm=1001.2014.3001.5501

在客户端正常断开会话前仍需手动释放,因为zookeeper判断会话断开需要时间,所以这个特性只能防止会话意外断开时锁长时间无法释放的情况,不能作为锁释放的正常途径。

Java代码实现:

import org.apache.zookeeper.*;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs.Ids;import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class ZookeeperDistributedLock {//zookeeper的ip,实际项目中写在配置文件中private static final String ZK_ADDRESS = "localhost:2181";//锁的根目录,在这个目录下创建子节点锁private static final String LOCK_PATH = "/locks";//要创建的锁的路径private static final String LOCK_NODE_PREFIX = LOCK_PATH + "/lockname";private ZooKeeper zooKeeper;private String currentNode;private String lockNode;public static void main(String[] args) throws Exception {ZookeeperDistributedLock lock = new ZookeeperDistributedLock();lock.connect();try {lock.acquireLock();System.out.println("Lock acquired!");// 执行需要保护的操作Thread.sleep(5000); // 模拟操作} finally {lock.releaseLock();System.out.println("Lock released!");lock.close();}}//连接zookeeper客户都安,并将连接赋予zookeeper变量public void connect() throws IOException {zooKeeper = new ZooKeeper(ZK_ADDRESS, 3000, new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == EventType.NodeDeleted && event.getPath().equals(lockNode)) {synchronized (this) {notify();}}}});}//获取锁,也就是创建key的过程public void acquireLock() throws Exception {//检查父目录/lock是否存在,如果不存在则创建if (zooKeeper.exists(LOCK_PATH, false) == null) {zooKeeper.create(LOCK_PATH, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}//在/lock下创建/lock/locknamecurrentNode = zooKeeper.create(LOCK_NODE_PREFIX, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);//循环判断当下最小的节点是不是本线程创建的while (true) {List<String> nodes = zooKeeper.getChildren(LOCK_PATH, false);Collections.sort(nodes);if (currentNode.endsWith(nodes.get(0))) {// This is the smallest node, thus the lock is acquiredlockNode = currentNode;break;}// Listen for changes to the previous nodeString previousNode = findPreviousNode(nodes);if (previousNode != null) {String previousNodePath = LOCK_PATH + "/" + previousNode;synchronized (this) {zooKeeper.exists(previousNodePath, true);wait();}}}}private String findPreviousNode(List<String> nodes) {String myNode = currentNode.substring(LOCK_PATH.length() + 1);for (int i = nodes.size() - 1; i >= 0; i--) {String node = nodes.get(i);if (node.compareTo(myNode) < 0) {return node;}}return null;}//删除key,释放锁public void releaseLock() throws KeeperException, InterruptedException {zooKeeper.delete(currentNode, -1);}//关闭连接,释放资源public void close() throws InterruptedException {zooKeeper.close();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/403726.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完整搭建windows下mysql8.0源码编译调试环境!

背景&#xff1a; 前段时间一直在看mysql相关的博客&#xff0c;所以对源码起了浓厚的兴趣&#xff0c;所以尝试通过vmware和vscode在windosw环境中搭建一套编译调试的环境~ 看了一下网上的搭建教程基本杂乱无章&#xff0c;想要从零跟着搭建出一个完善的调试环境也不是易事&…

Leetcode3232. 判断是否可以赢得数字游戏

Every day a Leetcode 题目来源&#xff1a;3232. 判断是否可以赢得数字游戏 解法1&#xff1a;3232. 判断是否可以赢得数字游戏 用一个 sum1 统计个位数的和&#xff0c;sum2 统计十位数的和。 只要 sum1 和 sum2 不相等&#xff0c;Alice 拿大的就能赢得这场游戏。 代码…

Maven的依赖范围

依赖的jar包&#xff0c;默认情况下&#xff0c;可以在任何地方使用&#xff0c;可以通过scope来设置作用范围 作用范围&#xff1a; 主程序范围有效&#xff08;main文件夹范围内&#xff09;测试程序范围有效&#xff08;test文件夹范围内&#xff09;是否参与打包运行&…

一次日志记录中使用fastjson涉及到ByteBuffer的教训

背景 目前本人在公司负责的模块中&#xff0c;有一个模块是负责数据同步的&#xff0c;主要是将我们数据产线使用的 AWS Dynamodb 同步的我们的测试QA 的环境的 MongoDB 的库中&#xff0c;去年开始也提供了使用 EMR 批量同步的功能&#xff0c;但是有时候业务也需要少量的数据…

【OpenCV_python】凸包检测 轮廓特征 直方图均衡化 模板匹配 霍夫变换

凸包特征检测 凸包就是图像的最小外接多边形&#xff0c;通过图像的轮廓点&#xff0c;找到距离最远的两个点的直线&#xff0c;根据直线找到距离最远的下一个点&#xff0c;直到所有的点被包围在多边形内 读取图像二值化找图像的轮廓获取凸包点的坐标绘制凸包点 convexHull 获…

程序员如何写PLC程序

PLC是可编程逻辑控制器的简称&#xff0c;常用的编程语言是IEC61131-3&#xff08;梯形图、结构化文本、指令表、功能块、顺序功能图&#xff09;和西门子的SCL。程序员常用的编程语言是JS、Java、Python、C/C、Go等。PLC广泛采用编程工具有codesys、博图等。程序员常用的编程工…

敏捷架构在数字时代的应用:从理论到实践的全面指南

在数字化转型和技术变革的浪潮中&#xff0c;企业面临着不断提升敏捷性和应对复杂环境的挑战。敏捷架构在数字时代的应用不仅从理论层面阐述了敏捷架构的基本原理&#xff0c;还为企业提供了详细的实践指南&#xff0c;帮助企业从理论走向实际操作。本文将从理论与实践的双重视…

STM32——CAN通讯基础知识

CAN 协议简介 CAN 是控制器局域网络 (Controller Area Network) 的简称&#xff0c;它是由研发和生产汽车电子产品著称的德国 BOSCH 公司开发的&#xff0c;并最终成为国际标准(ISO11519以及ISO11898),是国际上应用最广泛的现场总线之一。差异点如下&#xff1a; 高速CAN可以达…

YOLOv8_det/seg/pose/obb推理流程

本章将介绍目标检测、实例分割、关键点检测和旋转目标检测的推理原理,基于onnx模型推理,那么首先就需要了解onnx模型的输入和输出,对输入的图片需要进行预处理的操作,对输出的结果需要进行后处理的操作,这部分内容在我的另一个专栏《YOLOv8深度剖析》中也有介绍,如果对YO…

《机器学习》一元、多元线性回归的实现 No.4

一、一元线性回归实现 先直接看完整代码&#xff1a; import pandas as pd import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegressiondate pd.read_csv(data.csv) #导入数据plt.scatter(date[广告投入],date[销售额]) # 用散点图展示数据 plt.sh…

实训第二十八天(haproxy与利用python实现mysql主从的读写分离)

1、练习 [rootnat ~]# ipvsadm -d -t 192.168.10.101:3306 -r 10.0.0.22:3306 #删除真实主机 nat: [rootnat ~]# ifconfig ens33: flags4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.0.0.10 netmask 255.255.255.0 broadcast 10.0.0.25…

【JVM】深入理解类加载机制(二)

深入理解类加载机制 HSDB工具的使用 Hotspot Debugger(HSDB):JDK原生自带 以Windows系统为例&#xff0c;jdk8的环境&#xff0c;在jdk的lib目录下&#xff0c;启动之前&#xff0c;你需要确保你进入的lib目录和你当前的JAVA_HOME配置的JDK是相同的&#xff0c;否则可能会出现…

2.1 文件内容差异对比方法

2.1 文件内容差异对比方法 文件内容差异对比方法2.1.1 两个字符串的差异对比2.1.2 生成美观的HTML格式文档2.1.3 对比nginx 配置文件差异代码封装 文件内容差异对比方法 介绍如何通过difflib模块实现文件内容差异对比。difflib作为Python的标准库模块无需安装&#xff0c;作用…

2024年运营技术与网络安全态势研究报告:遭遇多次网络威胁的比例暴增

随着 OT 组织不断在其业务环境中集成各种数字工具和技术&#xff0c;它们面临的安全挑战也日益变得愈加复杂和多样化。正如 NIST 指出&#xff0c; “虽然安全解决方案旨在解决典型 IT系统中的一些问题&#xff0c;但将这些相同的解决方案引入不同的 OT 环境时&#xff0c;必须…

ruoyi-vue-pro项目新建模块的接口都报404错误

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 新建模块之后,该模块后端的请求都是返回404,如图所示: 2. 原理分析 抛开这个项目,对于路径请求不成功,返回404 主要的步骤如下: 检查路由配置: 确保在路由配置文件中添加了新模块的路由 例如,在Spring Boot中,这…

vue3+ts 前端word文档下载文件时不预览直接下载方法(支持 doc / excel / ppt / pdf 等)

前端word文档下载文件时不预览直接下载方法支持 doc / excel / ppt / pdf 等 根据需要&#xff0c;要实现一个下载文档的需要 最简单的方法就是使用a标签 如果是相同域可以直接下载&#xff0c;但如果是不同域的&#xff0c;就会先打开一个预览页&#xff0c;在预览页再点下载…

StarRocks Lakehouse 快速入门——Apache Paimon

StarRocks Lakehouse 快速入门指南为您提供了湖仓技术概览&#xff0c;旨在帮助您迅速掌握其核心特性、独特优势和应用场景。本指南将指导您如何高效地利用 StarRocks 构建解决方案。文章末尾&#xff0c;我们集合了来自阿里云、饿了么、喜马拉雅和同程旅行等行业领导者在 Star…

Eureka原理与实践:构建高效的微服务架构

Eureka原理与实践&#xff1a;构建高效的微服务架构 Eureka的核心原理Eureka Server&#xff1a;服务注册中心Eureka Client&#xff1a;服务提供者与服务消费者 Eureka的实践应用集成Eureka到Spring Cloud项目中创建Eureka Server创建Eureka Client&#xff08;服务提供者&…

什么叫日志门面

日志门面&#xff0c;是门面模式的一个典型的应用。 门面模式&#xff08;Facade Pattern&#xff09;&#xff0c;也称之为外观模式&#xff0c;其核心为&#xff1a;外部与一个子系统的通信必须通过一个统一的外观对象进行&#xff0c;使得子系统更易于使用。 就像Log4j、Lo…

stm32智能颜色送餐小车(ESP8266WIFI模块、APP制作、物联网模型建立、MQTTFX)

大家好啊&#xff0c;我是情谊&#xff0c;今天我们来介绍一下我最近设计的stm32产品&#xff0c;我们在今年七月份的时候参加了光电设计大赛&#xff0c;我们小队使用的就是stm32的智能送餐小车&#xff0c;虽然止步于省赛&#xff0c;但是还是一次成长的经验吧&#xff0c;那…