商品秒杀,防并发解决思路(乐观锁)

博客主要探讨数据库高并发场景下,当多人同时点击购买最后一件商品时商品分配问题。介绍了网上常用的乐观锁/悲观锁解决方法,重点阐述乐观锁原理,通过场景模拟说明在高并发下,如何利用乐观锁让唯一编号与商品编号匹配的用户购买商品。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们遇到的问题是,当有最后一件商品有两个或者多个人同时的去点击购买时,这个商品应该分配给谁。
要解决这个问题,网上一般推荐的方法是使用,乐观锁/悲观锁
数据库高并发下乐观锁的原理
 在高并发下,经常需要处理SELECT之后,在业务层处理逻辑,再执行UPDATE的情况。
  若两个连接并发查询同一条数据,然后在执行一些逻辑判断或业务操作后,执行UPDATE,可能出现与预期不相符的结果。
  在不使用悲观锁与复杂SQL的前提下,可以使用乐观锁处理该问题,同时兼顾性能。

场景模拟:
  假设一张表两个字段,一个id,一个use_count。表里存了100个id,每个id对应自己的use_count。
  当id每使用一次,use_count要加1。当use_count大于1000时,这个id就不能在被使用了(换句话说 无法从数据库中查出)。
  在高并发情况下,会遇到一种问题:假设数据表中有一条记录为:id=123456, use_count=999  A与B两个连接并发查询这个id=123456,都执行SQL:
就是说,如果我和另外一个人一起进来购买最后一件商品,我们同时进行点击,数据库给我们每个人都分配一个编号,这个编号是唯一的编号,只有能和数据库里面商品编号一样的用户可以购买这个商品。这就是乐观锁。

<think>嗯,用户让我介绍如何写一套Java高并发秒杀商品的源码。首先,我需要理解高并发秒杀系统的核心挑战是什么。秒杀场景下,大量用户同时访问,系统必须在极短时间内处理大量请求,同时保证数据的一致性,尤其是库存不能超卖。 我应该从架构设计开始考虑。可能要用分布式系统,比如用Spring Cloud或者Dubbo。不过如果是单体的,可能用Spring Boot简化开发。接下来是数据库部分,MySQL的话,库存扣减是个问题。直接更新库存可能会导致锁竞争,所以可能需要预扣库存,或者使用乐观锁,通过版本号控制。 然后,Redis的作用很大,作为缓存层。提前把商品库存加载到Redis中,用原子操作比如decrement来处理秒杀请求,这样可以减少数据库的压力。不过要注意缓存和数据库的一致性,可能需要异步同步或者消息队列来处理。 消息队列比如RabbitMQ或者Kafka,用来异步处理下单请求。这样可以削峰,把瞬间的高并发请求转为顺序处理,避免数据库崩溃。但是用户可能需要立即知道结果,所以前端可能需要轮询或者WebSocket来通知结果。 限流和降级也是关键,比如使用Sentinel或者Hystrix。限流可以止系统过载,降级可以在压力大时关闭非核心功能。还有止重复提交,前端按钮禁用,后端用验证码或者令牌机制。 分布式锁可能用Redis的setnx或者Redisson,确保同一用户不能重复秒杀,或者库存操作的原子性。另外,可以考虑库存预热,提前将库存放到缓存,避免每次都查数据库。 安全方面,止恶意请求,可能需要验证用户身份,限制IP频率。还有数据一致性问题,比如Redis预减库存后,如何保证数据库最终扣减成功,否则需要回滚缓存。事务消息或者本地事务表可能有帮助。 测试部分需要用JMeter模拟高并发,查看系统表现,找出瓶颈。可能还需要优化SQL语句,加索引,分库分表,或者使用数据库连接池提升性能。 现在要考虑用户可能的背景。他们可能有一定的Java基础,想了解高并发的处理方案,或者正在准备面试,需要项目经验。可能希望源码结构清晰,有详细注释,涵盖主要技术点。 需要分步骤讲解,从架构设计到具体实现,包括技术选型、数据库设计、缓存、消息队列、限流等。给出关键代码片段,比如库存扣减的Service层,Redis操作,消息队列的使用,以及如何保证原子性。 可能还需要提到常见的坑,比如缓存击穿、雪崩,超卖问题,事务的最终一致性。以及如何监控系统,比如使用Prometheus和Grafana。 最后,是否需要给出完整的源码?可能用户希望有示例代码或GitHub链接,但直接贴大量代码不合适,可以建议参考开源项目,或者提供关键部分的代码片段,并说明实现逻辑。</think>以下是一套Java高并发秒杀系统的核心设计思路和关键代码实现,涵盖高并发场景下的典型解决方案: --- ### **1. 技术选型** - **基础框架**:Spring Boot + MyBatis - **缓存层**:Redis(库存预热 + 原子操作) - **消息队列**:RabbitMQ/Kafka(流量削峰) - **限流降级**:Sentinel/Guava RateLimiter - **分布式锁**:Redisson - **数据库优化**:MySQL + 乐观锁 --- ### **2. 核心设计** #### **2.1 分层架构** ```java 用户请求 → 网关层(限流) → 服务层(Redis预减库存) → 消息队列(异步下单) → 数据库(最终扣减) ``` #### **2.2 流程设计** 1. **库存预热**:将商品库存提前加载到Redis 2. **请求拦截**:验证用户资格(是否重复秒杀) 3. **预减库存**:Redis原子操作避免超卖 4. **异步下单**:通过消息队列解耦核心逻辑 5. **最终扣减**:数据库乐观锁保证一致性 --- ### **3. 关键代码实现** #### **3.1 Redis预减库存** ```java // 初始化库存到Redis @PostConstruct public void initStock() { redisTemplate.opsForValue().set("seckill:stock:" + productId, stock); } // 原子操作预减库存 public boolean preReduceStock(String productId) { String key = "seckill:stock:" + productId; Long value = redisTemplate.opsForValue().decrement(key); return value != null && value >= 0; } ``` #### **3.2 乐观锁更新库存** ```sql UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = #{productId} AND stock > 0 AND version = #{version} ``` #### **3.3 消息队列异步处理** ```java // 发送秒杀请求到队列 @Autowired private RabbitTemplate rabbitTemplate; public void sendSeckillMessage(String userId, String productId) { SeckillMessage message = new SeckillMessage(userId, productId); rabbitTemplate.convertAndSend("seckill.exchange", "seckill.routingKey", message); } // 消费者处理下单 @RabbitListener(queues = "seckill.queue") public void processSeckillOrder(SeckillMessage message) { orderService.createOrder(message.getUserId(), message.getProductId()); } ``` #### **3.4 限流配置(Sentinel)** ```java @GetMapping("/seckill") @SentinelResource(value = "seckill", blockHandler = "blockHandler") public String seckill(String productId) { // 核心业务逻辑 } // 限流降级方法 public String blockHandler(String productId, BlockException ex) { return "当前请求过多,请稍后再试!"; } ``` --- ### **4. 止超卖与重复购买** - **Redis分布式锁**:控制单个商品的操作原子性 - **用户秒杀记录**:使用`userid + productid`生成唯一键存入Redis ```java String recordKey = "seckill:record:" + userId + ":" + productId; if (redisTemplate.opsForValue().setIfAbsent(recordKey, "1", 2, TimeUnit.HOURS)) { // 允许下单 } else { throw new RuntimeException("请勿重复参与!"); } ``` --- ### **5. 性能优化点** 1. **库存预热**:避免实时查询数据库 2. **页面静态化**:使用CDN缓存商品详情页 3. **Redis集群**:提升缓存吞吐量 4. **数据库分库分表**:按商品ID分片 5. **JVM调优**:合理设置堆内存与GC策略 --- ### **6. 完整项目结构** ``` src/ ├── main/ │ ├── java/ │ │ ├── controller/ // 接口层 │ │ ├── service/ // 业务逻辑 │ │ ├── mq/ // 消息队列处理 │ │ ├── dao/ // 数据持久层 │ │ └── config/ // Redis/Sentinel等配置 │ └── resources/ │ ├── application.yml // 配置文件 ├── test/ // 压测脚本(JMeter示例) ``` --- ### **7. 压测建议** - 使用JMeter模拟1万+ QPS - 监控指标:RT时间、MySQL连接数、Redis命中率 - 逐步优化瓶颈点(如:连接池参数、索引优化) --- 如需完整源码,可参考GitHub开源项目(例如miaosha),或通过以下简化版Demo扩展实现: ```java // 完整核心逻辑示例 public Result seckill(String userId, String productId) { // 1. 验证用户资格 if (redisUtil.hasKey("seckill:record:" + userId + ":" + productId)) { return Result.error("重复请求"); } // 2. Redis预减库存 if (!redisUtil.decrement("seckill:stock:" + productId, 1)) { return Result.error("库存不足"); } // 3. 发送消息队列 mqSender.sendSeckillMessage(userId, productId); return Result.success("排队中..."); } ``` 高并发系统需要结合具体业务场景持续优化,建议通过分布式压测工具验证各环节性能表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值