项目面试准备
一、黑马点评
1.项目介绍
我做的这个项目主要就是模仿大众点评,是使用springboot开发的前后端分离的项目,实现了商户的搜索、点评,好友关注、动态推送以及优惠券秒杀等功能。其中使用了redis做数据缓存,提高数据的访问效率,以及利用redis实现分布式锁解决一人一单的问题。此外,还利用了RabbitMq异步的创建优惠券订单,提高抢票的相应速度。
2.项目中遇到的难点
我认为项目中主要的难点在于优惠券秒杀这个功能的实现。首先这个需要考虑到库存超卖和一人一单问题,针对库存超卖问题,我使用的是乐观锁的思想,考虑到库存如果使用版本号来判断扣减库存是否成功的话,会出现库存充足的情况下依旧会抢券失败的情况。所以我最后没有使用版本号,而是在给扣减库存的sql语句加个where条件判断库存数量是否大于0。然后针对一人一单问题,因为数据库的一人一单判断和库存扣减操作是非原子性的,所以,我的解决方案是我一开始是给用户id放到常量池中,使用synchronized这个关键字获取到用户id的锁后进行相关的抢票校验代码。但后面考虑到在多个相同服务部署的情况下,锁无法跨服务感知,所以使用了redis来创建分布式锁。用的是redisson这个类来创建的锁。
后面考虑到抢券流程会经过查询优惠券、查询库存、查询一人一单、扣减库存、创建订单等多次的数据库操作,导致的响应速度可能比较慢,所以我就用把优惠券的信息缓存到redis中,在redis上进行优惠券的校验流程。校验成功后使用rabbitMq来异步通知订单的创建,加快抢券的响应速度。
3.简历上的技术亮点
(1).基于redis做缓存
原话: 基于Redis做数据缓存,使用布隆过滤器+缓存空值解决缓存穿透问题,通过Sentinel的限流和熔断机制解决缓存雪崩问题、利用互斥锁和逻辑过期解决缓存击穿问题。
提问:为什么要使用redis做缓存?
因为redis是把数据存放在内存中,而mysql数据库则是存放在磁盘中,磁盘的访问速度比内存要慢的多,使用redis可以提高数据的访问效率。在项目中,我主要是对商户信息做了数据缓存,因为考虑到商户信息访问量很多,如果每次都访问数据库获取商户信息,会给数据库带来很大的压力。
提问:怎么解决缓存穿透问题?
缓存穿透指的是请求访问的是数据库中不存在的数据,因为数据库中数据不存在,所以redis中也无法命中,导致每次的这种无效请求都会打到数据库。所以,我使用布隆过滤器和缓存空值这两种方法来解决。在访问redis和数据库时会先经过布隆过滤器的判断来决定是否进行后续的查询。
布隆过滤器底层是一个大型位数组(二进制数组)+多个无偏hash函数。
布隆过滤器针对已有的数据,肯定可以校验通过,而不存在的数据可能会有漏放的风险存在。所以我还使用了缓存空值的方式,若请求的商户id不存在,则会在redis中缓存这个商户id,并赋予空值和一个较短的过期时间,来减轻数据库的压力。
提问:缓存雪崩问题?
缓存雪崩指的是在同一个时间段内,大量的key同时过期,或者说redis突然宕机,导致请求都直接打到数据库的情况。
我项目里是给每个key设置过期时间值时,除了固定的过期时间常量外,再额外添加随机的秒数,保证key的过期时间不会过于接近。
提问:缓存击穿问题?
缓存击穿问题指的是对某一个key的访问量很大,当这个key突然过期,且恢复到缓存的时间比较久的情况下,这段时间内的请求都会打到数据库。
我采用的是互斥锁和逻辑过期的方式来解决的。只使用互斥锁的话,会导致多个请求在获取锁失败的情况下陷入阻塞状态,或者是直接返回请求失败的信息,用户体验上来说并不是很好。所以可以再加个使用逻辑过期的方式,获取到锁时,通过异步的方式刷新缓存。无论有无获取到锁,都是直接返回旧值。所以数据及时性方面还是有欠缺的。基于Token和Redis实现用户认证,利用双拦截器机制(刷新Token拦截器+登录拦截器)实现无感刷新与权限控制,通过ThreadLocal实现用户信息透传,提升接口安全性。
(2)续约token
嗯,我项目中前端调用登录接口后,后端会生成一个随机token令牌,返回给前端,并以token为key,用户信息为value存入redis中并设置过期时间。
用户访问页面时,使用了两个拦截器,一个是刷新token拦截器,一个是登录拦截器。其中刷新token拦截器优先级最高,是全接口拦截且全放行的拦截器,主要作用就是如果请求头有携带token,那么就解析token,要是redis中存在value值,就存入threadlocal中,并把redis的这个key的过期时间进行延长。至于登录拦截器则是专门对必须登录才能访问的接口进行拦截,会判断threadLocal中用户信息是否为null,如果为null就进行拦截。