整理秒杀系统的⾯试必备
分布式环境的秒杀系统
如果在简历中使⽤的是秒杀项⽬,那么在⾯对⾯试官时请做⾜准备。
应⽤场景
商城系统需要⼀个秒杀系统来提⾼购买量。秒杀活动通常都会伴随有⾼并发的情况。为了⽀撑⾼并发情况下的流量冲击,我们需要设计⼀个分布式的秒杀系统。黄褐斑的偏方
技术栈
数据库设计
⽤户信息表,⽤户密码表,商品表,库存表,订单信息表
秒杀信息表(字段如下:)
字段 信息
字段描述
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操作保证每天增长长度。
这⾥有⼀个未解决的问题:就是如果当天增加的订单数⾮常多那么会产⽣⼀个⾮常长的订单号,并且订单号的长度通常都固定,⼀旦超出将会产⽣⼀系列问题。
解决并发情况超卖问题:实际订单数⼤于库存数
友情岁月演唱会2013解决: 还是数据的原⼦性问题,在执⾏库存查询前进⾏加锁,这⾥可以使⽤悲观锁的思想,利⽤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 命令打包运⾏显⽰ 不到主类
wincc教程解决:这样的问题应该是编译后不到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安全性问题
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论