1、架构设计及说明(热点、非热点数据隔离)
说明:
(1)商品详情页做成静态页,通过nginx直接调用,使用freemaker生成静态页,以sku表的id+.html做为静态页名称,使用canal监听数据库表sku(即商品详情表),有变化则更新静态页。(新增数据则生成一个新的静态页,修改数据则覆盖原来的静态页,删除则删除静态页)
(2)秒杀开始前,用户浏览详情页时,记录商品的信息发送到kafka集群, Apache Druid大数据实时分析数据库消费kafka数据,进行实时热点数据分析,每5秒执行定时任务从druid中查询最近1小时商品的访问量,将请求数大于10000(假设)的商品信息存入redis。
(3)秒杀开始后,用户对商品下单,通过lvs+nginx,采用openresty+lua脚本从redis中获取该商品信息,如何获取不到,说明是非热点商品,直接调用后台微服务走正常逻辑下单即可。
如果获取到,说明是热点商品,此时有两种方案:
方案一、返回给前端,前端弹出验证码输入框,这样每秒20万并发请求分散成4-5秒内,每秒差不多5万并发。假如总共20万并发量,则需要部署lvs3台,nginx每台可承载3万并发,部署8台,经验输入验证码后,有差不多5万并发量,(1)如果按照应用3秒响应,后台应用差不多200tps,承载5万并发的话,需要部署250台服务器,太多也太贵了,所以在nginx处依然要进行限流,现在部署50台服务器,整个后台应用集群能承受10000的并发量,所以需要在nginx处限流每秒10000并发,也就是每台nginx需要限流1250,剩余的40000个请求直接返回请稍后重试。这8000请求进入后台应用,使用redis预减库存,假如库存有5000个,看是否能获取信号量,否直接返回结果秒杀失败,是则递减库存,返回秒杀成功,5000个请求生成订单,扣减库存等处理。
(2)如果是使用redis预减库存后进入mq,再消费mq,慢慢处理后面的逻辑的方式(使用分布式锁,防止库存线程安全问题)。此时应用tomcat的tps就很大了,假设tomcat线程数是500,每个线程执行的时间是100毫秒(其实可能更快,因为redis和mq处理很快),则500个线程1秒钟就可以处理5000个请求,也就是tps=5000,此时只需要部署200000/5000=40台服务器即可。此时就不需要秒杀开始时前端输入验证码,nginx也无需进行限流,用户体验更好。
方案二、直接在nginx中使用lua脚本做预减库存操作,看是否能获取信号量,否直接返回结果秒杀失败,是则递减库存,返回秒杀成功并进入mq,后台应用消费mq,慢慢处理后面的逻辑即可(使用分布式锁,防止库存线程安全问题)。
(4)下单后通过状态通知系统websocket+netty将状态返回给用户。
(5)openresty+lua脚本解析jwttoken,无效则直接返回,无需经过后端服务,过滤掉大量请求。
真实:
4核8G;
后端服务部署20台;
lvs部署3台,nginx部署10台(单台3万并发量);
redis-cluster集群部署4台(单台2万并发);
kafka集群部署5台,两个分区,每个分区5个副本。
qps大约20万,tps大约4000,(也就是说一个请求过来,差不多会经历2000000/4000=50个查询),每天订单量大约1万,高峰期5-6万,限流采用令牌桶算法,每秒限制3000个请求。
并发量=QPS✖平均响应时间,比如秒杀,一秒处理20万查询,那么系统的并发量就是20万。
最终方案:
1、前端商品详情页做成静态页,放入nginx,再推送到cdn,cdn购买流量包(1T几百块钱)。
问题1:如果有人恶意刷流量怎么办? 答:采用云厂商的ddos高防产品。
问题2:有人拿到路径使用程序恶意请求怎么办?答:商用验证码
问题3:雇多个人多个手机手刷怎么办?答:反作弊服务,还有可能是有的用户一下子搞多个僵尸账号,然 后 同一时间一起发送请求,就专门进行刷单,此时可以对历史请求日志进行分析,抽取出哪些IP地址 是喜欢每个秒杀场次都发请求的,而且每次都超速超标发送过多请求,直接就封杀掉;还有就是可以 对一些账号的日常行为进行数据分析,如果发现有些账号平时都不怎么浏览,几乎从不登录,每次就 是到秒杀场次的时候才来拼命抢,也可以判定为是刷单账号,直接封禁掉就可以了;反作弊机制,往 往都是要依赖大数据分析手段的,对账号过往日常行为进行分析,然后封禁账号,或者是让nginx调用 反作弊接口,判定请求是否合理,这就可以过滤掉一大批作弊请求了。
2、秒杀商品上架时,将商品信息存入redis,请求经过nginx时,需要进行限流,分为整体限流和业务限流,整体限流是根据后台服务整体承载能力进行设定,业务限流可以根据当前场次商品库存总数进行限流(使用lua脚本从redis中获取各商品总库存),比如12点场的秒杀,商品A有1万件,商品B有2万件,商品C有3万件,则可限制流量为1万+2万+3万=6万请求,为了避免网络或者其他原因导致请求失效,则可将流量增加10%-20%,进入服务后要做合法性校验,是否登录、秒杀时间是否正确,限制数量是否合适。
3、幂等性保证,验证这个人是否已经购买过,去redis中进行占位,使用redistemplete的setIfAbsent方法,key是用户id+商品id,value是购买的商品数量(随意值即可),过期时间设置为活动时间减去当前时间,也就是活动结束,就可以删除该占位。如果占位成功则说明没有购买过,则继续操作,否则返回不可重复秒杀。
4、幂等性验证完成后,需要去进行redis预减库存,商品上架时,会使用redisson的信号量,在redis中保存秒杀库存量,预减库存时,使用redissonClient.getSemaphore获取信号量的方法获取到库存量,使用信号量的tryacquire方法进行库存递减,redisson的信号量可保证原子性,递减成功,则生成订单号(雪花算法),然后将订单号,商品数量,用户id,商品id等发送到mq。递减失败,则直接返回用户秒杀失败。
5、进入mq后,比如kafka,如果使用批量消费数据,为加快处理速度,需要使用多线程进行处理,这时为保证线程安全,可以使用分布式锁,比如数据库扣减等。
6、用户秒杀成功后,进入恭喜你秒杀成功页面,然后进入订单页面,此时如果消费mq还未完成,可返回给用户秒杀订单正在处理中,请稍后再试,前端每隔5秒轮询去查询订单是否生成,消费mq完成后,进入订单页面,然后进行支付、履约等操作。
7、订单支付成功后,需要更新订单支付状态,进行履约等操作,这时还要继续发mq。
部署说明:
1、采用以上方案,完成redis和mq处理后就返回给用户结果,与数据库交互动作在后台慢慢处理。此时应用tomcat的tps就很大了,tomcat调优将线程数是500,这段逻辑每个线程执行的时间是100毫秒(其实可能更快,因为redis和mq处理很快,最多就30毫秒),则500个线程1秒钟就可以处理5000个请求,也就是tps=5000,此时只需要部署200000/5000=40台服务器即可。此时就不需要秒杀开始时前端输入验证码,用户体验更好。
2、为保障20万并发,
nginx:每台3万并发的话,需要8台,做个冗余,部署10台。
后台服务:每台tomcat并发1万,需要20台,部署30台做冗余
redis:每台2万并发, 可部署12台作为一个集群。
mq:由于秒杀商品的数量并不多,经过redis预减库存后,并发量更小了,部署两台即可(kafka)
3、万一突然流量变大,超过20万,会导致服务被压垮,这时需要在nginx进行限流,现在能承载20万的并发,就按20万的承载量进行限流,10台nginx,每台限制2万即可。