这篇博客是笔者学习慕课网若鱼老师的《Java秒杀系统方案优化 高性能高并发实战》课程的学习笔记。若鱼老师授课循循善诱,讲解由浅入深,欢迎大家支持。

本文记录课程中的注意点,方便以后code review。此外,本文将注意点相关的优质讲解链接在了一起,方便初学者系统学习。

本文并非单纯介绍秒杀系统特有的技术点,不适合高手。进阶学习的话,极客时间有个不错的小专栏——如何设计一个秒杀系统,阿里高级技术专家讲解秒杀系统的设计要点,那个课程挺干货的。

设计秒杀系统的技术要点

1. 登录的密码传输:

用户的数据库表设计,需要增加一字段保存密码的Salt值

两次MD5操作(敏感数据一定要使用https协议传输):

  • 客户端:将明文password和客户端硬编码的Salt值进行拼接,然后进行MD5操作。

不用盐的话,MD5字符串有可能会被彩虹表或者社工库破解

  • 服务端:将客户端传过来的MD5字符串和数据库用户对应的Salt字段进行拼接。然后进行MD5操作。

这次加盐MD5,可以有效防止内部员工泄露或者数据库被拖库后,明文密码泄露

2. 自定义JSR303的校验器

可以参照javax.validation.constraints.NotNull注解,自定义自己的校验器,将校验代码与业务代码分离。不过由于校验失败会输出BindException异常,所以最好配合全局捕获异常进行友好的输出。

自定义校验器很简单,只需要定义一个注解和对应的校验类

3. 自定义全局异常捕获

使用@ControllerAdvice注解,定义全局的异常捕获,并从异常中获取异常信息解析出来,发送给前端
可以自定义一个GlobalException异常,利用全局异常捕获,将所有服务器处理异常集中处理。(Service层处理异常后不设置状态码,而是直接抛GlobalException全局异常)

不返回状态码的好处是Controller层不需要再繁琐的判断Service层的返回值,代码更简洁

4. 数据库表设计

  • 通过将订单建立唯一索引来保证用户只能创建一个秒杀订单
  • 商品金额最好以分为单位,比较安全
  • 商品ID最好不要使用自增,会暴露商品总数等信息。可以使用UUID,但范围查找时会有性能损耗。所以一般采用SnowFlake算法生成ID

另外,自增ID的缺点也就是无法在多个表中,或者多个数据库中保持ID主键唯一不重复,所以若是使用分布式数据库以及数据合并的情况下时不能使用自增ID的。

5. 代码规范

  • 更新字段越多,产生的数据库Binlog就越多。所以只更新数据库部分字段的时候,最好新建一个对象,只赋值要更新的字段,然后调用mybatis的@Update,这样不做全量更新可以提高性能
  • 前端回包使用Result包装类封装,对报错信息使用CodeMsg包装类封装,保持代码风格统一
  • Service只注入跟自己同名的dao,如果需要别的dao,请注入对应的Service

Service的api相比dao会多一些防御代码(例如,直接修改了别的模块dao数据,但缓存未清理),更加安全

6. 事务

秒杀有两个事务:

  1. 减库存->创建秒杀订单
  2. 创建秒杀订单
    秒杀中涉及到上述两个事务,为了保障数据安全,可以使用声明式事务(Spring的@Transactional)

PROPAGATION_REQUIRED是Spring默认的传播机制,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。本工程的场景使用默认事务传播机制即可

有关Spring事务传播机制可以查看这篇博客

7. 压测

  • 在生产环境中,秒杀系统要独立运行与其他业务系统,实现资源隔离,避免业务系统相互影响稳定性
  • 请求入口可以使用nginx,LVS,F5等不同的负载均衡器
  • Jmeter 随机生成用户数据,然后使用Jmeter模拟用户压测。压测运行环境最好与被测服务器环境隔离。

接口测试可以还使用Postman和ApacheBench

8. 页面优化技术

  • 页面/URL缓存。用于数据变化不频繁的页面或者热点网页。如果数据较多需要分页的数据,类似商品详情数据,一般可以考虑只缓存前两页(根据访问量作取舍)

缓存方法:将渲染好的html文件存放到Redis。在访问Url时,首先检测Redis是否有html缓存。有缓存的话则直接返回缓存;没有缓存的话则渲染后存入Redis,并返回给前端。页面缓存过期时间具体根据业务场景判断。

  • 页面局部缓存。热点数据缓存,当Ajax请求信息更新,涉及的可能是需要保存在数据库的操作,例如表格信息等时,可以采用Redis缓存,方法同页面缓存一样,定义好可以区分业务的Key即可

  • 静态资源优化
    • JS/CSS压缩,减少流量(可通过升级HTTP2来解决)
    • 多个JS/CSS组合,减少连接数(例如:tengine)
    • CDN就近访问

如果需要采用JS/CSS压缩或者减少连接数等方法,可以使用HTTP2来提升性能

  • 对象缓存。例如使用Redis保存Session对象。对象缓存涉及到一个双写一致性问题,有关双写一致性问可以查看这篇博客

9. 秒杀的逻辑优化

顺序:

  1. 系统初始化,把商品库存数量加载到Redis
  2. 收到请求,Redis原子操作预减库存,库存不足,直接返回,否则进入3
  3. 请求入队,立即返回前端“排队中”
  4. 请求出队,生成订单,减少库存(服务端)
  5. 客户端轮询,是否秒杀成功(客户端)和4同步,得到结果刷新结果显示

优化:

  1. 在第二步预减库存时,可以在内存里加一个map,ID为商品ID,value为是否有库存,这样当库存没有之后,直接通过内存中的值判断是否还有库存,减少对Redis的访问。
  2. 购买请求加入消息队列,异步下单(前端显示排队中),增强用户体验
  3. 前端要尽量减少重复请求

10. 安全优化

10.1 秒杀接口地址隐藏

  1. 每次点击秒杀按钮,先从服务器获取动态拼接而成的秒杀地址。
  2. Redis以缓存用户ID和商品ID为Key,秒杀地址为Value缓存秒杀地址
  3. 用户请求秒杀商品的时候,要带上秒杀地址进行校验

10.2 数学公式验证码

  1. 防止恶意脚本抢购
  2. 使请求时间分散

10.3 接口限流防刷

使用计数法,在拦截器做限制请求频率。利用Redis缓存的有效期(以用户ID拼接Url作为key,以访问次数为value),指定缓存有效期为1秒,访问接口每次将value+1,到达阈值跳转全局异常。

优化:使用拦截器+自定义注解,减少对业务代码的侵入。有关拦截器可以查看这篇博客
另外对于接口限流也可以考虑使用令牌桶,控制对mysql的访问。

最后,限于笔者经验水平有限,欢迎读者就文中的观点提出宝贵的建议和意见。如果想获得更多的学习资源或者想和更多的技术爱好者一起交流,可以关注我的公众号『全菜工程师小辉』后台回复关键词领取学习资料、进入前后端技术交流群和程序员副业群。同时也可以加入程序员副业群Q群:735764906 一起交流。

Java秒杀系统优化的工程要点的更多相关文章

  1. Java Web编程技术学习要点及方向

    学习编程技术要点及方向亮点: 传统学习编程技术落后,应跟著潮流,要对业务聚焦处理.要Jar, 不要War:以小为主,以简为宝,集堆而成.去繁取简 Spring Boot,明日之春(future of ...

  2. Java秒杀简单设计二:数据库表和Dao层设计

    Java秒杀简单设计二:数据库表Dao层设计 上一篇中搭建springboot项目环境和设计数据库表  https://www.cnblogs.com/taiguyiba/p/9791431.html ...

  3. Java秒杀简单设计一:搭建springboot环境

    项目参考:慕课网  https://www.imooc.com/learn/587 Java秒杀 开发环境 JDK1.8.Maven.Mysql.Eclipse.SpringBoot2.0.5.myb ...

  4. Java秒杀系统实战系列~整体业务流程介绍与数据库设计

    摘要: 本篇博文是“Java秒杀系统实战系列文章”的第三篇,本篇博文将主要介绍秒杀系统的整体业务流程,并根据相应的业务流程进行数据库设计,最终采用Mybatis逆向工程生成相应的实体类Entity.操 ...

  5. Java秒杀系统实战系列~构建SpringBoot多模块项目

    摘要:本篇博文是“Java秒杀系统实战系列文章”的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项目,即“秒杀系统”! ...

  6. Java秒杀系统实战系列~商品秒杀代码实战

    摘要: 本篇博文是“Java秒杀系统实战系列文章”的第六篇,本篇博文我们将进入整个秒杀系统核心功能模块的代码开发,即“商品秒杀”功能模块的代码实战. 内容: “商品秒杀”功能模块是建立在“商品详情”功 ...

  7. 项目四:Java秒杀系统方案优化-高性能高并发实战

    技术栈 前端:Thymeleaf.Bootstrap.JQuery 后端:SpringBoot.JSR303.MyBatis 中间件:RabbitMQ.Redis.Druid 功能模块 分布式会话,商 ...

  8. java秒杀系列(1)- 秒杀方案总体思路

    前言 首先,要明确一点,高并发场景下系统的瓶颈出现在哪里,其实主要就是数据库,那么就要想办法为数据库做层层防护,减轻数据库的压力. 一.简单图示 我用一个比较简单直观的图来表达大概的处理思路 二.生产 ...

  9. Java基础进阶:时间类要点摘要,时间Date类实现格式化与解析源码实现详解,LocalDateTime时间类格式化与解析源码实现详解,Period,Duration获取时间间隔与源码实现,程序异常解析与处理方式

    要点摘要 课堂笔记 日期相关 JDK7 日期类-Date 概述 表示一个时间点对象,这个时间点是以1970年1月1日为参考点; 作用 可以通过该类的对象,表示一个时间,并面向对象操作时间; 构造方法 ...

随机推荐

  1. 【JVM从小白学成大佬】4.Java虚拟机何谓垃圾及垃圾回收算法

    在Java中内存是由虚拟机自动管理的,虚拟机在内存中划出一片区域,作为满足程序内存分配请求的空间.内存的创建仍然是由程序猿来显示指定的,但是对象的释放却对程序猿是透明的.就是解放了程序猿手动回收内存的 ...

  2. Spring与后端模板引擎的故事

    更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 现在很多开发,都采用了前后端完全分离的模式,随着近几年前端工程化工具和MVC框架的完善,使得这种模式的维护成本逐渐降 ...

  3. Spring数据库连接

    1.C3P0数据库连接 2.DBCP数据库连接 3.Druid数据库连接 C3P0数据库连接: 1.先到(http://www.mvnrepository.com/)Maven网址找到C3P0的数据源 ...

  4. Spring学习之旅(五)--AOP

    什么是 AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善. OO ...

  5. python学习——字符串

    1)字符串解释 字符串是python中常用的数据类型我们可以使用" "或' '来创建字符串. 2)字符串操作 """访问字符串中的值"&qu ...

  6. ubuntu使用yum安装软件问题

    其实ubuntu是不应该用yum来管理软件安装的,只是后来才发现的,这里记录一下尝试的过程. 一开始是想把windows桌面上的文件拖到xshell登录的ubuntu的目录中,但是没成功,参考http ...

  7. SDU暑期集训排位(4)

    SDU暑期集训排位(4) C. Pick Your Team 题意 有 \(n\) 个人,每个人有能力值,A 和 B 轮流选人,A 先选,B 选人按照一种给出的优先级, A 可以随便选.A 想最大化己 ...

  8. UVA 11294 wedding 2-sat

    可以把一对夫妇当成一个节点,然后拆点的话,h和w分别为真和假,然后直接按照题目中说的建图染色即可 #include <iostream> #include <cstdio> # ...

  9. CodeForces 1187G Gang Up 费用流

    题解: 先按时间轴将一个点拆成100个点. 第一个点相当于第一秒, 第二个点相当于第二秒. 在这些点之间连边, 每1流量的费用为c. 再将图上的边也拆开. 将 u_i 向 v_i+1 建边. 将 v_ ...

  10. poj 2649 Factovisors 对n!进行因数分解

    Factovisors Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 4431   Accepted: 1086 Descr ...