设计一个健壮的秒杀系统需要综合考虑多方面因素,包括超卖、高并发、接口防刷、数据库设计等,以下是详细的设计方案:
一、核心问题考虑- 超卖问题:秒杀场景下,商品备货数量有限,若系统设计不当,可能出现超卖现象,即售出数量超过库存,严重影响公司利益。因此,防止超卖是首要任务。
- 高并发:秒杀活动时间短、并发量大,大量请求在短时间内涌入,可能导致缓存击穿或失效,甚至击垮数据库。
- 接口防刷:存在专门针对秒杀的软件,模拟大量请求,一秒几百次,需防止这类重复无效请求。
- 秒杀url:普通用户通过页面参与秒杀,但有电脑功底的用户可能通过浏览器开发者工具获取秒杀url,提前或通过软件请求实现秒杀,需防止此类情况。
- 数据库设计:秒杀系统的高并发访问可能拖垮整个网站,若与其他业务共用数据库,会牵连影响其他业务。
- 大量请求问题:即使使用缓存,也难以应对短时间高并发流量冲击。以redis缓存为例,单台redis服务器QPS约4W,若秒杀QPS达几十万,单体redis无法支撑,缓存被击穿后会直接渗透到DB,击垮mysql。
二、设计和技术方案- 数据库设计
独立数据库:单独设计秒杀数据库,避免因秒杀活动影响其他业务。
表设计:
秒杀订单表:记录秒杀订单相关信息。
秒杀货品表:记录参与秒杀的货品信息。
商品表:关联goods_id,可查询商品图像、名称、平时价格、秒杀价格等信息。
用户表:根据用户user_id可查询用户昵称、手机号、收货地址等信息。
- 秒杀url设计:将秒杀url动态化,通过md5加密一串随机字符作为秒杀url,前端访问后台获取具体url,后台校验通过后才可继续秒杀,即使是开发人员也无法在秒杀开始前知道url。
- 秒杀页面静态化:使用freemarker模板技术,将商品描述、参数、成交记录、图像、评价等写入静态页面,用户请求直接在前台客户端生成,减少服务器压力。
- 缓存升级:构建redis集群,采用哨兵模式,提升redis性能和可用性,解决缓存击穿问题。
- 使用nginx:nginx并发能力强,可达几万,而tomcat只有几百。通过nginx映射客户端请求,再分发到后台tomcat服务器集群,提升并发能力。
- 精简sql:在进行扣减库存时,使用一个sql完成操作,如:update miaosha_goods set stock =stock - 1 where goos_id = {#goods_id} and version = #{version} and sock > 0;,并使用版本号的乐观锁,避免超卖。
- redis预减库存:在秒杀开始前,在redis中设置库存值,每次下单成功后,从redis中获取库存并判断,若小于设定值则减去1。取消订单时增加库存,注意不能超过总库存数,查询库存和扣减库存需原子操作,可借助lua脚本。
- 接口限流
前端限流:用户点击秒杀按钮后,在接下来的一定时间内(如5秒)禁用按钮,防止用户频繁点击。
同一用户限流:通过redis的键过期策略,对每个用户请求进行判断,若用户id对应的值为空或为null,则为有效请求,放行并设置过期时间;若不为空则为重复请求,直接拒绝。
令牌桶算法限流:使用guava提供的RateLimiter实现令牌桶算法,每个请求尝试获取一个令牌,后端只处理持有令牌的请求。可通过设定生产令牌的速度和效率,以及超时时间,控制请求的放行和拒绝。
public class TestRateLimiter2 { public static void main(String[] args) { final RateLimiter rateLimiter = RateLimiter.create(1); for (int i = 0; i < 10; i++) { long timeOut = (long) 0.5; boolean isValid = rateLimiter.tryAcquire(timeOut, TimeUnit.SECONDS); System.out.println("任务" + i + "执行是否有效:" + isValid); if (!isValid) { continue; } System.out.println("任务" + i + "在执行"); } System.out.println("结束"); }}- 异步下单:使用队列(如rabbitmq)进行异步下单,将下单操作从主流程中分离出来,提高下单效率并防止下单服务失败。有效请求经过限流、库存校验后进入队列,队列接收消息并异步下单,下单成功后可用短信通知用户,失败则采用补偿机制重试。
- 服务降级:通过Hystrix进行服务熔断和降级,开发备用服务。若在秒杀过程中出现服务器宕机或服务不可用,直接给用户友好提示,而不是卡死或返回服务器错误等生硬反馈。
三、系统架构图四、不同体量秒杀系统的技术选型调整上述流程可支撑几十万流量,若流量成千万破亿,需重新设计,如采用数据库分库分表、队列改成用kafka、增加redis集群数量等手段。
通过以上设计,可有效应对秒杀系统的高并发、防止超卖、防止接口被刷等问题,设计出一个健壮的秒杀系统。同时,在工作中需不断思考和实践,提升应对高并发场景的能力。