整理秒杀系统的面试必备!!!

分布式环境的秒杀系统

如果在简历中使用的是秒杀项目,那么在面对面试官时请做足准备。

应用场景

商城系统需要一个秒杀系统来提高购买量。秒杀活动通常都会伴随有高并发的情况。为了支撑高并发情况下的流量冲击,我们需要设计一个分布式的秒杀系统。

技术栈

在这里插入图片描述

数据库设计

用户信息表,用户密码表,商品表,库存表,订单信息表
秒杀信息表(字段如下:)
字段 信息

字段 描述
id 自增主键
promo_name 秒杀名称
start_time 开始时间
end_time 结束时间
promo_item_price 秒杀价格
item_id 关联商品ID

踩坑:Mysql5.7无法给DataTime类型一个默认的初始值 0000-00 解决:面向百度解决!

项目模块设计

在这里插入图片描述

简单模块说明及问题

用户注册 用户入库操作,将用户信息存入。密码进行MD5加密
用户登陆 通过用户名和密码进行验证登陆。并存储用户信息到Map中

问题:部署到分布式环境的时候会发生已经登陆的用户会丢失登陆信息的情况
解决:

  1. 查看本地的项目,登陆多次并未发现问题。猜测是服务器环境的问题。
  2. 对服务器环境和本地环境进行对比。发现服务器的环境上使用2台服务器部署项目,而我们的登陆却只需要在其中一个服务器的内存上进行存储。所以访问到另一台服务器的请求时便无法获取到内存的数据。
  3. 所以需要借助一种2个台服务器都能访问到的中间件(Redis)进行用户信息的存储。客户端携带着一个uuid来对登陆请求进行判定

核心模块详细说明

商品模块
秒杀系统下的商品由于需要大量的被访问可能导致数据库的压力过大。使用分级缓存来解决这一问题。如下图:
在这里插入图片描述

当一个客户端请求访问商品信息的时候,需要先到本服务器的内存中查找,然后到缓存中进行查找。最后进入数据库查找。找到后,将信息分别存入缓存和内存。

问题: 本地项目启动后在数据库修改了图片的链接后,刷新页面发现不能显示修改后的图片
解决: 这个想到了缓存的问题很快就想明白了,缓存未进行刷新。算是间接的发现了一个缓存刷新问题。在对缓存的数据进行操作时,一定要对所有的缓存进行一个同步的操作。否则会导致数据异常。

订单模块
订单模块需要对订单进行一个入库操作。订单的注意点就是判定商品是否以秒杀价格拍下的商品。 秒杀下单操作对库存有一个要求:库存由下单判定减少还是由支付判定减少。

入库操作的问题:
订单号如何保证有意义且唯一?
解决:UUID?没有任何格式,Pass。使用时间戳+随机码的形式。但是万一同时随机到相同的数字怎么办?最好是整一个像ID主键这种的自增序列。又有时间戳还又可以保证唯一性。这里想了下在内存设置一个增长键。使用加锁来保证增长键的原子性。
但是在分布式下会产生如下问题:
订单ID和登陆信息遇到了相同的问题,当时考虑了一下也可以借助缓存进行。但是使用数据库设置自增字段更稳定,还有很好的通用性。这样其他需要自增序列的ID也可以使用。这里设计了一个自增信息表,字段如下:

字段 描述
name 增长序列名称
current_value 当前起始值
step 增长长度

我们通过对表的操作来生成一个序列,并且通过name字段获取自增信息。可以每天对current_value 进行归0操作保证每天增长长度。
这里有一个未解决的问题:就是如果当天增加的订单数非常多那么会产生一个非常长的订单号,并且订单号的长度通常都固定,一旦超出将会产生一系列问题。

解决并发情况超卖问题:实际订单数大于库存数
解决: 还是数据的原子性问题,在执行库存查询前进行加锁,这里可以使用悲观锁的思想,利用mysql的select for update 加锁机制,悲观锁非常像syconized关键字,一旦当前的请求访问到数据,其他的请求就要被阻塞。这里顺便了解了下乐观锁思想,发现在这种高并发的情况下,真正的加锁是一种效率很低的方式。采用乐观锁的思想对数据库进行一个表的版本控制是一个不错的选择。在更新前必须保证更新的版本号和查询到的版本号一致,否则更新失败。

秒杀模块
首先利用时间段来判定某商品是否处于一个秒杀活动的时间段。对于进入秒杀的商品就进行一个秒杀价格的展示和倒计时的显示还有下单按钮的操作。前端获取到秒杀开始的时间需要对秒杀活动进行一个判定。在秒杀未开始时,执行一次倒计时操作。倒计时操作使用每隔一秒刷新一次页面来显示时间。
问题及解决
问题:使用客户端时间来进行倒计时展示的安全问题
解决:每次刷新都要重新向服务端请求一次秒杀活动时间。
未解决:如果说每一秒都要请求一次服务器那么是否会对服务器的性能有影响?或许实际情况中是有一台专用的时间校验服务器?
踩坑:从Redis存取时间出现了序列化乱的情况
解决:首先想到的是对时间进行一个格式化的存储。但是考虑到Json存入Redis的情况会很多,如果可以对redis进行一个通用配置就好了,百度发现可以通过配置对redis进行一个通用的json序列化配置,然后对日期格式进行格式化存取配置。

其他问题: 在对一个service测试类测试的时候抛出了空指针的异常
解决: 通常的空指针异常是对象的引用没有开辟出内存空间。Debug发现测试的service接口抛出了空指针。进入service接口的实现类debug 发现没有问题。思来想去,去测试类重新检查,最后发现对要调用的测试接口没有加入@Autowire注解。一直都想知道Spring框架如何实例化的,于是去查看了BeanFactory的源码,发现了在Spring实例化前要经历很多步骤,从实例的调用方法getBean开始到单例模式,原型模式的选择。默认单例模式的设计也有很多步骤。单例模式下调用的实例化方式有autowireConstructor自动装配构造方法,这个我猜测就是@Autowire注解底层的实例化部分。其他实例化方式有:无参构造实例化,工厂实例化,Bean实例化。我到这里只查看了无参构造的底层是使用的反射进行的实例化,解决了困惑。

其他踩坑:使用Nginx服务器搭建:前端ajax请求无法访问后端
解决:1.排查错误原因,前端浏览器报错是未连接到服务器
2.根据错误原因可能是:服务器端未启动,服务器端拒绝连接当前前端服务器的请求 。检查后发现启动日志正常。并且我并未对服务器进行配置。
3.此处一度怀疑是ajax请求出问题了。最后度娘找到了前后端分离出现的跨域问题。Springboot中可以设置一个允许请任何求头访问的注解。
4.因为在每一个controller都写一次注解过于重复,SpringBoot也可以使用一个通用的跨域配置解决问题。

分布式环境的部署

项目完成后需要进行一个环境的搭建,先看一下项目架构:
在这里插入图片描述
服务器: 1台 nginx 服务器 ,2台秒杀服务器,1台mysql ,1台Redis
问题:实行过程中启动2台1核1G虚拟机CPU占用就满了。
解决: 虚拟化技术不止有虚拟机,我认为容器技术也是一个微型虚拟机。借助docker这样的容器可以达到分布式模拟的效果,对于无力购买服务器的学生党来说是一个非常不错的选择。

分布式环境模拟容器部署详细方案

准备:创建一个Centos的虚拟机,安装一个docker 环境。
秒杀服务器的容器部署
SpringBoot由于是一个内嵌tomcat的框架,可以直接使用命令行启动,所以我们只需要使用 docker pull 一个java的镜像下来。将 SpringBoot打包发送到虚拟上进行容器部署。
部署步骤

  1. 将jar包使用 ftp 发送到服务器
  2. 运行一个 java环境的容器
  3. 将容器复制进入容器内部
  4. 进入容器内部
  5. 使用 jar 命令启动
    2台秒杀服务器均采用这种方法进行部署

踩坑

打包踩坑:jar 命令打包运行显示 找不到主类
解决:这样的问题应该是编译后找不到main()函数的问题,百度查找看一下SpringBoot的入口函数的配置即可解决问题。
容器踩坑:容器网络不通
解决:查看《Docker实战》中的网络相关知识,重新部署了含有网络配置的容器。重复执行上述步骤。

Nginx服务器的容器部署

项目的所有请求都发到Nginx服务器,使用Nginx的动态和静态分离提高访问效率。而Nginx服务器使用的是反向代理来代理秒杀服务器。负载均衡是使用的轮询访问策略配置的,每次请求都是交替代理到秒杀服务器上的。
问题:Nginx的高性能是如何实现的?
解决:了解了Nginx的一些进程模型的设计如:epoll模型,master-worker模型。
未解决:
静态资源访问抛出异常

Resource interpreted as Stylesheet but transferred with MIME type
text/plain:

百度到Nginx配置了静态资源,但是没有效果。后来查到去掉html上的 后就解决了。但是原因未找到。

Mysql服务器和Redis的容器部署

Mysql服务器和Redis只要注意网络配置就行。
Mysql要将本地sql的发送到虚拟机并复制到容器内部,然后进入容器内部执行sql文件生成数据库。

网络相关说明

docker 容器网络配置后所有的容器会使用一个默认的网段。相当于实际生产环境中的内网。外网访问Nginx ,其他服务器的访问使用内网环境。
未解决问题:Nginx安全性问题

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章