【商城实战(93)】商城高并发实战:分布式锁与事务处理深度剖析

devtools/2025/4/1 5:27:18/

商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录


一、引言

在当今电商行业蓬勃发展的背景下,商城系统面临着前所未有的高并发挑战。大量用户同时涌入商城进行商品浏览、下单、支付等操作,这对系统的稳定性和数据一致性提出了极高的要求。分布式锁与事务处理作为应对高并发场景的关键技术手段,对于保障商城系统的正常运行起着至关重要的作用。如果不能妥善处理高并发下的资源竞争和跨服务事务问题,将会导致数据不一致、超卖、订单状态混乱等一系列严重影响用户体验和商城运营的问题。因此,深入理解并合理运用分布式锁与事务处理技术,是打造高性能、高可靠商城系统的核心要点。

二、分布式锁在高并发下的应用

2.1 高并发下的资源竞争问题

高并发场景中,多个请求同时访问共享资源时,资源竞争问题极易出现。以商城商品库存扣减为例,当一款热门商品进行促销活动时,大量用户可能同时发起购买请求。如果系统没有采取有效的并发控制措施,就可能出现多个线程同时读取到相同的库存数量,然后各自进行扣减操作,最终导致库存数量出现负数,即超卖现象。这不仅会给商城带来经济损失,还会严重影响用户体验,损害商城的信誉。

2.2 Redis 分布式锁原理

Redis 分布式锁是基于 Redis 的单线程特性和原子操作来实现的。其核心原理是利用SETNX(SET if Not eXists)命令,该命令只有在键不存在时才会设置键的值。当一个客户端尝试获取分布式锁时,它会向 Redis 发送一个SETNX命令,若返回值为1,表示获取锁成功,因为此时锁对应的键不存在;若返回值为0,则表示锁已被其他客户端获取,获取锁失败。同时,为了防止锁被意外持有而无法释放,通常会给锁设置一个过期时间。例如,在一个典型的商品秒杀场景中,多个客户端同时竞争获取商品库存扣减的锁,只有获取到锁的客户端才能执行库存扣减操作,其他客户端则需要等待或重试。

2.3 Spring Boot 集成 Redis 实现分布式

  1. 添加依赖:在 Spring Boot 项目的pom.xml文件中添加spring-boot-starter-data-redis依赖,代码如下:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置连接信息:在application.properties文件中配置 Redis 服务器的地址、端口等信息,示例如下:
spring.redis.host=your - redis - host
spring.redis.port=6379
spring.redis.password=your - password
  1. 实现分布式锁代码:创建一个工具类RedisLockUtil,使用RedisTemplate实现获取和释放分布式锁的功能。核心代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;@Component
public class RedisLockUtil {@Autowiredprivate RedisTemplate<String, String> redisTemplate;public boolean tryLock(String lockKey, String requestId, long expireTime) {return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);}public void unlock(String lockKey, String requestId) {String value = redisTemplate.opsForValue().get(lockKey);if (requestId.equals(value)) {redisTemplate.delete(lockKey);}}
}

在商品秒杀业务代码中使用分布式锁,示例如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;@RestController
@RequestMapping("/seckill")
public class SeckillController {@Autowiredprivate RedisLockUtil redisLockUtil;@PostMapping("/product/{productId}")public String seckillProduct(@PathVariable Long productId) {String lockKey = "seckill:product:" + productId;String requestId = UUID.randomUUID().toString();boolean locked = redisLockUtil.tryLock(lockKey, requestId, 10);if (locked) {try {// 执行商品秒杀业务逻辑,如扣减库存等return "秒杀成功";} finally {redisLockUtil.unlock(lockKey, requestId);}} else {return "秒杀失败,商品已售罄或系统繁忙";}}
}

三、分布式事务处理

3.1 跨服务事务问题

在商城系统中,创建订单这一常见业务操作通常涉及多个服务间的协同工作。例如,当用户下单时,订单服务需要创建订单记录,库存服务需要扣减相应商品的库存,账户服务需要扣减用户的余额。这些操作必须作为一个整体要么全部成功,要么全部失败,以保证数据的一致性。然而,由于这些服务可能部署在不同的服务器上,且网络通信存在不确定性,传统的本地事务无法满足这种跨服务的事务需求。如果在创建订单过程中,订单服务成功创建了订单记录,但库存服务扣减库存失败,而此时订单状态已经更新为已创建,就会导致数据不一致,用户可能会收到订单创建成功但商品无货的情况,严重影响用户体验和商城的正常运营。

3.2 Seata 分布式事务框架介绍

  1. Seata 组件与原理:Seata 是一款开源的分布式事务解决方案,它由三个核心组件组成:事务协调器(TC)、事务管理器(TM)和资源管理器(RM)。其工作原理如下:当一个分布式事务开始时,TM 向 TC 申请开启一个全局事务,并获取一个全局唯一的事务 ID(XID)。在各个服务的业务操作中,RM 负责向 TC 注册分支事务,并将本地事务的执行结果汇报给 TC。当所有分支事务都执行完毕后,TM 根据所有分支事务的执行情况向 TC 发起全局事务的提交或回滚请求。TC 根据全局事务的状态协调各个 RM 完成最终的事务提交或回滚操作,从而保证分布式事务的一致性。
  2. Seata 与 MyBatis-plus 集成:在 Spring Boot 项目中,首先需要在pom.xml文件中引入 Seata 和 MyBatis - plus 相关依赖,示例如下:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring - cloud - alibaba - seata</artifactId>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis - plus - boot - starter</artifactId>
</dependency>

然后,在application.properties文件中添加 Seata 的配置信息,如事务协调器地址等,示例如下:

spring.application.name=mall - service
seata.tx - service - group=mall_tx_group
seata.service.vgroup - mapping.mall_tx_group=default
seata.config.type=file
seata.config.file.conf - file - path=classpath:seata/registry.conf
seata.registry.type=file
seata.registry.file.filename=classpath:seata/file.conf

3.3 Seata 管理分布式事务示例

  1. 业务场景模拟:假设商城数据库中有三张表,分别是order_info(订单表)、product_stock(商品库存表)和user_account(用户账户表)。表结构如下:
CREATE TABLE order_info (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,product_id BIGINT NOT NULL,order_amount DECIMAL(10, 2) NOT NULL,order_status INT NOT NULL
);CREATE TABLE product_stock (id BIGINT AUTO_INCREMENT PRIMARY KEY,product_id BIGINT NOT NULL,stock INT NOT NULL
);CREATE TABLE user_account (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,balance DECIMAL(10, 2) NOT NULL
);

初始化数据如下:

INSERT INTO product_stock (product_id, stock) VALUES (1, 100);
INSERT INTO user_account (user_id, balance) VALUES (1, 1000.00);
  1. 代码实现:在订单服务中,创建订单并调用库存服务和账户服务的关键代码如下:
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockService stockService;@Autowiredprivate AccountService accountService;@GlobalTransactional@Transactionalpublic void createOrder(Long userId, Long productId, BigDecimal orderAmount) {// 创建订单记录OrderInfo orderInfo = new OrderInfo();orderInfo.setUserId(userId);orderInfo.setProductId(productId);orderInfo.setOrderAmount(orderAmount);orderInfo.setOrderStatus(0);orderMapper.insert(orderInfo);// 调用库存服务扣减库存stockService.reduceStock(productId, 1);// 调用账户服务扣减用户余额accountService.reduceBalance(userId, orderAmount);}
}

库存服务扣减库存代码如下:

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class StockService {@Autowiredprivate StockMapper stockMapper;@Transactionalpublic void reduceStock(Long productId, int quantity) {Stock stock = stockMapper.selectById(productId);if (stock.getStock() >= quantity) {stock.setStock(stock.getStock() - quantity);stockMapper.updateById(stock);} else {throw new RuntimeException("库存不足");}}
}

账户服务扣减用户余额代码如下:

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;@Service
public class AccountService {@Autowiredprivate AccountMapper accountMapper;@Transactionalpublic void reduceBalance(Long userId, BigDecimal amount) {Account account = accountMapper.selectById(userId);if (account.getBalance().compareTo(amount) >= 0) {account.setBalance(account.getBalance().subtract(amount));accountMapper.updateById(account);} else {throw new RuntimeException("余额不足");}}
}

四、性能优化

4.1 分布式锁性能优化

  1. 减少锁持有时间:在业务代码中,应尽量优化逻辑,将需要在锁保护下执行的代码块缩到最小。例如,在商品秒杀场景中,对于一些非关键的业务逻辑,如记录日志、发送通知等,可以在获取锁之前或释放锁之后异步执行,而不是在锁持有期间同步执行。这样可以显著减少锁的持有时间,提高系统并发性能,让更多的请求能够及时获取锁并执行核心业务操作。
  2. 锁粒度控制:合理控制锁的粒度至关重要。以商城的商品管理为例,如果对整个商品模块进行全局加锁,当一个用户对某一个商品进行库存更新时,其他用户对所有商品的操作都将被阻塞,严重影响系统并发性能。而如果将锁粒度细化到单个商品,即每个商品对应一个锁,那么不同用户对不同商品的操作就可以并行进行,大大提高了系统的并发处理能力。但需要注意的是,锁粒度过细也可能会带来锁的管理成本增加等问题,因此需要根据实际业务场景进行权衡。

4.2 分布式事务性能优化

  1. 优化事务隔离级别:不同的事务隔离级别对性能有着不同程度的影响。在商城业务场景中,应根据具体业务需求选择合适的事务隔离级别。例如,对于一些读多写少且对数据一致性要求不是特别高的业务,如商品浏览统计等,可以选择较低的事务隔离级别,如READ_COMMITTED,以减少事务之间的锁竞争,提高系统并发性能。而对于涉及资金交易、库存扣减等对数据一致性要求极高的业务,则需要选择较高的事务隔离级别,如SERIALIZABLE,虽然会在一定程度上降低并发性能,但能确保数据的绝对一致性。
  2. 异步处理与批量操作:在分布式事务中,采用异步处理和批量操作可以有效减少事务执行时间,提高系统吞吐量。例如,在订单创建成功后,发送订单确认邮件、更新用户积分等操作可以异步执行,而不是在事务中同步等待这些操作完成。同时,对于一些批量操作,如批量更新库存、批量创建订单记录等,应尽量避免逐条操作,而是采用批量 SQL 语句或批量处理框架,减少数据库交互次数,从而提高事务执行效率。

五、总结与展望

通过合理应用 Redis 分布式锁解决高并发下的资源竞争问题,以及采用 Seata 分布式事务框架处理跨服务事务,能够有效提升商城系统在高并发场景下的稳定性和数据一致性。同时,通过对分布式锁和事务处理性能的优化,如减少锁持有时间、控制锁粒度、优化事务隔离级别、采用异步处理和批量操作等措施,可以进一步提高系统的并发处理能力和运行效率。随着技术的不断发展,未来分布式锁和事务处理技术将不断演进,例如更高效的分布式锁算法、更智能的分布式事务协调机制等,我们需要持续关注并探索新的技术应用,以不断提升商城系统的性能和用户体验。


http://www.ppmy.cn/devtools/172224.html

相关文章

2025年渗透测试面试题总结-某快手-安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 快手-安全工程师 一、SQL注入漏洞类型及利用技术扩展 1. SQL注入漏洞分类 2. SQL注入写入Web Shell的…

数据库基础之DDLDML

目录 一.SQL分类 二.DDL 2.1 数据库操作 2.2 表操作 2.2.1 表操作-数据类型(基于MYSQL) 2.2.2 表操作-修改 2.2.3 表操作-删除 三.DML 3.1 添加数据 3.2 修改数据 3.3 删除数据 一.SQL分类 分类全称说明DDLData Definition Language 数据定义语言&#xff0c;用来定…

个人学习编程(3-29) leetcode刷题

最后一个单词的长度&#xff1a; 思路&#xff1a;跳过末尾的空格&#xff0c;可以从后向前遍历 然后再利用 while(i>0 && s[i] ! ) 可以得到字符串的长度&#xff0c; int lengthOfLastWord(char* s) {int length 0;int i strlen(s) - 1; //从字符串末尾开始//…

git在实践使用中的操作流程

更新git管理的代码仓的流程应该是这样的&#xff0c; 1. git pull 拉最新代码&#xff0c;减少修改之后冲突的可能性&#xff0c;比如别人修改了同一个文件&#xff0c;你也修改这个文件&#xff0c;如果你没有git pull拉下来他最新的修改版本的话&#xff0c;不在他改过的基…

springboot 四层架构之间的关系整理笔记一

‌1. 控制层&#xff08;Controller&#xff09;—— 像工厂的前台接待员‌ 就像你去玩具工厂订玩具&#xff0c;前台接待员负责收你的订单&#xff0c;然后把做好的玩具交给你。控制层就是专门和用户打招呼的部门&#xff0c;负责接收用户的请求&#xff08;比如点击按钮&…

数据库同步中间件PanguSync:如何跳过初始数据直接进行增量同步

某些用户在使用数据库同步中间件PanguSync时说&#xff0c;我不想进行初次的全量同步&#xff0c;我已经源备份还原到目标库了&#xff0c;两边初始数据一样&#xff0c;想跳过初始数据&#xff0c;直接进行增量同步&#xff0c;该怎么设置。 直接上干货&#xff0c;按如下步骤…

算法 | 小龙虾优化算法原理,引言,公式,算法改进综述,应用场景及matlab完整代码

小龙虾优化算法(Crayfish Optimization Algorithm, COA)详解 一、引言 背景与意义 小* 龙虾优化算法(COA)是一种受小龙虾自然行为启发的元启发式算法,模拟其温度适应、洞穴选择、觅食竞争等机制,用于解决复杂优化问题。相比传统算法(如遗传算法、粒子群优化),COA通过…

SAP-ABAP:SAP数据集成全场景技术指南(BAPI、RFC、IDOC、BATCHJOB、ODATA、WEBSERVICE):从实时交互到批量处理

SAP数据集成全场景技术指南:从实时交互到批量处理 #mermaid-svg-hpPMerJYUerla0BJ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-hpPMerJYUerla0BJ .error-icon{fill:#552222;}#mermaid-svg-hpPMerJYUerla0BJ .er…