黑马点评面试前复习
文章目录
- 1.短信登录
- 1.1 问题1:基于session你是如何实现短信登录的?
- 1.2 问题2:为什么用redis代替session存储?
- 1.3 问题3:如何基于redis实现短信登录?
- 1.4 问题4:用户一直在访问的过程中,突然用户登录的redis过期?
- 1.4 问题5:上述拦截的路径都是需要登录的,如果用户访问的是不需要登录的怎么办?token还是会过期了
- 2.商铺查询缓存
- 2.1 问题1:请你谈谈添加redis缓存的逻辑
- 2.2 问题2:缓存更新策略有哪些,一般如何选择?
- 2.3 问题3:操作缓存和数据库是删缓存还是更新缓存?
- 2.4 问题4:一般是先删缓存还是先操作数据库?
- 2.5问题5:什么是缓存穿透?如何解决缓存穿透问题?
- 2.6问题6:什么是缓存雪崩?如何解决缓存雪崩问题?
- 2.7 问题7:什么是缓存击穿?如何解决缓存击穿?
- 2.8问题8:逻辑过期时间你具体是怎么设置进去的呢?
- 3.优惠券秒杀
- 3.1问题1: 秒杀订单的主键一般如何设置
- 3.2问题2:实现优惠券秒杀下单的流程是如何的?
- 3.3 问题3:为什么会出现超卖问题?如何解决超卖问题?
- 3.4 问题4:乐观锁解决失败率高什么情况如何解决?
- 3.5 问题5:如何实现一人一单的功能呢?
- 4.分布式锁
- 4.1 问题1:锁监视器一般一个jvm对应一个,如果集群模式下如何保证锁成功?
- 4.2 问题2:什么是分布式锁?如何使用
- 4.3 问题3:另一个方法想要调用定义了事务的方法,事务如何生效?
- 4.4问题4:线程1业务阻塞锁超时释放,导致线程2获取锁业务没执行完,线程1释放锁问题如何解决?
- 4.5问题5:判断锁标识通过到释放锁中间业务阻塞,这可能导致释放别的线程的锁,如何解决?
- 4.6问题6:基于setnx实现的分布式锁有哪些可以优化的地方?
- 5.秒杀优化
- 5.1问题1:上述秒杀都在mysql实现,效率较低如何优化?
- 5.2 问题2:基于阻塞队列实现会有什么问题?
1.短信登录
1.1 问题1:基于session你是如何实现短信登录的?
1.用户提交发送请求验证码。后端校验用户手机号是否正确,正确的话先保存验证码和手机号到session,并返回验证码到前端
2.用户点击登录。校验手机号和验证码是否和服务端存储再session一致,一致再判断手机号再数据库是否存在,存在,保存到session中。不存在用户信息保存到数据库,再写入session中
3.实现登录校验。自定义一个登录拦截器,获取请求中携带的sessionid,再从session中找到对应的user信息,并保存用户信息到ThreadLocal当中,然后放行
1.2 问题2:为什么用redis代替session存储?
1.session只能存储在某一个tomcat服务器内,如果出现多台tomcat前端访问并不知道自己的信息存储在哪一台,因此需要使用redis存储session,并且redis也刚好符合内存存储,可以实现数据共享,并且是键值对形式存储
1.3 问题3:如何基于redis实现短信登录?
1.用户点击请求验证码。后端生成验证码,并把(手机号,验证码)设置为键值对存储到redis中,并设置有效期
2.用于点击登录的时候,校验手机号通过,判断发送的验证码和redis中该手机号存储验证码是否一致,通过的话。查询数据库,用户信息以Hash形式存储到redis中,其中以UUID生成的随机值作为key(token),并将其返回给前端。并为redis这组数据设置30min有效期
1.4 问题4:用户一直在访问的过程中,突然用户登录的redis过期?
- 依然可以在登录校验中进行设置,登录拦截器中获取前端传递的token,从token中获取redis用户,并将用户保存到threadLocal当中,并重置时间为30分钟,因此只要有请求经过了拦截器,就会刷新时间
1.4 问题5:上述拦截的路径都是需要登录的,如果用户访问的是不需要登录的怎么办?token还是会过期了
1.再添加一层拦截器,拦截所有请求路径,把保存ThreadLocal和刷新的动作都放在这里执行 。第一个拦截器没有token或者没有用户直接放行。第二个拦截器判断ThreadLocal是否有值,无就拦截
2.商铺查询缓存
2.1 问题1:请你谈谈添加redis缓存的逻辑
1.用户发起请求,先到redis中查询缓存,缓存命中返回,未命中,查询数据,数据写入redis,再返回给前端
2.2 问题2:缓存更新策略有哪些,一般如何选择?
有内存淘汰,超时剔除,主动更新。内存淘汰是redis内部内存不足,自动淘汰部分数据。超时剔除是给缓存添加过期时间,到期自动删除。主动更新是编写业务逻辑,修改数据库的同时修改缓存。
低一致性需求时:内存淘汰机制。如店铺类型的查询缓存
高一致性需求时:主动更新+超时剔除。如店铺的详情信息
2.3 问题3:操作缓存和数据库是删缓存还是更新缓存?
一般选择删缓存,更新数据库,直接删除缓存,等查询的时候再更新缓存(事务保证同成功/失败)。如果是更新缓存的话,如果数据库更新N次,缓存也要更新N次,压力较大
2.4 问题4:一般是先删缓存还是先操作数据库?
一般是先操作数据库再删缓存。如果是线程1先删缓存,线程2查缓存未命中,查数据库,更新缓存。线程1再更新数据库,此时容易出现数据不一致性的问题。缓存操作一般是微秒级别的,更新数据库较慢,因此这种情况容易发生。
先操作数据库再删缓存。可以大大避免这种情况。先执行慢操作,再执行快操作,降低两者之间其他线程趁虚而入的可能性。
2.5问题5:什么是缓存穿透?如何解决缓存穿透问题?
缓存穿透指的是客户端请求的数据在缓存和数据库中都不存在,这样缓存不会生效,请求都打到数据库里面。
被动: 采用缓存空对象的形式,存到redis中,并设置较短的过期时间
主动:增加id复杂度,做好基础的格式校验,加强用户权限
2.6问题6:什么是缓存雪崩?如何解决缓存雪崩问题?
缓存雪崩指的是同一时段大量缓存的key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
给不同的key的TTL添加随机值
给业务添加多级缓存
2.7 问题7:什么是缓存击穿?如何解决缓存击穿?
缓存击穿问题也称为热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数请求访问数据库带来巨大压力。
解决方案:给缓存重建加互斥锁,一个重建,其他线程等待。或者设置逻辑过期时间,交给另一个线程做缓存重建逻辑(开启互斥锁),自己返回旧数据给请求。前者维护一致性,后者维护可用性
2.8问题8:逻辑过期时间你具体是怎么设置进去的呢?
重新定义一个类,定义逻辑过期字段,和data字段用来存放原本的店铺数据。然后函数new这个类,把这两个字段的数据放进去
3.优惠券秒杀
3.1问题1: 秒杀订单的主键一般如何设置
使用全局ID生成器,(符合位+31位时间戳+32位序列号),序列号是redis自增的数据。
也可以使用UUID生成,但是都是随机的。不够友好
3.2问题2:实现优惠券秒杀下单的流程是如何的?
前端提交优惠券的id,查询优惠券信息,判断是否开始或结束,判断库存是否充足,都满足就返回订单的id给前端
3.3 问题3:为什么会出现超卖问题?如何解决超卖问题?
库存剩下1的时候,100个线程进来查询,有部分刚好同时查询到库存等于1,都执行扣减操作,就会出现超卖问题。
解决超卖问题:1.版本号法,数据库字段增减一个版本号字段,修改了库存,版本号加一,其他线程修改判断不一致就停止修改
2.CAS法:直接拿库存本身做判断 ,比较数据有没有发生变化
总结:更新时候查询的版本和之前查询到的版本是否一致
3.悲观锁,直接给更新的部分加锁
3.4 问题4:乐观锁解决失败率高什么情况如何解决?
库存大于0的时候,就让他执行更新操作,不是更新查询的库存和之前的库存一致。因为库存大于0的时候并发修改时不会造成超卖问题的
3.5 问题5:如何实现一人一单的功能呢?
扣库存之前,添加判断订单表里面用户id和优惠券id是否已经存在,存在就拒接执行。同时给这段代码加悲观锁方案(因为乐观锁方案是用在更新操作上的)
4.分布式锁
4.1 问题1:锁监视器一般一个jvm对应一个,如果集群模式下如何保证锁成功?
使用分布式锁方案,jvm外部的一个锁监视器
4.2 问题2:什么是分布式锁?如何使用
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
利用redis的setnx方法实现互斥效果,并设置超时释放,如果获取锁了返回true,其他线程想获取就会返回false
4.3 问题3:另一个方法想要调用定义了事务的方法,事务如何生效?
因为事务时被spring生成动态代理得,直接调用该方法,使用得时本地方法,没有事务功能,所以要用AopContext.currentProxy().对应方法获取到代理对象,此时该方法就有事务功能
同时pom文件添加aspectjweaver依赖
启动类添加注解暴露代理对象@EnableAspectJAutoProxy(exposeProxy=true)
4.4问题4:线程1业务阻塞锁超时释放,导致线程2获取锁业务没执行完,线程1释放锁问题如何解决?
释放锁的时候,添加一个锁标识,判断是不是自己的锁,再决定是否释放锁
4.5问题5:判断锁标识通过到释放锁中间业务阻塞,这可能导致释放别的线程的锁,如何解决?
需要判断锁标识和释放操作的原子性
使用lua脚本实现分布式锁的释放锁逻辑,主要调用的是redis.call方法
使用RedisTemplate调用lua脚本,stringRedisTemplate.execute()方法
4.6问题6:基于setnx实现的分布式锁有哪些可以优化的地方?
1.不可重入问题
2.不可重试。获取锁只尝试一次就返回false,没有重试机制
3.超时释放。原本业务执行时间就较长
4.主从一致性。如果Redis提供主从集群,主从同步存在延迟,当主宕机时,从未同步完成主的锁节点,新的线程进来就又会setnx,导致锁失效。
解决方案,推荐使用redisson(内置分布式锁的实现方案等等)
redisson也可以实现可重入的作用,实现原理何Reentertlock一致,内部维护一个锁计数器。
利用watchDog延续锁时间,利用信号量设置锁重试等待
Redisson的multiLock,设置多个独立的redis节点,所有节点都获取可重入锁,才算获取锁成功
5.秒杀优化
5.1问题1:上述秒杀都在mysql实现,效率较低如何优化?
将判断库存和一人一单的功能放在redis执行,通过Lua脚本实现。生成订单,先返回给前端。至于真的下单的操作异步执行,通过阻塞队列操作就行。
5.2 问题2:基于阻塞队列实现会有什么问题?
1.jvm内存限制问题,超出jvm内存溢出
2.jvm内存安全问题,服务重启阻塞队列内容丢失