Java高并发秒杀系统

文章详细描述了设计和实现秒杀系统的过程,涉及高并发处理、Redis缓存、双层MD5加密、分布式Session、RabbitMQ消息队列、QPS提升、数据库优化、安全措施如验证码和接口限流,以及使用SpringBoot进行异常管理和集成RabbitMQ的配置。

概述&如何设计一个秒杀系统

模拟了一个高并发场景的商城系统,它具备秒杀功能,并在经过几个版本的迭代之后成为支持高并发的高性能系统。为了解决秒杀场景下的高并发问题,引入了redis作为缓存中间件,主要作用是缓存预热、预减库存等等。针对高并发场景进行了页面优化,缓存页面至浏览器,加快用户访问速度。在安全性问题上,我使用双重MD5密码校验,隐藏了秒杀接口地址,设置了接口限流防刷。最后还使用数学公式验证码不仅可以防恶意刷访问,还起到了削峰的作用。通过Jmeter压力测试,系统的QPS从150/s提升到2000/s。

秒杀主要解决两个问题,一个是并发读,一个是并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。
秒杀的整体架构可以概括为“稳、准、快”几个关键字,对应了我们架构上的高可用、一致性和高性能的要求。

  • 高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。
  • 一致性。 在大并发更新的过程中要保证数据的准确性
  • 高可用。 现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。

设计MySQL表

用户表(用户ID、密码等)、商品信息表(商品ID和其他商品详情信息)、商品秒杀表(商品ID、秒杀始末时间、秒杀价和库存数量)、秒杀订单表(订单ID、用户ID和商品ID)、订单详情表(订单ID和其他订单详情信息)

两次MD5加密的时机及原因

MD5(MD5(明文 + salt1) + salt2)
第一次:客户端输入的密码传入后端之前
原因:客户端输入的是明文密码,直接在网络中传输容易被截获,因此要防止密码在网络中明文传输。
第二次:后端接收到第一次加密后的密码之后,传入到数据库之前
原因:万一数据库被盗,盗用者虽然可以获得第二次的密文和盐,但由于MD5“解密”过程很困难并且无法确定加盐的方式,因此基本无法反推出第一次加密后的密文。

黑客在网络中截获数据包后获得了第一次加密后的密文怎么办?
如果使用的是https进行传输,黑客即使截获了数据包也无法获得里面的内容。如果不是https,黑客只能用第一加密的密文伪造数据包向服务端发送请求,而无法在前端用用户的明文密码登录,增加了作案成本。

登录功能

在这里插入图片描述

用自定义注解进行参数校验

每个类都写大量的健壮性判断过于麻烦,我们可以使用 validation 简化我们的代码。比如可以自定义一个@IsMobile注解来判断登录功能中手机号码的合法性

异常处理

如何将异常展现在前端?使用SpringBoot全局异常处理

系统中异常包括:编译时异常和运行时异常 RuntimeException ,前者通过捕获异常从而获
取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。在开发中,不管是
dao层、service层还是controller层,都有可能抛出异常,在Springmvc中,能将所有类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。
SpringBoot全局异常处理方式主要两种:

  1. 使用 @ControllerAdvice@ExceptionHandler 注解。
  2. 使用 ErrorController类 来实现

区别:

  1. @ControllerAdvice 方式只能处理控制器抛出的异常。此时请求已经进入控制器中。
  2. ErrorController类 方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误
  3. 如果应用中两者共同存在,则 @ControllerAdvice 方式处理控制器抛出的异常,
    ErrorController类 方式处理未进入控制器的异常。
  4. @ControllerAdvice 方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常
    信息,自由度更大

因此,我们使用@ControllerAdvice@ExceptionHandler 注解的组合。

用分布式Session完善登录功能

使用cookie+session记录用户信息,这样可以保持用户的登录状态。

为什么要用分布式Session?

之前的代码在我们之后一台应用系统,所有操作都在一台Tomcat上,没有什么问题。当我们部署多台系统,配合Nginx的时候会出现用户登录的问题
原因:由于 Nginx 使用默认负载均衡策略(轮询),请求将会按照时间顺序逐一分发到后端应用上。也就是说刚开始我们在 Tomcat1 登录之后,用户信息放在 Tomcat1 的 Session 里。过了一会,请求又被 Nginx 分发到了 Tomcat2 上,这时 Tomcat2 上 Session 里还没有用户信息,于是又要登录。

在这里插入图片描述

基本解决方案:

java实现秒杀系统@Controller @RequestMapping("seckill")//url:/模块/资源/{id}/细分 /seckill/list public class SeckillController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillService seckillService; @RequestMapping(value="/list",method = RequestMethod.GET) public String list(Model model){ //获取列表页 List list=seckillService.getSeckillList(); model.addAttribute("list",list); //list.jsp+model = ModelAndView return "list";//WEB-INF/jsp/"list".jsp } @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET) public String detail(@PathVariable("seckillId") Long seckillId, Model model){ if (seckillId == null){ return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null){ return "forward:/seckill/list"; } model.addAttribute("seckill",seckill); return "detail"; } //ajax json @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult exposer(@PathVariable("seckillId") Long seckillId){ SeckillResult result; try { Exposer exposer =seckillService.exportSeckillUrl(seckillId); result = new SeckillResult(true,exposer); } catch (Exception e) { logger.error(e.getMessage(),e); result = new SeckillResult(false,e.getMessage()); } return result; } @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public SeckillResult execute(@PathVariable("seckillId")Long seckillId,
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值