黑马点评大总结

8.2.1 短信登录

  • 首先是用户提交手机号,后端将生成的验证码以及用户信息存入session中,用户登录时进行拦截并从session中拿出来信息校验,并把用户信息存入ThreadLocal中
  • session共享问题:每个tomcat有自己的一份session,分布式、微服务下有多个tomcat实例,之间的session无法共享
  • 解决: 负载均衡器通过特定算法如IP哈西,保证同一用户的请求始终路由到同一服务器。(失去负载均衡的灵活性)session复制,所有服务器同步session变更。(带宽消耗大)集中存储,将会话数据存储在外部,如redis客户端存储。(安全性挑战)
  • 采用后端生成token存入redis,前端请求头携带token校验身份 方案,并通过两层拦截器解决token刷新TTL问题(背景是有些页面不要求登录即可访问),先拦截所有请求判断token有无,有则刷新并存信息与ThreadLocal,后拦截特殊路径判断是否登录(ThreadLocal中是否有用户信息),有则放行,无则拦截显示请登录 8.2.2 商户缓存
  • 查询逻辑:先查询缓存再查询数据库再写入缓存(背景:老查询数据库,压力很大)
  • 缓存更新策略有内存淘汰、超时剔除、主动更新
  • 缓存双写一致性问题:数据源来自于数据库,数据库发生变化时也要更新缓存
  • 采用先更新数据库再删除缓存的方案,但仍有问题:多线程环境下可能出现缓存正好过期去查询数据库得到旧值,更新操作完成并删除缓存(空缓存)然后旧值被写入(极小概率)
  • 解决: 设置较短的TTL,减少缓存与数据库不一致的时间窗口将策略改为 延迟双删策略
  • 引入缓存提高查询性能,必然会引出一系列问题,上面的缓存双写一致性问题为其一,其二就是缓存穿透问题,若查询本就不存在的数据,每次都会打到数据库上,增加压力
  • 解决: 缓存特殊值:查询数据库后返回特定值比如defaultNull并设置ttl写入缓存,所有命中缓存后增加判断value值一逻辑操作。问题:只能应对正常业务问题,不能应对黑客使用大量不同的key进行攻击问题(还是会全部打到数据库上)布隆过滤器:本质是BitMap+多个Hash函数,将所有有效key存入bitmap,挡在redis前多一次判断。问题:由于其特性(hash冲突问题)导致存在误判(有的不一定有,但无的一定没有)
  • 当大量缓存key同时失效或者redis突然宕机就,导致大量请求打到数据库,造成缓存雪崩
  • 解决: 给不同的Key的TTL添加随机值利用redis集群提高服务的可用性给缓存业务添加降级限流策略给业务添加多级缓存
  • 当一个高并发访问的key突然失效(热门商户),无数的请求打到数据库,造成了缓存击穿问题
  • 解决: 加互斥锁:缓存未命中则加分布式锁(set nx ex)如果失败则进行方法休眠+递归重试,成功则去查询数据库并写入缓存后释放锁。问题1:递归容易造成OOM,改为休眠+自旋重试。问题2:其他线程在重试,当缓存写好锁被释放后,又进行抢锁,改为双检加锁(在自旋过程中尝试去命中缓存),锁被释放后自旋的线程也无需在抢锁。缺点:死锁问题,且只能串行执行,性能受影响逻辑过期:缓存击穿问题主要是key的TTL到了,不设置TTL,而是吧TTL设置到value中,通过逻辑判断是否过期。重新封装一个类包含过期时间以及Object数据。判断过期后获取互斥锁(保证异步任务只提交一次),将查询数据库写入缓存的操作提交给异步线程处理(串行处理也可以)。返回过期数据。缺点:其他线程返回的是脏数据(已经过期的)总结:方案1是串行,执行加锁保证只有一个线程去查询数据库,方案2是异步执行,加锁保证查数据库写入缓存的操作只被执行一次,且先返回已经过期的数据问题:只要加分布式锁(set nx ex)就需要考虑业务未执行完锁超时过期问题,但此处业务是查询以及写入逻辑,即使锁过期导致其他线程获取锁进入逻辑,无非就是多了个线程执行缓存重建操作,但在删除锁时可能会出现张冠李戴的问题(DEL命令即使key不存在也会返回成功),无伤大雅。如果是扣减库存之类的操作就会出现超卖现象,是不允许的,需要增加watchdog。 8.2.3 优惠券秒杀
  • 全局唯一ID:规律性不能太明显,受单表数据量的限制。应当满足 唯一性、高可用、高性能、递增性、安全性。由符号位0+时间戳31bit+序列号32bit(秒内的计数器,支持每秒产生2^32个不同ID)
  • 下单业务逻辑:提交优惠券id - 查询信息 - 判断是否在活动期间 - 库存是否充足 - 扣减库存 - 创建订单
  • 超卖问题: 直接扣减库存在高并发情况下会导致超卖问题。解决:加悲观锁(太重);加乐观锁(CAS):执行sql时多一个库存容量判断是否大于0
  • 一人一单问题:在判断库存后增加 - 根据优惠券id+用户id判断是否已经下过单
  • 问题:多线程下(用户多次点击)可能同时判断出未下单,然后执行下单逻辑造成一人多单
  • 解决:加synchronized锁(判断是否已经购买+下单逻辑)保证只有一个线程执行下单逻辑,加到方法上,锁的粒度太粗,改为在代码块上加锁,用intern()方法从常量池中拿用户数据加锁,因为直接用userId的话不是同一个对象,每个线程都有自己的ThreadLocal,其中的用户信息对象是分别new的,不唯一。问题:方法被spring事务控制,如果在方法内部加锁,可能导致方法事务还没有提交,但是锁已经释放的问题(锁在方法结束时释放,但事务可能还未提交。其他线程可能读取到未提交的数据(脏读),或者覆盖未提交的修改。)
  • 通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式(分布式)下还会有超卖问题(用户连多次点击被负载均衡到不同的服务上)
  • 解决:换用分布式锁,在判断完库存充足之后,执行逻辑前先创建锁key,这里以userId作为key(保证该用户的多次请求对应一个分布式锁)。加锁后通过代理对象执行接下来的方法(一人一单判断+扣减库存+创建订单),因为该方法加了@Transactional注解,一个非事务方法调用事务方法,事务不会生效。
  • 问题:若持有锁的线程在执行逻辑时阻塞了,导致锁超时提前释放,这时候其他线程(该用户的相同请求)就会持有锁执行后面逻辑,可能造成超卖问题。(也会出现误删问题,但不是本质问题,重点还是逻辑被执行了多次)
  • 解决:(锁误删解决,属于辅助方案),在获取锁时UUID+ThreadID作为value,在unlock()的时候判断value值是否为自己的锁。问题:unlock的逻辑不是原子的,极端情况下还是会出现误删情况,使用Lua脚本保证逻辑的原子性。(注意:解决误删问题并不能解决超卖问题,这个方案解决的是次要问题-误删锁,属于辅助手段,要想解决这里的超时释放问题得用watchdog机制)
  • 当下分布式锁:需要给锁续期解决超时释放问题,锁不可重入,主从一致性
  • 解决:Redisson中的RedLock 可重入原理:底层Hash结构+Lua脚本实现,大key标识锁是否存在,小key标识锁被那个线程持有重试原理:抢锁过程中,通过tryAcquire进行抢锁,先判锁是否存在,再判断是否属于当前线程watchdog原理:在获取锁后开启一个后台线程监控锁的ttl,超过三分之一后重置TTL 8.2.4 秒杀优化
  • 问题:当前逻辑全是串行执行
  • 解决:将部分逻辑单独提出来,异步执行,在判断用户可以下单后直接返回给用户成功,由后台完成剩下的下单操作,将判断逻辑放入redis中,库存是否足够、是否为一人一单,相应的信息存入redis中,判断后开启异步 queue 难点1:如何在reids中判断,将商品库存信息以及用户下单信息(set集合)提前存入,有库存以及set集合中没有该用户则说明可以下单。基于Lua脚本实现资格判断保证原子性,将订单id返回给前端难点2:如何知道最后下单是否成,将优惠券id和用户id封装后存入阻塞队列 开启独立线程不断从阻塞队列中获取信息实现异步下单逻辑
  • 问题:阻塞队列在JVM内存中,容易OOM;JVM挂了数据订单数据全没了
  • 解决:MQ 基于List的MQ:只支持单消费者,无法避免信息丢失(内存占用多时内存淘汰机制可能淘汰List中的数据,持久化没法保证数据100%持久),使用LPOP/RPOP命令消费消息后,消息立即从队列移除基于Stream的MQ:增加消费者组,生产可用 8.2.5 探店点赞
  • 对于其他用户发的博客可以进行点赞,开始逻辑为 直接数据库+1
  • 问题:用户可以无限点赞,应当为点赞/取消且点赞按钮高亮显示
  • 解决:增加isLike属性,标志是否被当前用户点赞;使用redis set集合判断是否点赞过,key为Blog,value为userId
  • 问题:检查Redis后到执行数据库更新之间存在时间窗口,可能导致多个请求同时通过检查,造成重复更新。
  • 点赞排行榜,显示出最早点赞的几名用户(如TOP5),有唯一性又具备有序性的 zset集合 以时间戳为score存入用户信息 8.2.6 好友关注
  • 一张tb_follow表标示关注信息,根据前端传来的id直接更新数据库保存关系信息
  • 增加共同关注功能需要求交集,利用set集合,存储用户的关注列表,userId作为key,然后用set集合的api求共同关注的用户id,关注则先存入数据库再更新redis,取关则先删除数据库再删除redis
  • 发动态时提醒粉丝,使用 Feed流 完成推送功能,采用TimeLine模式
  • 拉模式:查看时拉去所有Blog然后排序展示
  • 推模式:博主发送Blog时将信息推送给所有粉丝,就是我们在保存完探店笔记后,获得到当前博主的粉丝,然后把数据推送到粉丝的 redis 中去
  • 推拉结合:推送给活跃用户,普通用户拉取
  • 问题:因为数据是不断变化的,传统分页会导致展示重复的数据
  • 解决:Feed流的滚动分页 8.2.7 附近商户
  • redis使用GEO结构存储商家的经纬度以及店铺id,以店铺的typeId为key,将对应的商铺信息存入value 8.2.8 用户签到
  • 直接用数据表记录数据量太大
  • 解决:使用bitmap中的0,1来记录用户的签到情况
  • 吧年和月作为bitMap的key,每次签到把当天对应bitmap位置上的下标从0变为1
  • 获取某月的bitmap数据(十进制展示),与1进行与运算看看此位是否为1,从而进行签到统计(签到总数、连续签到)
全部评论
点赞 回复 分享
发布于 今天 14:31 广东
mark学习
点赞 回复 分享
发布于 今天 13:46 湖北

相关推荐

评论
7
45
分享

创作者周榜

更多
牛客网
牛客企业服务