ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

移动电商平台核心源码拆解速查手册

移动电商平台核心源码拆解速查手册 移动电商平台核心源码拆解速查手册 面试被问“高并发下单怎么保证库存不超卖”,你答不上来?别慌,这不仅是业务逻辑,更是底层源码设计的体现。很多开发者只知调用接口,不知底层如何扣减库存、如何防止并发冲突。今天这份速查手册,直接拆解主流移动电商平台(如 Spring Cloud Alibaba 生态)的核心源码,让你从“只会用”变成“懂原理”。 入口定位:从请求到库存扣减的全链路 在移动电商平台中,用户点击“立即购买”只是冰山一角。真正的战场在微服务内部的库存服务(Stock Service)。我们以常见的基于 Redis + Lua 脚本的方案为例,梳理从 Controller 到 Redis 操作的全链路。 当请求到达网关,经过鉴权、限流后,被路由至订单服务。订单服务并不直接操作数据库,而是先调用库存服务的 decr 接口。这里的核心在于:库存预热与原子性扣减。 为什么不用数据库直接 update set stock = stock - 1 where id = ? and stock 0? 因为数据库锁粒度粗,性能瓶颈明显。而 Redis 是单线程模型,通过 Lua 脚本执行扣减,保证了原子性和高性能。 关键点:预热: 应用启动时,将数据库中的库存数据加载到 Redis,避免缓存击穿。 原子性: Lua 脚本在 Redis 中是原子执行的,避免了“检查”和“扣减”之间的竞态条件。核心片段:Redis Lua 脚本的原子扣减 下面是一段典型的库存扣减 Lua 脚本,它通常被封装在库存服务的 RedisStockService 中。 -- KEYS[1]: 商品ID对应的Redis Key, 例如 stock:1001 -- ARGV[1]: 扣减数量, 通常为 1-- 1. 检查 key 是否存在 if redis.call(exists, KEYS[1]) ~= 1 thenreturn 0 -- 商品不存在或已下架 end-- 2. 获取当前库存 local stock = redis.call(get, KEYS[1])-- 3. 判断库存是否充足 if tonumber(stock) tonumber(ARGV[1]) thenreturn 0 -- 库存不足,返回失败 end-- 4. 执行扣减 redis.call(decrby, KEYS[1], ARGV[1])-- 5. 返回成功 return 1逐行注释与设计思想:redis.call(exists, KEYS[1]):先检查 Key 是否存在。这是为了处理缓存失效或商品下架的情况。如果不存在,直接返回 0,避免后续报错。 redis.call(get, KEYS[1]):获取当前库存值。注意,这里在 Lua 脚本内部执行,意味着在“获取”和“判断”之间,没有任何其他客户端能插入操作。 tonumber(stock) tonumber(ARGV[1]):将字符串转换为数字进行比较。Lua 中的 get 返回的是字符串,必须转换后才能做数值比较。如果库存小于请求数量,返回 0,表示扣减失败。 redis.call(decrby, KEYS[1], ARGV[1]):只有当库存充足时,才执行原子扣减。decrby 是 Redis 的原子命令,确保扣减过程的不可中断性。 return 1:返回 1 表示扣减成功。Java 层接收到 1 后,才会继续创建订单;接收到 0 则直接返回“库存不足”。为什么这样设计? 这正是为了解决高并发下的竞态条件。如果先 get 判断,再 decr,两个请求可能同时读到 stock=1,都判断为充足,然后都执行 decr,导致 stock=-1。而 Lua 脚本将整个逻辑打包成一个原子操作,彻底杜绝了这种可能性。 进阶技巧:数据库最终一致性与补偿机制 Redis 扣减成功后,订单服务会尝试插入订单表。但如果此时数据库宕机,或网络超时,订单没创建成功,Redis 里的库存就“丢”了。这就是典型的分布式事务问题。 移动电商平台通常采用最终一致性策略:本地消息表: 订单服务在本地事务中同时写入订单表和消息表。 异步同步: 定时任务扫描消息表,将消息发送到 MQ(如 RocketMQ/Kafka)。 库存回滚: 如果订单创建失败,发送“库存回滚”消息;如果订单创建成功,发送“库存确认”消息。避坑指南:不要依赖 @Transactional 跨服务: Spring 的 @Transactional 只能保证本地数据库事务一致性,跨服务调用必须用 TCC、Saga 或本地消息表。 幂等性设计: 消息可能重复消费,库存回滚接口必须幂等。例如,使用唯一订单号作为 Key,记录回滚状态,避免重复回滚导致库存增加。手写简化版:单机环境下的库存服务 为了加深理解,我们手写一个简化的单机版库存服务,模拟 Redis + 数据库的协作。 import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger;public class SimpleStockService {// 模拟 Redis 缓存private final ConcurrentHashMapLong, AtomicInteger redisCache = new ConcurrentHashMap();// 模拟数据库private final ConcurrentHashMapLong, Integer dbStock = new ConcurrentHashMap();public SimpleStockService() {// 初始化:将数据库库存加载到缓存dbStock.put(1001L, 100);dbStock.put(1002L, 50);for (Long id : dbStock.keySet()) {redisCache.put(id, new AtomicInteger(dbStock.get(id)));}}/*** 扣减库存* @param productId 商品ID* @param quantity 数量* @return true: 扣减成功, false: 库存不足或商品不存在*/public boolean decrementStock(Long productId, int quantity) {// 1. 获取缓存中的库存对象AtomicInteger stock = redisCache.get(productId);if (stock == null) {return false; // 商品不存在}// 2. 原子扣减:compareAndSet 实现 CAS 操作int current;do {current = stock.get();if (current quantity) {return false; // 库存不足}} while (!stock.compareAndSet(current, current - quantity));// 3. 异步同步数据库(此处简化为同步,实际生产中应为异步MQ)syncToDb(productId, quantity);return true;}/*** 同步到数据库*/private void syncToDb(Long productId, int quantity) {// 模拟数据库更新Integer dbCurrent = dbStock.get(productId);if (dbCurrent != null) {dbStock.put(productId, dbCurrent - quantity);}// 实际项目中,这里会发送 MQ 消息,由消费者更新数据库// 并处理失败重试、幂等性校验等} }代码解析:ConcurrentHashMap + AtomicInteger:在单机环境下,AtomicInteger 的 compareAndSet (CAS) 操作模拟了 Redis Lua 脚本的原子性。它通过硬件层面的原子指令,保证“读-改-写”过程的线程安全。 do-while 循环:这是 CAS 操作的典型写法。如果 compareAndSet 失败(即其他线程修改了值),则重新读取最新值并重试。 异步同步:注释中提到的 MQ 异步同步,是生产环境的关键。直接同步数据库会拖慢响应速度,且容易因数据库故障导致整体不可用。应用场景与面试实战 这套源码设计思想,不仅适用于电商,也适用于秒杀系统、票务预订、优惠券发放等高并发场景。 面试高频问题:Q: 为什么用 Lua 脚本而不是直接调用 Redis 命令? A: 为了原子性。如果分步调用 get 和 decr,中间可能被其他请求插入,导致超卖。Lua 脚本在 Redis 中是原子执行的。 Q: Redis 库存和数据库库存不一致怎么办? A: 采用最终一致性。Redis 扣减成功后,通过 MQ 异步更新数据库。如果数据库更新失败,通过重试机制或人工介入补偿。同时,定期校对 Redis 与数据库的库存,确保数据一致性。 Q: 如何防止恶意用户频繁请求? A: 在网关层做限流(如 Sentinel),在业务层做用户维度的频控(如 Redis 记录用户请求次数)。速查要点总结:原子扣减: 必须使用 Redis Lua 脚本或 CAS 操作。 最终一致性: 不要强求强一致性,用 MQ + 补偿机制。 幂等性: 所有写操作接口必须幂等,防止重复消费。 预热: 启动时加载库存到缓存,避免冷启动问题。移动电商平台的核心竞争力,不在于前端页面多炫酷,而在于后端在高并发下是否稳定、数据是否准确。理解这些底层源码设计,才能在面试中从容应对,在实际工作中避坑无数。 还有什么不懂的?评论区留言挨个回。
返回列表