All Projects → MinheZ → seckill

MinheZ / seckill

Licence: other
🛍️ 实现一个高并发秒杀系统,关键技术:SSM + Redis + MySQL

Programming Languages

java
68154 projects - #9 most used programming language
javascript
184084 projects - #8 most used programming language
SQLPL
141 projects

seckill 🚅


1 秒杀系统业务分析

首先分析,秒杀系统问题的本质其实是对商品库存的管理。主要业务逻辑如下图:

用户针对库存业务分析:

用户的购买行为:

难点:如何高效地处理“竞争”。

2 开发环境

InteliJ IDEA + Maven + Tomcat8 + JDK8

3 工程创建

新建一个 Maven 工程,并完善相应的目录结构。

pom 文件的依赖可以分为 4 部分:

  • 日志。使用的是 slf4j + logback 的组合。
  • 数据库。数据库连接池 + DAO框架,MyBatis依赖。
  • Servlet web。jsp 等。
  • Spring。主要是 Spring 相关的依赖。

全部依赖参考 pom.xml

4 业务实现

4.1 数据库建表

  • 秒杀库存表
  • 秒杀成功明细表

创建 seckill表时,end_time字段默认值为0000-00-00 00:00:00,报错:1067 - Invalid default value for 'end_time'。

因为在MySQL5.7之后,默认值范围必须为1970-01-01 10:00:00~2037-12-31 23:59:59

4.2 DAO实体和接口开发

首先是实体类 entity 的编写,分为 SeckillSuccessKilled

SeckillDaoSuccessKilledDao 接口为查询数据库,或者修改数据库的一些方法。

剩下的就是一些 MyBatis 整合 Spring 的配置文件编写。

启用 Junit 单元测试的时候遇到一个小插曲,如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/seckill?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=****

之前没有遵循 Driud 官方设计规范,没有添加jdbc.前缀,导致数据库连接异常 ERROR 1045 (28000)。

4.3 Service层开发

SeckillService 接口开发,完成秒杀业务逻辑的一些方法。

List<Seckill> getSeckillList();	// 获取秒杀列表

Seckill getById(long seckillId);	// 通过 ID 获取秒杀对象

Exposer exportSeckillUrl(long seckillId);	// 判断是否需要暴露秒杀接口

// 执行秒杀操作
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException;
// 秒杀存储过程优化
SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5);

业务逻辑为,当秒杀开始前,秒杀页面只显示秒杀商品类型和倒计时。只有当秒杀开始的时候,才暴露秒杀地址,防止脚本提前登录。

定义一个 Exposer 类来实现此功能。主要是通过比较系统当前时间与秒杀开始和结束的时间。

定义一个 SeckillExecution 类来封装秒杀操作之后的结果。其中用枚举类型 SeckillStatEnum 封装秒杀过程之后的各种状态,例如:成功、失败等。

具体的实现类 SeckillServiceImpl 也就是对数据库的一些增删改查,包括对秒杀接口暴露,判断秒杀状态等一些列方法的具体实现。这里要注意 executeSeckill 方法的事务性,该操作必须是原子的。

4.4 Controller层开发

首先明确 SeckillController 业务流程:

详情页流程逻辑:

一般来说,Controller 层的 URL 表达方式默认使用 Restful 规范:

  • GET -> 查询操作
  • POST -> 添加/修改操作
  • PUT -> 修改操作(幂等操作)
  • DELETE -> 删除操作

下图为秒杀 API 的 URL 设计:

由于笔者是个前端菜鸡,因此页面那些东西就不在这里误人子弟了🚱

5 并发优化

5.1 缓存优化

在优化之前,首先弄清楚秒杀的高并发发生在哪?如下图,红色部分代表可能会有高并发区域:

详情页:参与秒杀的第一步,所有参与用户都会访问此页面。

系统时间:在进行当前时间与秒杀开始时间对比的过程中,由于系统访问一次内存的时间(Cacheline)非常短,大约是10ns,因此这一部分可以不做具体优化。

地址暴露接口:使用服务端缓存:Redis 集群。6 个 Redis 实例,3 个节点,每个节点都有自己的 slave 做备份,详细搭建过程自行 google 。

Redis 与数据库数据一致性保证

键值上设置过期时间,超时穿透;或者每当数据库变更时,主动更新至缓存。

5.2 数据库优化

业务逻辑中原始数据落地的方法:

数据库行级锁持有的时间(最差):(update + 网络延迟 + GC) + (insert + 网络延迟 + GC) 。GC 的 Stop the World 时长大概在 50ms 左右,当系统中并发量越高,GC 就越频繁。

优化的方向:如何减少行级锁持有的时间?

优化思路

  • 把客户端逻辑放到 MySQL 服务端,避免网络延迟和 GC 的影响。
    • 定制 SQL 方案:update /* + [suto_commit] */,需要修改 MySQL 源码。
    • 使用存储过程:整个事务在 MySQL 端完成。seckill.sql

本项目学习自慕课网

有任何疑问欢迎提 issues 一起交流、探讨,或者直接联系我 [email protected]

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].