黑马点评项目笔记

发布于:2024-05-09 ⋅ 阅读:(34) ⋅ 点赞:(0)

短信登录

基于session的短信登录

  1. 发送验证码,session保存验证码。
  2. 验证验证码,session保存用户信息。
  3. 校验登录状态,判断session里是否保存用户信息。

使用ThreadLocal保存用户信息,是方便后面业务获取当前用户,避免传参和频繁从session中取对象。

基于Redis实现共享session登录

集群的session共享问题:多台tomcat不能共享session存储空间,当请求切换到不同tomcat服务器时导致数据丢失。

  1. 发送验证码,Redis保存验证码。
  2. 校验验证码,用户信息保存到Redis中,返回token给前端
  3. 前端请求首部行中携带token(请求首部中添加Authorization字段)校验登录状态,判断Redis里是否有用户信息。

验证码:key是手机号,value是string类型,验证码。

用户信息:key是token,value是Hash类型,用户。

商户查询缓存

缓存,交换数据的缓冲区,存储数据的临时地方,读写性能好。

缓存的作用,降低后端数据库负载,提高读写效率降低响应时间

添加Redis缓存

  1. 商铺请求先访问Redis,Redis命中直接返回数据。
  2. Redis未命中,查询数据库,如果数据库中存在数据,再写入Redis中,否则返回404。

缓存更新策略:

  • 内存淘汰。内存淘汰自动删除部分数据。
  • 超时剔除。给缓存增加TTL,到期自动删除缓存。
  • 主动更新。先操作数据库,再删除缓存。

一致性也就是缓存中有值时,与数据库中一致。

低一致性需求:使用Redis自带的内存淘汰机制。

高一致性需求:使用主动更新策略,并以超时剔除为兜底。

主动更新:先删缓存再写数据库、先写数据库再删缓存、先写数据库再写缓存、读写穿透、写回。

读操作:

  • 缓存命中直接返回。
  • 缓存未命中则查询数据库,并写入缓存,设置TTL。

写操作:

  • 先写数据库,再删除缓存
  • 确保数据库与缓存的操作原子性。

先删缓存再写数据库的问题?

更新操作删除缓存后还没更新数据库时,查询操作缓存未命中读数据库旧值,更新操作更新缓存,查询操作写入缓存旧值。

读写穿透:读写缓存数据库同步服务。

写回:只更新缓存,异步更新数据库。

缓存穿透

客户端请求的数据在数据库和缓存中都不存在,缓存永远无法生效,请求会打到数据库上给数据库带来压力。

常见的解决方案:缓存空对象和布隆过滤器。

缓存空对象:在查询时缓存和数据库都未没命中,在缓存中缓存空值设置TTL。在查询缓存的时候判断是否是空值(不是nil)。

布隆过滤器:所有可能请求的值存放在布隆过滤器中,请求来时先判断是否存在,不存在直接返回错误信息。

缓存雪崩

同一时间大量的缓存key同时失效或者Redis服务宕机,大量请求到达数据库带来压力。

解决方案:

  • 设置随机失效时间。
  • 缓存预热。
  • Redis集群。
  • 多级缓存。
  • 业务添加多级缓存。

缓存击穿

热点key问题,被高并发访问并且缓存重建业务复杂的key突然失效,大量请求瞬间到达数据库带来压力。

解决方案:互斥锁和逻辑过期。

互斥锁方案

查询缓存未命中获取互斥锁,查询数据库重建缓存数据,写入缓存,释放锁。其他线程查询缓存未命中获取互斥锁失败,休眠重试直到缓存命中。

逻辑过期

缓存数据中添加逻辑TTL,查询缓存发现已过期,直接返回过期数据,获取互斥锁开启新线程重建缓存,写入缓存后重置逻辑TTL释放互斥锁。其他线程查询缓存发现已过期,获取互斥锁失败返回过期数据。

优惠券秒杀

全局ID生成器

分布式系统下生成全局唯一的ID工具。

基于Redis自增策略,订单ID由Redis中整型数据4个字节64位组成,1比特为0符号位,31位时间戳,32为序列号。使用Redis整型数据自增来生成ID。

秒杀券下单

实现优惠券秒杀:

  1. 判断秒杀是否开始或者结束
  2. 库存是否充足,扣减库存,添加订单

在高并发环境下,会出现超卖的问题(在库存量为1的时候,多个线程进入判断库存充足扣减订单,超卖订单)。

悲观锁:添加互斥锁,让线程串行执行,实现简单但性能一般。

乐观锁:不加锁,在更新时判断是否有其他线程在修改,性能好但成功率低。版本号法,在更新数据之前检查版本号,更新数据时修改版本号。

// 优化乐观锁成果率低的问题,在更新时同时检查订单是否大于0
seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();

一人一单

在更新前判断数据库订单表中是否相同用户相同秒杀券的记录,由于没有加锁同样会出现超卖的问题。

由于不存在数据更新,所以无法使用乐观锁,使用悲观锁synchronized给创建订单上锁。

一人一单的并发安全问题:在集群模式下,同一个用户在不同服务器上的请求会获取不同的锁(不同的JVM,不同的锁监视器),会超单。

分布式锁

满足分布式系统或集群模式下多进程可见并且互斥的锁。

基于Redis分布式锁

版本1:基于Redis的setnx命令实现分布式锁,设置TTL超时自动释放锁。

版本1的问题:线程1业务阻塞,锁TTL超时释放,线程2获取锁执行业务中,线程1执行完任务释放线程2的锁。

版本2:获取锁时存入线程标识(UUID+线程ID,不能只使用线程ID,不同JVM线程ID可能相同),释放锁时核对线程标识。

版本2的问题:线程1判断锁标识和释放锁之间阻塞,锁TTL超时释放,线程2获取锁执行业务前,线程1阻塞结束后释放锁。

版本3:使用Luna脚本完成判断线程标识和锁释放的原子性。

总结:基于Redis的分布式锁实现思路,利用setnx获取锁设置过期时间,保存线程标识。释放锁先判断线程标识是否一致,一致释放锁

基于Redis分布式锁优化

基于setnx实现的分布式锁存在的问题:

  1. 不可重入。同一个线程无法多次获取同一把锁。
  2. 不可重试。获取锁尝试一次就返回。
  3. 超时释放。如果业务执行时间耗时长,导致锁提前释放。
  4. 主从一致性。Redis采用主从集群结构, 主节点宕机且锁数据未同步给从节点,导致锁失效。

Redisson分布式锁工具。

可重入锁的原理:利用Hash结构记录线程标识和重入次数。

可重试的原理:利用pubsub机制,订阅锁释放的信号尝试获取锁。

锁不过期的原理:开启watchdog定时更新TTL。

主从一致性的原理:联锁,每台Redis服务器独立,获取锁时需要半数以上的每台服务器都获取锁。

秒杀优化

业务流程:

  1. 查询优惠券,判断秒杀库存。读数据库。
  2. 查询订单, 判断一人一单。读数据库。
  3. 更新库存。写数据库。
  4. 创建订单。写数据库。

优化方式,同步下单变成异步下单,读写数据库分离。利用Redis完成库存余量、一人一单判断,完成抢单业务。再将下单业务放入阻塞队列,利用独立线程异步下单。

基于阻塞队列的异步秒杀问题:内存限制,阻塞队列使用JVM内存,高并发情况下大量订单占用JVM内存。数据安全问题,服务重启或者宕机,阻塞队列数据丢失。

基于Redis的消息队列实现异步秒杀
  • 基于List双向链表实现。LPUSH和BRPOP阻塞获取。只支持单消费者,无法避免消息丢失
  • 基于PubSub实现。发布消息,订阅频道。不支持数据持久化,无法避免消息丢失
  • 基于Stream实现。多个消费者划分到一个消费者组,监听同一个队列。

Stream消费者组消息队列的特点:

  • 消息分流。同一个消费者组的消费者之间竞争消息。
  • 消息标识。消费者组维护一个标识,最后一个被处理的消息。
  • 消息确认。消费者获取消息后,消息处于pending状态,在pending-list中,当消息处理返回ack确定后才会从list中移除。

秒杀优化券方案总结:利用lua脚本完成库存余量、一人一单判断,完成抢单业务。将下单信息放到消息队列,开启独立线程异步下单,扣减库存使用乐观锁。

达人探店

点赞

在Redis中记录以blogid为key,点赞用户为value的集合。

点赞排行榜:使用SortedSet,点赞时带时间为score。

好友关注

关注

user_idfans_id多对多关系表中添加。

共同关注

为每个用户在redis添加一个关注集合set,求两个集合的交集。

关注推送

Feed流,无限下拉刷新获取新消息。

  • 拉模式:用户从关注邮箱中拉去消息。读,延迟高。
  • 推模式:发布消息到用户的邮箱中。写,延迟低。
  • 推拉结合:粉丝多的采用拉,粉丝少的采用推。

基于推模式实现关注推送:blog保存到数据库的同时,推送到粉丝的邮件箱,收件箱使用Redis的SortedSet满足时间戳排序,查询收件箱数据时实现分页查询(滚动式分页,数据不断变化,数据的角标也在变化。维护上一次最小的时间戳和偏移量)。

附近商户

Redis GEO结构存储地理位置信息。

根据商铺类型分组,typeid作为key存入同一个GEO集合中,成员是店铺ID。


网站公告

今日签到

点亮在社区的每一天
去签到