概况
1.秒杀架构设计理念?
(1)限流:首先是由于只有少部分用户能够秒杀成功,那么就要限制大部分流量,只允许少部分进入后端。
(2)削峰:于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。
(3)异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
(4)内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
(5)可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了;像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。
2.秒杀架构的设计思路?
(1)拦截请求在上游,降低下游压力。
(2)利用缓存
(3)消息队列:消息队列可以削峰。
3.前端方案?
(1)页面静态化:图片等固定不动资源静态化,减少请求传输。
(2)禁止重复提交:按钮按一次就变灰了。
(3)可以用户限流:一段时间内一个用户只允许提交一次,比如IP限流。
4.后端方案?
(1)采用缓存应对订单中数据库部分得修改,转而在redis中进行处理。
(2)利用消息队列进行流量的削峰,库存删减成功则返回成功,否则失败。
5.登录模块加密?
(1)首先在前端就需要对我们的密码进行一次加密,之后在网络上传输,要是不加密的话,就会以明文的形式送到后端去。
(2)之后在后端随机加上一个盐值之后再进行一次加密,因为这个需要保证数据库被入侵之后,还是能够不被查出来密码的。
6.session共享的问题?
(1)我们可以使用redis进行共享。
(2)可以为每一个登录成功的用户建立一个token,之后放到cookie里面,之后每一次操作都需要携带token。
(3)我们可以在后台看看这个token有没有对应的登录消息,这些可以做到拦截器里面。
7.MQ如何解决消息堆积?
(1)如果是因为消费者这边有问题,导致的消息堆积,那么我们应该去首先恢复Consumer,之后停掉服务。
(2)rabbitMQ实际不存在积压而是丢失,我们找到哪些数据,之后再写回去。
8.削峰操作?
(1)将其顺序的输入mq里面,之后再读出来。
(2)可以写到文件里面,之后再读出来,类似于binlog。
(3)这类操作的核心就是将操作由一步变成两步。
(4)可以采用答题的方式进行削峰。
(5)还可以在开始的时候发优惠券什么的,将流量分发到其他地方去。
(6)消息队列适合于上下游系统不平稳的场景,由于内部系统的服务质量不能随意丢弃请求,所以需要使用消息队列进行削峰和缓冲。
(7)答题则是从源头处减缓请求的场景,在请求发起端控制请求的速度,使其散开来不是一下子涌进来。
9.常用的缓存技术?
(1)页面缓存:页面都是HTML的,可以将页面缓存到金额护短
(2)静态资源优化:可以考虑一次性的将js和css请求一起拉过来,也可以考虑使用压缩版本的js和css,比如jquery里面有个mini版本的。
(3)缓存淘汰策略:FIFO先进先出;LRU最久未使用。
秒杀的方案
1.我们秒杀的所有环节?
(1)使用redis预减库存减少对数据库的访问。
(2)使用内存标记减少redis的访问
(3)使用rabbitmq消息队列缓冲,异步下单,增强用户体验。
2.第一步
(1)系统初始化的时候,将商品库存加到redis里面去。
(2)收到请求,redis预减库存,不足就直接返回;如果已经到达了库存为0,那么都不用继续往下走了,直接返回秒杀失败。
(3)请求放到消息队列里面。
(4)将请求从消息队列里面取出来,生成订单,减少库存。
(5)之后websocket或者是客户端轮循去看看是否秒杀成功。
(6)之后使用延时队列去看看订单是否支付。
3.如何缓解压力?
(1)首先需要在启动得时候,库存等信息扔到redis里面
(2)之后用户请求过来,先扣减库存,之后看一下是否有生成订单,有的话就库存更新回来,防止重复秒杀。
(3)都没问题了,将请求封装后放如秒杀队列,向前端返回一个正在排队中。
(4)前端接受到数据之后,可以轮循,可以websocket。
(5)后端监听这个通道,执行真正得操作之前,现需要检查是否重复,是否库存充足,之后执行秒杀事务,(库存-1锁住,之后下订单,填写详情)
(6)支付成功则秒杀成功,否则失败。(使用死信队列)
(7)失败来一个补偿事务,将这些操作倒着做一遍。
4.秒杀地址隐藏
(1)做出来两个接口,第一个接口是产生随机得一个字符串;
(2)第二个接口是验证这个字符串是不是在redis
(3)我们每次请求都是请求后端产生一个随机得字符串,后端产生字符串之后放到redis里面设置过期时间,返回给用户;
(4)用户收到之后将其拼接到地址里面发送到后端;
(5)后端接受到之后,先看看这个字符串过期没。
(6)过期了就gg返回失败,其实就是防刷得一种手段。
5.接口限流防刷?
(1)可以在redis里面设置一个计数器,第一次访问可以将其置为1,之后每次访问都+1,一分钟之内可以设置一共能访问多少次
(2)次数到了就显示访问过于频繁,起到了一个防刷得作用。
其余零散得问题
1.网站并发?
压力测试下大概1000左右,分布式得话,是可以进行水平扩展的。
2.sku和spu分别是什么意思?
Sku是库存得最小单位,每一个对应一个商品;spu是这个商品得门类,例如iphone,就是一个spu;红色iphone 64G就是一个sku;
3.购物车如何处理?
只要是用户访问,用,就会生成一个uuid作为其唯一标识,但是登录过后得用户则会生成一个带有其用户名得对象,当我们添加购物车得时候,就是用这个作为key;
(1)添加商品得时候,要是登陆了,就放到redis里面;没登陆就用cookie里面得uuid作为key,之后存放在redis;
(2)查询得时候,先看登陆没,没登陆,用cookie里面得uuid直接查询;登陆了首先先看看redis里面有没有key为uuid得查,之后将其合并到用户id作为key得里面;要是没有就直接查;
4.单点登录得核心是什么?
单点登录得核心就是在多个系统之间共享身份信息;
5.单点登录,是http无状态得,别人模仿如何在后端处理?
别人模仿浏览器发送http请求,一般是无法识别得,所以我们需要使用https这种安全得协议,保证传输过程中无法被窃取信息;
6.别的网站使用爬虫技术怎么办?
(1)验证码;
(2)单位时间内限制访问次数;
(3)实在是访问多得话还可以设置黑名单;
7.订单得数据量太大,想把订单分部到多个表中,想用一条sql查出所有?
(1)mycat数据库中间件,将多个表进行统一管理,逻辑上是一个表;
(2)如果在一个库中得话,可以使用union将其连接起来;
8.两个用户买一件商品,但是库存只有一个我们怎么办?
(1)行级锁,可以使用乐观锁,更新商品之前将其锁定;
(2)Redis管理库存就可以了;
9.数据库实在是压力大了怎么办?
(1)读写分离
(2)分库分表
10.两个用户登录如何挤掉另一个?
(1)如果不需要强制对方下线得话,可以采用就查一下有没有这个id得session在,有的话,将那个session删掉。
(2)如果需要强制对方下线得话,可以采用websocket;
11.如何防止重复下单?
(1)使用解决接口幂等性的方式去防止重复下单。
(2)可以以查询用户id的形式去防止用户重复下单。
1.QPS大概在多少为正常,优化方案?
(1)实现的系统业务逻辑处理完成,最开始的这个商品类目页面大概只有50左右,但是有了缓存之后大概能到300-400差不多。
(2)使用的工具为jmeter。
-QPS最开始不经过中间节点的时候有个几百;但是后来增添了Nginx以及SpringCloud之后,这个就降低了一些,可能由于网络等原因。
(1)针对单一接口,首页分类的部分进行优化,优化前的QPS只有个位数。
(2)首先是对页面进行静态化处理,就是把图片进行动静分离,放到服务器里面,这样就能直接缓存。
(3)之后查询后的进行Map存储,不再都从数据库里面进行查找。
(4)之后使用redis进行优化,达到了400左右。
(5)使用JMETER进行操作的。
(6)本地环境下进行部署的。
(7)堆内存设置是100M。
2.性能和压力测试需要注意的点?
(1)Linux环境和Windows环境的区别。
(2)多个服务之间需要在同一个网之内。
(3)CPU,磁盘内存等规格,以及网络延迟等都需要注意。
3.系统得瓶颈在哪?
(1)数据库的瓶颈:可以考虑对数据库进行分库分表,将不同的业务放在不同的数据库里面,防止大量的请求在同一个数据库里面。
(2)服务的拆分:在合理范围内尽可能的拆分,拆分以后同类服务可以通过水平扩展达到整体的高性能高并发,同时将越脆弱的资源放置在链路的越末端,访问的时候尽量将访问链接缩短,降低每次访问的资源消耗。(同一个服务部署多份)
将一些图片啊,资源类的尽量提前展示。
4.UUID为什么不适合作为索引?
(1)因为其占用空间比较大,会影响性能。
(2)B+树的索引是会分块的,如果是有序的,数据塞进去,比如page2没满,这样就会查到page2后面,但是要是无序了,如果插在page1里面这个块就会分开再合并。
5.进行数据查询的时候,有商品有详情,怎么优化?
先查询直接出现的部分数据,之后异步任务去查询详情。
一次性查询的太多了,就会产生特别大的对象,导致可能直接进入老年代了,这样就时间长甚至会宕机。
6.如果我们有大量对于数据库的修改,我们怎么同步缓存?
(1)首先就是用消息队列,去向mysql里面去进行更新。
(2)更新完了会产生binlog。
(3)Binlog里面的东西我们可以做一个监听,监听的到的结果用消息队列去向redis一条一条同步就可以了。
7.秒杀过程的性能瓶颈在哪?
就是在我们工单的生成以及对数据库的修改的过程是最大的性能瓶颈-使用消息队列。
8.如何设置线程池的各个参数的大小?
(1)核心线程数为每秒的任务数除以(1/每个任务的时间);8020
(2)队列大小为核心线程数 / 任务的消耗时间 * 任务最大的等待时间;
(3)辅助线程 = (最大线程 - 任务队列长度) / 每个线程每秒处理的任务数字。
9.如何保证库存不超卖?
(1)首先我们可以考虑将库存放在redis里面,由于他是一个单线程的,所以不会超卖。
(2)可以考虑使用乐观锁,这样也可以保证不超卖。
10.如何修改密码?
(1)首先先走一遍登录流程,之后验证无误进行密码修改。
(2)修改之后需要删除掉当前的登录信息,如果不删除的话会导致前面的也能登录。
(3)删除之后可以使用websocket直接将用户踢掉。
11.为什么使用双token机制去进行登录验证(保持长登录)
(1)短token保证用户活性(做认证请求)。
(2)长token保证用户登录状态。
12.双写模式下的缓存处理方案?
(1)先写缓存,再写数据库:如果更新完了缓存,再更新数据库的时候挂掉了,这样就会导致在缓存里面更新的数据丢掉了,无法获取了。
(2)先写数据库,再写缓存:两个服务同时请求写入数据库,A写完了,没更新呢,B就写完了之后更新了,这时候A更新了,这样B更新的就丢失了。
(3)先删缓存,再写数据库:A更新,删除缓存,之后刚要写数据库,这时候B的查询来了,发现缓存没东西,之后查询完了,这时候又切换到A了,A写入数据库,这时候B写入缓存,这时候缓存和数据库不一致了。
(4)先写数据库,再删除缓存(同时为缓存设置一个过期时间):这种情况下很难出现问题,出现了必须刚好赶在缓存失效,同时查询到空,还没更新到内存中,之后又有插入还先插入完成了,之后这头又扔进缓存了,这个太难了。
(5)还可以缓存双删除,先删除,之后存数据库,间隔一小段时间,之后再删除一次缓存。
13.限流?
–信号量计数:就是使用Semphore设置一个最大的能够承受的信号的个数来保证这个东西能不能行,之后每次用完了释放,保证同时能有多少个并发。
–线程池隔离:就是使用线程池和阻塞队列,阻塞队列的长度到达了一定的长度,就采用一定的拒绝策略来保证低一些的流量,但是要注意控制线程的个数,要不然过大的线程数会带来创建线程以及上下文切换的巨大压力。
–窗口计数:在redis里面设置一个key,这个key一分钟就过期,之后每次访问都让这个key对应的值进行自增,到了100我们就不让其访问了,这样简单粗暴,但是可能并发量大一下子把缓存打崩了。
–令牌桶计数:就是创建一个桶子,之后定时的向里面塞东西,可能一秒一个,之后我们需要从桶子里面拿到东西才能给流量,否则不行。
高可用
1.什么是高可用?
其实就是描述一个系统在绝大部分情况下都是可用的。
2.什么情况会导致系统不可用?
(1)黑客攻击
(2)硬件故障,比如服务器坏了
(3)并发量/用户请求激增导致整个服务宕机或者部分服务不可用
(4)代码中的问题导致内存泄漏
(5)网站架构例如nginx或者数据库突然不可用了
3.如何保证高可用?
(1)可以使用一些代码检测工具诸如sonar。
(2)对脆弱的节点可以使用集群。
(3)系统瓶颈数据库部分使用缓存。
(4)大量的流量可以使用限流和熔断。
4.实现负载均衡的几种方式?
(1)HTTP的方式,就是计算之后重定向。
(2)DNS的方式。
(3)反向代理服务器的方式。
5.分布式Session的解决方案?
(1)使用特定的算法,保证来自某一个IP的请求全部落在同一个服务器上(服务器宕机,丢失Session)。
(2)每一台服务器都保存所有Session(占用过多的内存)。
(3)使用第三方的控件保证(Mysql或者是Redis)。
高并发
1.什么是消息队列,为什么使用消息队列?
消息队列是分布式系统中重要的组成组件之一,使用消息队列主要是为了通过异步处理提高系统的性能和削峰,降低系统耦合。
(1)通过异步处理提高系统性能(减少响应所需时间)。
(2)削峰/限流。
(3)降低系统的耦合性。
但是我们在这种操作完成之后,不能够立即返回用户成功或者失败,我们需要知道其可能成功也可能失败,所以只能先给用户返回一个状态,即等待返回结果,之后过一会再将结果通过电子邮件之类的形式去返回。
2.如何保证消息的可靠投递和消息的不重复消费?
(1)消息的可靠投递可以有消息的确认机制来保证。
(2)不重复消费可以为每一个消息携带一个唯一id,消费者接受到之后先去redis/db里面查询一下,看看有没有,没有的话就执行业务,有的话就认为消费过了。
3.为什么HashMap的扩容是0.75?
(1)如果是1,那么比如表长为8,我们需要堆满了8个就可以扩容了,但是如果要是都在一个节点上,那么就会查询效率很低,时间代价大。
(2)如果是0.5,那么就会频繁的扩容,1M的数据,2M的容量。
(3)0.75是综合时间和空间考虑的。
4.为什么是2倍扩容?
(1)首先是因为这样就能把mod运算变成位运算。
(2)并且只需要计算哈希值的高位即可,看看是1那就需要重新计算,0得话就不需要动了。
5.为什么HashMap有线程不安全的问题?
(1)JDK1.7有并发死链的问题
(2)JDK1.8,如果两个线程都在做Hash值得插入,并且找到了一个位置,A先插完了之后切换到B了,这时候就会覆盖掉;同时,A读size得时候,B也读了,都插入完了,再写回去得时候,又覆盖了,插入两个,写了一个。
6.使用消息队列可能带来得问题?
(1)系统得可用性降低:引入了之后就需要考虑mq得稳定性,消息确认等等相关问题了。
(2)系统复杂性提高:思考消息丢失等。
(3)一致性问题:丢失消息,有的有有的没有这种情况。
7.如何保证消息队列发送端/接收端的可靠性?
(1)本地建立一张信息表,消息数据与业务数据保存在同一个数据库实例中,利用本地数据库的事务机制,提交成功了,就将任务发送到消息队列里面,没成功就重传。
(2)保证幂等性(就是多少个重复都只消费一次)
8.数据库压力大可以采用主从分离来解决,那么主从分离存在什么问题?
(1)数据一致性差,可能会有延迟。
(2)我们可以延迟读取,就是比如同步需要0.5s,那么我们就1s之后去读,比如我们插入完成之后,可以显示一个成功页面,之后返回,这样就有1s了。
(3)主库分库,降低压力。
9.引入分库分表之后有什么问题?
(1)跨库join,分开之后我们需要手动进行数据的封装。
(2)事务问题,提交数据,需要多个数据库同步。
(3)分布式id。
10.如果已经分库分表了,怎么查询商户/用户的所有订单?
(1)可以考虑使用多线程去查询,之后再service层去组织起来。
(2)如果我们用用户id作为shardingkey,那么很快就能定位到在哪个库哪个表里面,但是要是我们要查询商户,就不容易了。
(3)最简单的就是可以搞多一个表,商户和用户id的表,这样可以根据商户查询用户id,之后用户id查询订单。