# hm-dianping **Repository Path**: StarSea007/hm-dianping ## Basic Information - **Project Name**: hm-dianping - **Description**: 学习黑马2022版的redis的实战篇 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-07-05 - **Last Updated**: 2025-06-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: Redis ## README 学习黑马Redis的实战篇demo(黑马点评项目) 前端采用nginx部署,Redis集群,MySQL集群。 视频地址:https://www.bilibili.com/video/BV1cr4y1671t/ 项目笔记:https://www.yuque.com/july-sea/tga2o9/kzxu6mbse0ahgvcl ## 短信登录 1、基于session实现登录流程(发送验证码,登录拦截器,敏感信息处理) 存在问题:session共享问题 2、使用redis替代session(登录状态刷新问题) ## 商户查询缓存 需求:根据id查询商铺信息接口添加redis缓存 存在问题:数据库与缓存数据不一致问题 解决办法: 1. 根据id查询店铺信息,如果未命中,查询数据库,将查询的结果写入缓存,并设置超时时间。 2. 根据id修改店铺信息,先修改数据库,再删除缓存。 存在问题:缓存穿透问题:客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不生效,这些请都会打在数据库。 解决办法: 1. 缓存空对象,并设置过期时间 2. 使用布隆过滤器 存在问题:缓存雪崩问题:同一时间段大量的缓存key同时失效或者redis服务宕机,导致大量请求访问数据库。 解决办法: 1. 给不同的key设置不同的过期时间 2. 利用redis的集群提高服务的高可用 3. 给缓存业务添加熔断降级策略 4. 给业务添加多级缓存 存在问题:缓存击穿问题:一个key被高并发访问并且这个key突然失效了,无数的请求都访问数据库。 解决方法: 1. 使用互斥锁 2. 采用逻辑过期 ## 优惠卷秒杀 当用户抢购优惠券时,就会生成订单保存在表中,而订单表如果使用数据库自增id就存在一些问题: 1. id的规律性太明显 2. 受单表数据量的限制。 采用办法:使用全局id生成器。 countdownlatch:信号枪:主要作用是同步协调在多线程的等待唤醒问题。 如果我们没有用countdownlatch,那么由于程序是异步的,当异步程序还没有执行完时,主线程就已经执行完了, 然后我们期望的是分线程全部走完之后,主线程在走,所以我们就需要使用到countdownlatch。 ### 秒杀下单 实现思路: 1. 根据优惠券id查询优惠券信息 2. 判断秒杀时间是否已经开始和已经解决 3. 如果没有开始给出提示信息 3. 如果已经开始判断库存是否充足 4. 如果不充足给出提示信息 5. 如果充足,扣减库存 6. 创建订单 7. 返回订单id 存在问题: 1. 库存超卖问题 解决方式: 1. 乐观锁:添加同步锁,让线程串行执行 2. 悲观锁(version版本号)【使用】 ### 优惠券秒杀一人一单 要求同一个优惠券,一个用户只能下一单。 实现思路: 1. 比如时间是否充足,如果时间充足,则进一步判断库存是否足够 2. 然后再根据优惠卷id和用户id查询是否已经下过这个订单 3. 如果下过这个订单,则不再下单,否则进行下单。 存在问题: 现在问题还是和之前一样,并发过来,查询数据库,都不存在订单,所以我们还是需要加锁,但是乐观锁比较适合更新数据,而现在是插入操作,我们需要使用悲观锁操作。 解决方法: 使用synchronized关键字加锁。 注意: 1. synchronized关键字不要加在方法上,因为锁的粒度大。 2. 所以我们将synchronized关键字加到方法内部。 但是存在问题:当前方法被spring的事务控制,如果我们在方法内部加锁,可能会导致当前方法事务还没有提交,但是锁已经释放,也会导致问题。 所以我们选择将当前方法包裹起来,确保事务不会出现问题。 但是依旧存在问题:因为你调用的方法,其实是this.的方式调用的,事务想要生效,还的利用代理对象来生效,所以我们还是使用最原始的事务对象来操作事务。 锁的粒度在单机情况下一人一单是没有问题的,但是在集群分布式情况下就不行了。 原因:在集群模式下或者分布式系统下,有多个jvm存在,每个jvm内都有自己的锁,导致每一个锁都有自己的线程,所以就出现并行运行 就会出现线程安全问题。 所以我们使用分布式锁来解决这个问题。 ## 分布式锁(redis) 基于Redis的分布式锁实现思路: * 利用set nx ex获取锁,并设置过期时间,保证线程标识 * 释放锁时先判断线程标识是否与自己的一致,一致则删除锁。 * 判断锁标识和释放锁不一定是原子操作,也会出现超卖问题,通过使用Lua脚本来解决。 说明: 第一个线程进来,得到了锁,手动删除锁,模拟锁超时了,其他线程会执行lua来抢锁,当第一个线程利用lua删除锁时,lua能保证他不能删除他的锁,第二个线程删除锁时,利用lua同样可以保证不会删除别人的锁,同时还能保证原子性。 ## 分布式锁(redisson) 基于setnx实现分布式锁还存在以下问题: * 不可重入:同一个线程无法多次获取同一把锁 * 不可重试:获取锁只尝试一次就返回false,没有重试机制 * 超时释放:超时释放虽然可以避免死锁,但如果业务耗时比较长,也会导致锁释放,存在安全隐患 * 主从一致性:如果Redis提供了主从集群,主从同步存在延迟,当主宕机时,如果从并同步主的锁数据,则会出现锁实现。 redisson如何解决: * 可重入:利用hash结构记录线程id和重入次数。 * 可重试:利用信号量和PubSub功能实现等待、唤醒、获取锁失败的重试机制。 * 超时续约:利用watchDog,每隔一段时间(releaseTime/3),重置超时时间 缺陷:redis宕机引起锁失效问题。 为了提高redis的可用性,我们会搭建集群或者主从。 * 此时我们写命令在主机上,主机会将数据同步给从机,但是假设在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机, 并且选举一个slave变成master,而此时新的master中实际上并没有锁信息,此时锁信息就已经丢掉了。 * 为了解决这个问题,redisson提出来了**MutiLock锁**,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的, 这把锁加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功,假设现在某个节点挂了,那么他去获得锁的时候, 只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。 redisson锁的MutiLock原理: * 多个独立的redis节点,必须在所有节点都获取重入锁,才算获取锁成功。 缺点:运维成本高,实现复杂。 **注意** :一定要通过跟踪源码的方式查看整个redisson的执行过程。 ## 秒杀优化 针对上面秒杀的操作步骤,有很多操作都是要查询数据库的,而且还是一个线程串行执行,这样会导致我们的程序执行很慢,对此我们进行优化。 优化步骤: * 新增秒杀优惠卷的同时,将优惠劵信息保存到redis中。 * 库存信息使用String存储,一人一单信息使用Set存储。 * 基于Lua脚本,判断秒杀库存、一人一单,决定用户是否可以抢购成功。 * 如果抢购成功,将优惠劵id和用户id封装后存入阻塞队列。 * 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能。 缺点: * 此时的阻塞队列是JDK中的,它使用的是JVM的内存,如果不加以限制,在高并发情况下就会有很多订单对象需要创建并且放到阻塞队列中, 可能会导致内存溢出。 * 我们在创建阻塞队列时指定了长度,但是阻塞队列存满了,还有新的订单就放不进去队列中了。 ## redis消息队列 需求步骤: * 创建一个Stream类型的消息队列,名为stream.orders * 修改之前的秒杀下单Lua脚本,在认定有抢购资格后,直接向stream.orders中添加消息,内容包含voucherId、userId、orderId * 项目启动时,开启一个线程任务,尝试获取stream.orders中的消息,完成下单 ## 达人探店 1. 达人探店-发布探店笔记 * 图片上传接口 * 保存探店博文接口 2. 达人探店-查看探店笔记 3. 达人探店-点赞功能 初始实现:直接更新数据库点赞数量。 存在问题:一个人可以无线点赞,显然是不合逻辑的。 需求: * 同一个用户只能点赞一次,再次点击则取消点赞 * 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性) 实现步骤: * 给Blog类中添加一个isLike字段,标示是否被当前用户点赞 * 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1 * 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段 * 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段 4. 达人探店-排行榜 我们选择sortedSet集合。 > 分析: > > set不允许添加重复数据。 > > 判断是否已经存在使用sismember > > sortedSet有排序功能。 > > 判断是否已经存在使用score,存在返回对应值,不存在返回nil。 > > 范围查询选择zrange ## 好友关注 1. 关注和取消关注 2. 共同关注 采用redis的set方式存储,使用sinter求交际。 3. Feed流实现方案 * Timeline * 拉模式 * 推模式 * 推拉模式 * 智能排序 4. 推送到粉丝收件箱 5. 实现分页查询收邮箱 采用sortedset实现。