Java基于数据库、乐观锁、悲观锁、Redis、Zookeeper分布式锁的简单案例实现(保姆级教程)

1. 分布式锁的定义

分布式锁是一种在分布式系统中用来协调多个进程或线程对共享资源进行访问的机制。它确保在分布式环境下,多个节点(如不同的服务器或进程)不会同时访问同一个共享资源,从而避免数据不一致、资源竞争等问题。

2. 分布式锁的工作原理

分布式锁的工作原理与单机锁类似,但它需要考虑多个节点之间的协调。在获取锁时,进程必须确保锁的唯一性,即在任何时刻,只有一个进程能够成功获取锁,并且锁的状态能够在不同节点之间保持一致。通常,分布式锁的实现需要满足以下条件:

条件描述
互斥性在同一时间,只有一个进程能获得锁,确保没有其他进程能够同时访问该资源。
死锁避免应防止由于某些原因(如进程崩溃或网络问题)导致的死锁情况。这通常通过设置锁的过期时间来实现,确保锁在持有者意外失联时能够自动释放。
容错性即使某些节点发生故障或网络分区,锁机制依然能够正确运行,或在适当的时间内恢复正常。这意味着锁的状态在多个节点之间要保持一致,且系统具备一定的自愈能力。

3. 基于数据库

基于数据库的分布式锁是利用数据库的特性来实现的一种简单而常见的分布式锁机制。通过对数据库记录的插入、更新或删除操作,确保在同一时间只有一个进程能够持有锁,从而实现对共享资源的互斥访问。

3.1 使用表记录实现分布式锁

原理:使用表记录实现分布式锁的核心思想是通过数据库表的一行记录来充当锁的标识。每次请求想要获取锁时,会尝试在表中插入一条特定的记录。如果插入成功,则表示获得了锁;如果插入失败(如违反了唯一性约束),则表示锁已被其他请求持有。

步骤

  1. 创建一张专门用于锁定的表,表中包含一个锁名(或资源名)字段和一个锁定状态字段。
  2. 当一个进程需要获取锁时,尝试向该表中插入一条记录。如果插入成功,则表示获取锁成功。
  3. 如果记录已存在(利用主键冲突,唯一约束实现可能更加灵活,可以应用在表的任何字段上),表示锁已被其他进程持有,获取锁失败。
  4. 任务完成后,持锁进程删除该记录以释放锁。

除了锁表,我们还需要一个商品库存表模拟秒杀。

create database lock_;
use lock_;
CREATE TABLE product
(product_id   INT PRIMARY KEY,product_name VARCHAR(255),stock        INT -- 库存量
);-- 初始化库存为5
INSERT INTO product (product_id, product_name, stock)
VALUES (1, '大白菜', 5);-- 分布式锁表
CREATE TABLE distributed_lock (lock_name VARCHAR(255) PRIMARY KEY,  -- 锁的名称lock_owner VARCHAR(255),             -- 锁的持有者标识lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 锁定时间
);

3.1.1 项目结构

在这里插入图片描述

3.1.2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>lock</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.3.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

3.1.3 application.yml

spring:application:name: lockdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/lock_?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456
mybatis:configuration:map-underscore-to-camel-case: true
server:port: 8001

3.1.4 LockApplication.java

package org.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("org.example.mapper")
public class LockApplication {public static void main(String[] args) {SpringApplication.run(LockApplication.class, args);}}

3.1.5 Product.java

package org.example.model;import lombok.Data;@Data
public class Product {private Integer productId;private String productName;private Integer stock;
}

3.1.6 DistributedLock.java

package org.example.model;import java.util.Date;
import lombok.Data;@Data
public class DistributedLock {private String lockName;private String lockOwner;private Date lockTime;}

3.1.7 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper {@Select("SELECT stock FROM product WHERE product_id = #{productId}")int  queryStock(Integer productId);@Update("UPDATE product SET stock = stock - 1 WHERE product_id = #{productId}")int updateStock(Integer productId);
}

3.1.8 DistributedLockMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;@Repository
public interface DistributedLockMapper {@Insert("INSERT INTO distributed_lock (lock_name, lock_owner) VALUES (#{lockName}, #{lockOwner})")int insert(String lockName, String lockOwner);@Delete("DELETE FROM distributed_lock WHERE lock_name = #{lockName} AND lock_owner = #{lockOwner}")int delete(String lockName, String lockOwner);
}

3.1.9 DistributedLockService.java

package org.example.service;import org.example.mapper.DistributedLockMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class DistributedLockService {private final DistributedLockMapper distributedLockMapper;@Autowiredpublic DistributedLockService(DistributedLockMapper distributedLockMapper) {this.distributedLockMapper = distributedLockMapper;}// 尝试获取锁public boolean tryLock(String lockName, String lockOwner) {try {return distributedLockMapper.insert(lockName, lockOwner) > 0;} catch (Exception e) {return false;}}// 释放锁public boolean unlock(String lockName, String lockOwner) {try {return distributedLockMapper.delete(lockName, lockOwner) > 0;} catch (Exception e) {return false;}}
}

3.1.10 ProductService.java

package org.example.service;import org.example.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class ProductService {private final ProductMapper productMapper;@Autowiredpublic ProductService(ProductMapper productMapper) {this.productMapper = productMapper;}// 扣减库存public boolean reduceStock(int productId) {// 查询库存int stock = productMapper.queryStock(productId);if (stock > 0) {// 扣减库存productMapper.updateStock(productId);System.out.println("库存扣减成功,剩余库存: " + (stock - 1));return true;}System.out.println("库存不足,无法扣减。");return false;}
}

3.1.11 DistributedLockController.java

package org.example.controller;import org.example.service.DistributedLockService;
import org.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;@RestController
public class DistributedLockController {@Autowiredprivate DistributedLockService distributedLockService;@Autowiredprivate ProductService productService;@GetMapping("/order")public void pay() {UUID uuid = UUID.randomUUID();String lockName = "product_1";String lockOwner = "user_" + uuid;boolean lockAcquired = false;int retryCount = 0;int maxRetries = 10; // 重试次数while (!lockAcquired && retryCount < maxRetries) {lockAcquired = distributedLockService.tryLock(lockName, lockOwner);if (lockAcquired) {System.out.println(lockOwner + " 成功获取锁,尝试扣减库存...");try {productService.reduceStock(1);} finally {if (distributedLockService.unlock(lockName, lockOwner)) {System.out.println(lockOwner + " 成功释放锁。");} else {System.out.println(lockOwner + " 释放锁失败。");}}} else {retryCount++;System.out.println(lockOwner + " 获取锁失败,重试中... (" + retryCount + "/" + maxRetries + ")");}}if (!lockAcquired) {System.out.println(lockOwner + " 最终未能获取锁,放弃。");}}}

3.1.12 测试验证

前提准备:修改目录3.1.3的服务端口,先启动8001和8002两个不同的端口代表两台不同的服务器,等待两台服务器启动完成后打开Apache JMeter工具,创建一个线程组100个用户,10s内启动所有用户,同时创建两个不同的HTTP端口请求。
在这里插入图片描述
在这里插入图片描述

说明:Copy然后另一个HTTP Request改端口为8002即可。

运行结果8001:

user_8001 成功获取锁,尝试扣减库存…
库存扣减成功,剩余库存: 4
user_8001 成功释放锁。
user_8001 成功获取锁,尝试扣减库存…
库存扣减成功,剩余库存: 2
user_8001 成功释放锁。
user_8001 成功获取锁,尝试扣减库存…
库存扣减成功,剩余库存: 0
user_8001 成功释放锁。

user_8001 成功获取锁,尝试扣减库存…
库存不足,无法扣减。
user_8001 成功释放锁。

运行结果8002:

user_8002 成功获取锁,尝试扣减库存…
库存扣减成功,剩余库存: 3
user_8002 成功释放锁。
user_8002 成功获取锁,尝试扣减库存…
库存扣减成功,剩余库存: 1
user_8002 成功释放锁。
user_8002 成功获取锁,尝试扣减库存…
库存不足,无法扣减。
user_8002 成功释放锁。

说明:在高并发情况下,锁的争夺会导致部分用户未能成功获取锁,造成库存无法完全消耗的情况。这种设计虽然确保了数据的一致性,但在极端并发场景下,可能会导致一些请求被拒绝.所以引入了重试机制进行优化。

3.2 使用乐观锁 (Optimistic Lock) 实现分布式锁

**原理:**乐观锁基于“乐观”的假设,认为并发冲突的可能性较小,因此在更新数据时不直接加锁,而是通过版本号或时间戳等机制来检测数据是否被其他事务修改过。如果在提交时检测到冲突(版本号变化),则放弃本次操作,并要求重试。

  • 步骤

    1. 创建一张锁表,包含锁名、锁定状态以及一个版本号或时间戳字段。
    2. 获取锁时,通过更新操作将锁的状态改变,并检查版本号或时间戳,确保操作是原子性的。
    3. 如果版本号不匹配,表示锁已被其他进程持有,获取锁失败。
  1. 任务完成后,进程更新锁的状态并修改版本号或时间戳。
CREATE TABLE product
(product_id INT PRIMARY KEY,stock      INT,version    INT  default 0 -- 乐观锁的版本号
);-- 初始化库存为5
INSERT INTO product (product_id, stock)
VALUES (1, 5);

3.2.1 项目结构

在这里插入图片描述

3.2.2 pom.xml

同:目录3.1.2完全一致。

3.2.3 application.yml

同:目录3.1.3完全一致。

3.2.4 LockApplication.java

同:目录3.1.4完全一致。

3.2.5 Product.java

package org.example.model;import lombok.Data;@Data
public class Product {private Integer productId;private Integer stock;private Integer version;
}

3.2.6 ProductStock.java

package org.example.model;import lombok.Data;@Data
public class ProductStock {private Integer stock;private Integer version;
}

3.2.7 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.example.model.ProductStock;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper {// 获取当前库存和版本号@Select("SELECT stock,version FROM product WHERE product_id = #{productId}")ProductStock queryStock(Integer productId);// 更新库存并更新版本号@Update("UPDATE product SET stock = stock - 1 , version = version + 1 WHERE product_id = #{productId} AND version =#{version} AND stock > 0")int updateStock(Integer productId,Integer version);
}

3.2.8 ProductService.java

package org.example.service;import org.example.mapper.ProductMapper;
import org.example.model.ProductStock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;@Service
public class ProductService {private final ProductMapper productMapper;@Autowiredpublic ProductService(ProductMapper productMapper) {this.productMapper = productMapper;}@Value("${server.port}")private String port;// 扣减库存public boolean reduceStock(int productId) {// 获取当前库存和版本号ProductStock stock = productMapper.queryStock(productId);// 检查库存是否足够if (stock.getStock()<=0) {System.out.println("库存不足,扣减失败");return false;}// 尝试更新库存并更新版本号if (productMapper.updateStock(productId,stock.getVersion())==0) {// 如果更新失败,说明版本号不一致,乐观锁冲突System.out.println(port+"-乐观锁冲突,扣减库存失败,可能其他线程已经修改了数据");return false;}System.out.println(port+"-库存扣减成功,剩余库存: " + (stock.getStock()-1));return true;}
}

3.2.9 ProductController.java

package org.example.controller;import org.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {@Autowiredprivate ProductService productService;@GetMapping("/order")public String order() {final int MAX_RETRIES = 3; // 设置最大重试次数for (int i = 0; i < MAX_RETRIES; i++) {boolean success = productService.reduceStock(1);if (success) {return "库存扣减成功";} else {System.out.println("第 " + (i + 1) + " 次扣减库存失败,尝试重试...");}}return "扣减库存失败,超出最大重试次数";}}

3.2.10 测试验证

前提准备:同目录3.1.12完全一致。

运行结果8001:

8001-库存扣减成功,剩余库存: 4
8001-库存扣减成功,剩余库存: 2
8001-库存扣减成功,剩余库存: 0
库存不足,扣减失败
第 1 次扣减库存失败,尝试重试…
库存不足,扣减失败
第 2 次扣减库存失败,尝试重试…
库存不足,扣减失败
第 3 次扣减库存失败,尝试重试…
库存不足,扣减失败

运行结果8002:

8002-库存扣减成功,剩余库存: 3
8002-库存扣减成功,剩余库存: 1
库存不足,扣减失败
第 1 次扣减库存失败,尝试重试…
库存不足,扣减失败
第 2 次扣减库存失败,尝试重试…
库存不足,扣减失败
第 3 次扣减库存失败,尝试重试…
库存不足,扣减失败

在这里插入图片描述

3.3 使用悲观锁(Pessimistic Lock)实现分布式锁

原理:

悲观锁(Pessimistic Lock)是一种基于“悲观”假设的锁机制,即假设每次对数据的操作都会发生并发冲突,因此在操作数据之前必须先对其进行加锁,防止其他事务或线程对数据进行并发操作。这样可以确保在加锁期间,只有获得锁的进程或线程能够访问数据,从而避免数据的不一致性。

开始事务: 当方法用@Transactional注解标记时,Spring会在方法开始时开启一个事务。

获取悲观锁:在事务内执行查询时,如果使用了悲观锁(如SELECT ... FOR UPDATE),数据库会锁定相关数据行。

事务提交:当方法执行完毕且没有发生异常时,事务提交,锁会被释放,其他等待的事务可以继续执行。

事务回滚:如果在事务期间发生了异常,事务回滚,锁也会被释放,确保数据的原子性和一致性。

CREATE TABLE product
(product_id INT PRIMARY KEY,stock      INT
);-- 初始化库存为5
INSERT INTO product (product_id, stock)
VALUES (1, 5);

3.3.1 项目结构

同:目录3.2.1项目完全一致。以下说明不同的类。

3.3.2 Product.java

package org.example.model;import lombok.Data;@Data
public class Product {private Integer productId;private Integer stock;
}

3.3.3 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.example.model.Product;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper {// 使用悲观锁获取当前库存@Select("SELECT stock FROM product WHERE product_id = #{productId} FOR UPDATE")Product queryStockWithLock(Integer productId);// 更新库存@Update("UPDATE product SET stock = stock - 1 WHERE product_id = #{productId} AND stock > 0")int updateStock(Integer productId);
}

3.3.4 ProductService.java

package org.example.service;import org.example.mapper.ProductMapper;
import org.example.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class ProductService {private final ProductMapper productMapper;@Autowiredpublic ProductService(ProductMapper productMapper) {this.productMapper = productMapper;}@Value("${server.port}")private String port;// 使用悲观锁扣减库存@Transactionalpublic boolean reduceStock(int productId) {System.out.println(port+"-开始尝试获取悲观锁,锁定库存记录...");// 使用悲观锁获取当前库存Product stock = productMapper.queryStockWithLock(productId);// 悲观锁已生效,此时其他事务无法修改当前行System.out.println(port+"-已成功获取悲观锁,库存记录已锁定。当前库存:" + stock.getStock());// 检查库存是否足够if (stock.getStock() <= 0) {System.out.println("库存不足,扣减失败");return false;}// 更新库存int result = productMapper.updateStock(productId);if (result == 0) {System.out.println("库存扣减失败");return false;}System.out.println("库存扣减成功,剩余库存: " + (stock.getStock() - 1));return true;}
}

3.3.5 测试验证

前提准备:同目录3.1.12完全一致。

运行结果8001:

8001-开始尝试获取悲观锁,锁定库存记录…
8001-已成功获取悲观锁,库存记录已锁定。当前库存:5
库存扣减成功,剩余库存: 4
8001-开始尝试获取悲观锁,锁定库存记录…
8001-已成功获取悲观锁,库存记录已锁定。当前库存:3
库存扣减成功,剩余库存: 2
8001-开始尝试获取悲观锁,锁定库存记录…
8001-已成功获取悲观锁,库存记录已锁定。当前库存:1
库存扣减成功,剩余库存: 0
8001-开始尝试获取悲观锁,锁定库存记录…

运行结果8002:

8002-开始尝试获取悲观锁,锁定库存记录…
8002-已成功获取悲观锁,库存记录已锁定。当前库存:4
库存扣减成功,剩余库存: 3
8002-开始尝试获取悲观锁,锁定库存记录…
8002-已成功获取悲观锁,库存记录已锁定。当前库存:2
库存扣减成功,剩余库存: 1
8002-开始尝试获取悲观锁,锁定库存记录…
8002-已成功获取悲观锁,库存记录已锁定。当前库存:0
库存不足,扣减失败
第 1 次扣减库存失败,尝试重试…

4.基于 Redis

4.1 安装Redis

4.1.1 拉取 Redis 官方镜像

docker pull redis:latest

4.1.2 启动 Redis 容器并设置用户名和密码

docker run -d --name redis \-p 6379:6379 \redis:latest \--requirepass "123456" #设置密码

4.1.3 验证 Redis 服务

进入redis容器

docker exec -it redis /bin/bash

通过 Redis CLI 客户端连接到 Redis 服务器:

redis-cli -h 127.0.0.1 -p 6379

在 Redis 命令行界面中手动输入密码进行验证

AUTH 123456 #验证密码

成功连接后,可以通过运行简单的 Redis 命令来验证连接是否成功:

127.0.0.1:6379> ping

如果返回 PONG,表示连接成功。

4.2 Redis 分布式锁的实现思路

Redis 分布式锁的核心在于:

  1. 获取锁:使用 SETNX 命令尝试设置锁,如果设置成功则获取锁。
  2. 设置过期时间:使用 Redis 的 EXPIRESET 命令设置锁的过期时间,避免死锁。
  3. 释放锁:业务完成后释放锁,确保只有持有锁的线程可以释放它。
CREATE TABLE product
(product_id INT PRIMARY KEY,stock      INT
);-- 初始化库存为5
INSERT INTO product (product_id, stock)
VALUES (1, 5);

4.2.1 项目结构

在这里插入图片描述

4.2.2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>lock</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.3.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

4.2.3 application.yml

spring:application:name: lockdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/lock_?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456data:redis:host: 192.168.186.77port: 6379password: 123456
mybatis:configuration:map-underscore-to-camel-case: true
server:port: 8001

4.2.4 LockApplication.java

package org.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("org.example.mapper")
public class LockApplication {public static void main(String[] args) {SpringApplication.run(LockApplication.class, args);}
}

4.2.5 RedisConfig.java

package org.example.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 使用StringRedisSerializer来序列化和反序列化redis的keytemplate.setKeySerializer(new StringRedisSerializer());// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化redis的valuetemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());// 同样设置HashKey和HashValue序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());template.afterPropertiesSet();return template;}
}

4.2.6 Product.java

package org.example.model;import lombok.Data;@Data
public class Product {private Integer productId;private Integer stock;
}

4.2.7 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper {@Select("SELECT stock FROM product WHERE product_id = #{productId} FOR UPDATE")int getStockByProductId(int productId);@Update("UPDATE product SET stock =stock-1 WHERE product_id = #{productId}")void updateProductStock(int productId);
}

4.2.8 ProductService.java

package org.example.service;
import org.example.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class ProductService {@Autowiredprivate ProductMapper productMapper;public boolean reduceStock(int productId) {// 获取当前库存int stock = productMapper.getStockByProductId(productId);if (stock > 0) {// 扣减库存productMapper.updateProductStock(productId);System.out.println( "库存扣减成功,剩余库存:" + (stock-1));return true;} else {return false;}}
}

4.2.9 RedisDistributedLockService.java

package org.example.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.UUID;
import java.util.concurrent.TimeUnit;@Service
public class RedisDistributedLockService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductService productService;private static final String LOCK_KEY_PREFIX = "distributed_lock_";@Value("${server.port}")private String port;public boolean acquireLock(String lockKey, String clientId, long expireTime) {Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId);if (Boolean.TRUE.equals(success)) {redisTemplate.expire(lockKey, expireTime, TimeUnit.SECONDS);return true;}return false;}public void releaseLock(String lockKey, String clientId) {String currentValue = (String) redisTemplate.opsForValue().get(lockKey);if (clientId.equals(currentValue)) {redisTemplate.delete(lockKey);}}public void execute() {String lockKey = LOCK_KEY_PREFIX+1;String clientId = UUID.randomUUID().toString();long expireTime = 10;try {boolean lockAcquired = acquireLock(lockKey, clientId, expireTime);if (lockAcquired) {System.out.println(port + " - 成功获取锁,执行任务。");// 扣减库存boolean success = productService.reduceStock(1); // 假设每次扣减1个库存if(!success) {System.out.println("库存不足。");}} else {System.out.println(port + " - 未能获取锁,任务已被其他节点处理。");}} finally {releaseLock(lockKey, clientId);}}
}

4.2.10 RedisDistributedLockController.java

package org.example.controller;import org.example.service.RedisDistributedLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class RedisDistributedLockController {@Autowiredprivate RedisDistributedLockService redisDistributedLockService;@GetMapping("/order")public String executeTask() {redisDistributedLockService.execute();return "任务请求已提交";}
}

4.2.11 测试验证

前提准备:同目录3.1.12完全一致。

运行结果8001:

8001 - 成功获取锁,执行任务。
库存扣减成功,剩余库存:4
8001 - 成功获取锁,执行任务。
库存扣减成功,剩余库存:2
8001 - 成功获取锁,执行任务。
库存扣减成功,剩余库存:0
8001 - 成功获取锁,执行任务。
库存不足。

运行结果8002:

8002 - 成功获取锁,执行任务。
库存扣减成功,剩余库存:3
8002 - 成功获取锁,执行任务。
库存扣减成功,剩余库存:1
8002 - 成功获取锁,执行任务。
库存不足。

4.3 Redis+悲观锁实现秒杀(适合单一节点)

4.3.1 实现原理

1. Redis

  • 目的:利用 Redis 的高性能来快速处理库存的扣减操作,减少对数据库的直接访问,从而提升系统的并发处理能力。
  • 过程:在应用启动时,将数据库中的库存数据加载到 Redis 中。秒杀时,所有的库存操作首先在 Redis 中进行,这样可以显著减少数据库的压力。

2. Redis 原子操作 (decrement)

  • 目的:确保在并发情况下,多个线程对同一个库存的扣减操作不会发生冲突,从而防止超卖。
  • 过程:每次秒杀请求到达时,直接通过 Redis 的 decrement 操作原子性地减少库存。如果库存不足(stock < 0),则直接返回失败。

3. 数据库的悲观锁

  • 目的:进一步确保数据库中的库存数据与 Redis 中的数据一致,防止并发情况下的库存不一致问题。
  • 过程:如果 Redis 中的库存扣减成功,则使用悲观锁(SELECT ... FOR UPDATE)在数据库中锁定库存行,进行库存更新。悲观锁确保在锁定的库存更新完成之前,其他事务无法修改该库存数据。

4.回滚机制

  • 目的:确保在任何异常情况下,Redis 和数据库的库存数据一致。
  • 过程:如果在数据库操作中发现库存不足,或者在执行过程中发生异常,会回滚 Redis 中的库存操作(即通过 increment 恢复 Redis 库存),并抛出异常或返回操作失败。
CREATE TABLE product
(product_id INT PRIMARY KEY,stock      INT
);-- 初始化库存为5
INSERT INTO product (product_id, stock)
VALUES (1, 5);CREATE TABLE orders
(order_id   INT PRIMARY KEY AUTO_INCREMENT,product_id INT,time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

4.3.2 项目结构

在这里插入图片描述

4.3.3 pom.xml

同:目录4.2.2完全一致。

4.3.4 application.yml

同:目录4.2.3完全一致。

4.3.5 LockApplication.java

同:目录4.2.4完全一致。

4.3.7 RedisConfig.java

同:目录4.2.5完全一致。

4.3.6 Product.java

同:目录4.2.6完全一致。

4.3.7 OrderMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;@Repository
public interface OrderMapper {@Insert("INSERT INTO orders (product_id) VALUES (#{productId})")void insertOrder(int productId);
}

4.3.8 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.example.model.Product;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface ProductMapper {@Select("SELECT * FROM product")List<Product> getAllProducts();@Select("SELECT * FROM product WHERE product_id = #{productId} FOR UPDATE")Product getStockByProductId(int productId);@Update("UPDATE product SET stock =stock-1 WHERE product_id = #{productId}")void updateProductStock(int productId);
}

4.3.9 ProductService.java

package org.example.service;import jakarta.annotation.PostConstruct;
import org.example.mapper.OrderMapper;
import org.example.mapper.ProductMapper;
import org.example.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Service
public class ProductService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductMapper productMapper;@Autowiredprivate OrderMapper orderMapper;private static final String PRODUCT_STOCK_KEY = "product_stock_";// 初始化时从数据库加载库存到 Redis@PostConstructpublic void loadProductStockToRedis() {List<Product> products = productMapper.getAllProducts();for (Product product : products) {redisTemplate.opsForValue().set(PRODUCT_STOCK_KEY + product.getProductId(), product.getStock());System.out.println("已加载商品ID:" + product.getProductId() + " 的库存到 Redis,库存为:" + product.getStock());}}@Transactionalpublic boolean reduceStock(int productId) {String stockKey = PRODUCT_STOCK_KEY + productId;// 1. 从 Redis 中扣减库存,确保原子操作Long stock = redisTemplate.opsForValue().decrement(stockKey);if (stock == null || stock < 0) {// 如果库存不足或扣减失败,回滚 Redis 库存并返回失败redisTemplate.opsForValue().increment(stockKey);System.out.println("秒杀失败!商品ID:" + productId + ",库存不足。");return false;}// 2. 使用数据库的悲观锁检查并扣减库存try {Product product = productMapper.getStockByProductId(productId);if (product.getStock() >= 1) {// 更新数据库库存productMapper.updateProductStock(productId);// 创建订单orderMapper.insertOrder(productId);System.out.println("秒杀成功!商品ID:" + productId + ",剩余库存:" + stock);return true;} else {// 如果数据库库存不足,回滚 Redis 库存并返回失败redisTemplate.opsForValue().increment(stockKey);System.out.println("秒杀失败!商品ID:" + productId + ",数据库库存不足。");return false;}} catch (Exception e) {// 发生异常时回滚 Redis 库存并抛出异常redisTemplate.opsForValue().increment(stockKey);throw e;}}
}

4.3.10 ProductController.java

package org.example.controller;import org.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {@Autowiredprivate ProductService productService;@GetMapping("/order")public String order() {boolean success = productService.reduceStock(1);return success ? "秒杀成功" : "秒杀失败";}}

4.3.12 测试验证

前提准备:修改商品的stock(余额)为10,进行秒杀模拟。

运行结果:

已加载商品ID:1 的库存到 Redis,库存为:10
秒杀成功!商品ID:1,剩余库存:3
秒杀成功!商品ID:1,剩余库存:8
秒杀成功!商品ID:1,剩余库存:0
秒杀成功!商品ID:1,剩余库存:1
秒杀成功!商品ID:1,剩余库存:5
秒杀成功!商品ID:1,剩余库存:6
秒杀成功!商品ID:1,剩余库存:7
秒杀失败!商品ID:1,库存不足。
秒杀成功!商品ID:1,剩余库存:9
秒杀失败!商品ID:1,库存不足。
秒杀成功!商品ID:1,剩余库存:2
秒杀失败!商品ID:1,库存不足。
秒杀成功!商品ID:1,剩余库存:4
秒杀失败!商品ID:1,库存不足。

在这里插入图片描述
在这里插入图片描述

说明:订单是并发下的,同时抢购,同时产品表没有出现超卖。

5. 基于 Zookeeper

5.1 安装 Zookeeper

5.1.1 拉取 Zookeeper Docker 镜像

docker pull zookeeper

5.1.2 运行 Zookeeper 容器

docker run -d --name zookeeper -p 2181:2181 zookeeper

5.1.3 验证是否启动成功

docker exec -it zookeeper /bin/bash

5.1.4 连接到Zookeeper 客户端

zkCli.sh -server localhost:2181

5.2 分布式锁

​ Zookeeper 是一个分布式协调服务,它为分布式系统提供了一种强一致性的机制。Zookeeper 集群中的所有节点(通常为奇数个)通过一致性算法(如 ZAB 协议)来保证数据的一致性和可靠性。
临时节点(Ephemeral Node):临时节点是 Zookeeper 的一种特殊节点,它在客户端会话有效期间存在,一旦客户端断开连接(例如崩溃或超时),临时节点将自动删除。临时节点的特性确保了锁在客户端失效后能够被自动释放,从而避免了死锁的发生。
顺序节点(Sequential Node):顺序节点是在创建节点时,Zookeeper 会在节点名称后附加一个全局递增的序号。每次请求创建顺序节点时,Zookeeper 会生成一个具有唯一序号的节点。通过顺序节点,可以为多个客户端竞争锁的请求排序,实现公平锁。

步骤

  1. 创建锁节点:客户端尝试在 Zookeeper 的某个路径下创建一个带有唯一序号的临时顺序节点(如 /locks/lock-000000001)。
  2. 判断锁的持有者:客户端获取当前所有节点的列表,判断自己创建的节点是否是序号最小的节点。如果是,则认为自己获取到了锁;如果不是,则监听比自己序号小的节点(即前一个节点)的删除事件。
  3. 等待锁释放:如果当前节点不是序号最小的节点,客户端会进入等待状态,直到它监听的前一个节点被删除。当前一个节点被删除时,客户端重新检查自己是否是序号最小的节点,如果是,则获取锁。
  4. 释放锁:当客户端完成对共享资源的操作后,删除它创建的临时顺序节点,从而释放锁。
  5. 通知下一个客户端:锁的释放会触发下一个序号节点的监听事件,该客户端随即尝试获取锁并执行相应操作。

5.3 简单案例

CREATE TABLE product
(product_id INT PRIMARY KEY,stock      INT
);-- 初始化库存为5
INSERT INTO product (product_id, stock)
VALUES (1, 5);

5.3.1 项目结构

在这里插入图片描述

5.3.2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>lock</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.3.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.3.0</version></dependency><!-- Zookeeper client --><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.7.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

说明:选择合适的版本zookeeper依赖。

5.3.3 application.yml

spring:application:name: lockdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/lock_?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456
mybatis:configuration:map-underscore-to-camel-case: true
zookeeper:connect-string: 192.168.186.77:2181session-timeout-ms: 5000connection-timeout-ms: 3000retry:base-sleep-time-ms: 1000max-retries: 3
server:port: 8001

5.3.4 LockApplication.java

package org.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("org.example.mapper")
public class LockApplication {public static void main(String[] args) {SpringApplication.run(LockApplication.class, args);}
}

5.3.5 ZookeeperConfig.java

package org.example.config;import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ZookeeperConfig {@Value("${zookeeper.connect-string}")private String connectString;@Value("${zookeeper.session-timeout-ms}")private int sessionTimeoutMs;@Value("${zookeeper.connection-timeout-ms}")private int connectionTimeoutMs;@Value("${zookeeper.retry.base-sleep-time-ms}")private int baseSleepTimeMs;@Value("${zookeeper.retry.max-retries}")private int maxRetries;@Beanpublic CuratorFramework curatorFramework() {CuratorFramework client = CuratorFrameworkFactory.newClient(connectString,sessionTimeoutMs,connectionTimeoutMs,new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries));client.start();return client;}
}

5.3.6 Product.java

package org.example.model;import lombok.Data;@Data
public class Product {private Integer productId;private Integer stock;
}

5.3.7 ProductMapper.java

package org.example.mapper;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;@Repository
public interface ProductMapper {@Select("SELECT stock FROM product WHERE product_id = #{productId} FOR UPDATE")int getStockByProductId(int productId);@Update("UPDATE product SET stock =stock-1 WHERE product_id = #{productId}")void updateProductStock(int productId);
}

5.3.8 ProductService.java

package org.example.service;import org.example.mapper.ProductMapper;
import org.example.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class ProductService {@Autowiredprivate ProductMapper productMapper;public void reduceStock(int productId) {// 获取当前库存int stock = productMapper.getStockByProductId(productId);if (stock > 0) {// 扣减库存productMapper.updateProductStock(productId);System.out.println("库存扣减成功,剩余库存:" + (stock-1));} else {System.out.println("库存不足。");}}
}

5.3.9 ZookeeperLockService.java

package org.example.service;import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class ZookeeperLockService {private final CuratorFramework client;@Autowiredpublic ZookeeperLockService(CuratorFramework client) {this.client = client;}/*** 获取分布式锁** @param lockPath  锁路径* @param timeout   获取锁的超时时间* @param timeUnit  时间单位* @return 返回锁实例,如果成功获取锁,否则返回null*/public InterProcessMutex acquireLock(String lockPath, long timeout, TimeUnit timeUnit) {InterProcessMutex lock = new InterProcessMutex(client, lockPath);try {if (lock.acquire(timeout, timeUnit)) {return lock; // 返回成功获取锁的实例}} catch (Exception e) {e.printStackTrace();}return null; // 如果未能获取锁,返回null}/*** 释放分布式锁** @param lock 锁实例*/public void releaseLock(InterProcessMutex lock) {if (lock != null) {try {lock.release();System.out.println("锁释放成功!");} catch (Exception e) {e.printStackTrace();}}}
}

说明:InterProcessMutex 是 Apache Curator 库中提供的一种实现分布式锁的工具类,它基于 Zookeeper 来实现锁的互斥性。InterProcessMutex 提供了一种跨进程、跨节点的锁机制,确保在分布式环境中,同一时间只有一个客户端能够获得锁,其他客户端需要等待该锁被释放后才能继续操作。

5.3.10 LockController.java

package org.example.controller;import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.example.service.ProductService;
import org.example.service.ZookeeperLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
public class LockController {@Autowiredprivate ZookeeperLockService zookeeperLockService;@Autowiredprivate ProductService productService;@Value("${server.port}")private String port;@GetMapping("/order")public void executeTaskWithLock() {int productId = 1;String lockPath = "/zk-lock/" + productId;InterProcessMutex lock = zookeeperLockService.acquireLock(lockPath, 5, TimeUnit.SECONDS);if (lock != null) {try {System.out.println(port+"-成功获取锁");productService.reduceStock(1);} finally {// 释放锁zookeeperLockService.releaseLock(lock);System.out.println(port+"-成功释放锁");}} else {System.out.println(port+"-未能获取锁,任务已被其他节点处理");}}
}

5.3.11 测试验证

前提准备:同目录3.1.12完全一致。

运行结果8001:

8001-成功获取锁
库存扣减成功,剩余库存:4
锁释放成功!
8001-成功释放锁
8001-成功获取锁
库存扣减成功,剩余库存:2
锁释放成功!
8001-成功释放锁
8001-成功获取锁
库存扣减成功,剩余库存:0
锁释放成功!
8001-成功释放锁

运行结果8002:

8002-成功获取锁
库存扣减成功,剩余库存:3
锁释放成功!
8002-成功释放锁
8002-成功获取锁
库存扣减成功,剩余库存:1
锁释放成功!
8002-成功释放锁
8002-成功获取锁
库存不足。

6. 总结

​ 简单的模拟包括基于数据库的乐观锁、悲观锁、以及利用数据库的唯一性约束的分布式锁,以及基于Redis、Zookeeper的分布式锁,仅供学习参考。

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

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

相关文章

等保测评服务的业务连续性规划:确保信息安全服务的韧性

在当前的数字化转型浪潮中&#xff0c;信息安全已成为企业运营的关键一环。等保测评服务作为信息安全合规的重要组成部分&#xff0c;其业务连续性规划对于保障服务的稳定性和客户信息资产的安全至关重要。本文将探讨等保测评服务的业务连续性规划策略&#xff0c;旨在构建一个…

树状数组算法

文章目录 树状数组是什么树状数组与线段树的区别与联系树状数组讲解点修&#xff0c;区查&#xff0c;讲解及模板点查&#xff0c;区修讲解及模板 树状数组是什么 树状数组是一种数据结构&#xff0c;提供O(logn)时间内的单点修改和区间求和操作&#xff0c;比线段树有更优的常…

MD编辑器学习笔记

MD编辑器学习笔记 目录标题文本样式列表图片链接代码片数学公式表格部分总结 目录 目录是使用“[TOC](目录&#xff09;”&#xff0c;记住别忘了加上&#xff08;&#xff09;标题 使用#来确定标题&#xff0c;几个#就是几级标题。记住#后面要加上空格文本样式 tips: 在写正…

物流抓取机器人整体设计方案

一、功能简介 1、运行环境&#xff1a;巡线行驶&#xff08;7路数字循迹&#xff0c;麦克纳姆轮车底盘&#xff09; 2、目标识别&#xff1a;颜色识别&#xff08;Maix-II Dock 视觉模块&#xff09; 3、目标定位&#xff1a;视觉测距&#xff08;Maix-II Dock 视觉模块&#x…

VS实⽤调试技巧(附调试例题)

&#x1f381;&#x1f381;创作不易&#xff0c;关注作者不迷路&#x1f380;&#x1f380; VS实⽤调试技巧&#xff08;附调试例题&#xff09; 前言一、什么是bug&#xff1f;二、什么是调试&#xff08;debug&#xff09;&#xff1f;三、Debug和Release四、VS调试快捷键五…

上书房信息咨询:商业项目调研方法有哪些

商业项目调研是为了了解市场需求、竞争情况和目标受众&#xff0c;从而制定合适的商业策略和项目规划。下面是一些常用的商业项目调研方法&#xff1a; 1、市场调查和问卷调查&#xff1a;通过设计和分发问卷&#xff0c;收集潜在顾客和目标市场的意见、偏好和需求。这可以帮助…

linux dig域名DNS 查询与iptables域名ip访问流量限制;PTR 反向解析从 IP 地址到域名的映射

一、域名 dns查询 在 Linux 系统中&#xff0c;你可以使用多种工具和技术来进行 DNS 查询和 IP 限制。以下是一些常用的方法和工具&#xff1a; DNS 查询 dig 命令&#xff1a; dig 是一个强大的命令行工具&#xff0c;用于查询 DNS 信息。 dig example.com你可以指定查询类型…

dockerfile搭建部署LNMP

目录 实验 架构&#xff1a; 实验步骤&#xff1a; nginx部分 mysql部分 php部分 实验 实验&#xff1a;用dockerfile搭建LNMP论坛 架构&#xff1a; 一台docker虚拟机 docker部署nginx 1.22 指定ip地址172.111.0.10 docker部署mysql 8.0.30 指定ip地址…

标准库标头 <execution> (C++17)学习

此头文件是算法库的一部分。本篇介绍策略类型的一些应用示例&#xff1a; 类 is_execution_policy (C17) 测试一个类是否表示某种执行策略 (类模板) 在命名空间 std::execution 定义 sequenced_policyparallel_policyparallel_unsequenced_policyunsequenced_policy (C17)(C1…

外排序之文件归并排序实现

外排序介绍 外排序是指能够处理极大量数据的排序算法。通常来说&#xff0c;外排序处理的数据不能一次装入内存&#xff0c;只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采用的是⼀种“排序-归并”的策略。在排序阶段&#xff0c;先读入能放在内存中的数据量&#x…

ACCESS 工具注入实战 凡诺靶场

简介 Access数据库注入攻击是一种常见的网络安全&#xff0c;通过注入SQL代码来获取未授权的数据访问权限。这种攻击利用了应用程序与数据库之间的交互漏洞&#xff0c;攻击者通过输入特定的SQL代码片段来操纵数据库查询&#xff0c;从而绕过应用程序的安全机制&#xff0c;获取…

基于x86 平台opencv的图像采集和seetaface6的人脸特征点功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.2 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的人脸特征点功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的人脸特征点模块从而…

Selenium + Python 自动化测试19(补充-读取各种文件数据操作)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了数据驱动测试中如何完成重复的测试实例&#xff0c;今天我们补充一些读取各种文件的方法。 本篇文章我们讨论一下如何使用读取txt、CSV、Excel文件&#xff0…

多门店多端平台系统小程序源码

&#x1f525;【健身新纪元】多门店多端分开健身系统&#xff0c;打造你的个性化健身体验&#x1f31f; &#x1f3cb;️‍♀️ 一、告别单一&#xff0c;拥抱多元化健身时代 你还在为找不到合适的健身房而烦恼吗&#xff1f;或是想要随时随地开启健身模式&#xff0c;却受限…

xss靶场 pwnfunction WW3

目录 代码 代码分析 payload构造 结果 代码 <div><h4>Meme Code</h4><textarea class"form-control" id"meme-code" rows"4"></textarea><div id"notify"></div> </div><scri…

ubuntu上cmake3.30.2的安装

引言 安装下载安装包将安装包从windows拷贝到ubuntu解压进入解压后的文件夹执行boostrap编译CMake安装CMake查看是否安装成功 目前的ubuntu系统是20.04.4&#xff0c;用命令行安装了cmake的版本是3.16的&#xff0c;由于项目需要升级cmake到cmake3.22之上&#xff0c;使用命令行…

如何使用cornerstone3D渲染3D影像

&#x1f353; 前言 在日常开发中经常会遇到除了渲染MPR视图外&#xff0c;还需要渲染3D扫描影像&#xff0c;本文从具体的实现、viewport type解读、场景预设等方面来介绍3D具体的实现及涉及到的相关概念。 &#x1f951; 效果演示 点击查看完整代码 &#x1f352; 实现及概…

详细分析 el-progress的基本知识以及用法(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 由于实战项目中有所引用&#xff0c;对此记录基本的知识点&#xff0c;并且以Demo的形式呈现 1. 基本知识 el-progress 是 Element Plus UI 库中的一个进度条组件&#xff0c;用于显示任务的完成情况 可以帮助用户了解某个操作或任…

移动云电脑手机端3.0全新升级,畅享个人便捷管理,筑牢安全管控防线

在当今数字化飞速发展的时代&#xff0c;企业对于高效、便捷且安全的办公模式需求日益迫切。移动云电脑手机端3.0全新升级&#xff0c;迎来科技领域的又一突破。其基于企业管理平台为企业客户提供管理能力&#xff0c;实现对用户、终端、资源的全方位集中管控&#xff0c;助力提…

探索GitLab:从搭建到高效使用的实用指南

企业里为什么喜欢使用GitLab 一、GitLab简介二、搭建GitLab三、GitLab的权限管理3.1、用户注册3.2、创建用户组3.3、为用户组添加用户3.4、为工程添加访问权限 四、GitLab的code review五、团队知识管理六、总结 一、GitLab简介 GitLab是利用 Ruby on Rails 一个开源的版本管理…