一、基于Session的短信登陆

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

二、基于Redis的短信登陆

登陆验证流程:

拦截器优化:

三、商户查询

3.1 缓存

3.2 缓存更新策略

3.3 缓存穿透解决方案

缓存穿透是指客户端的数据在缓存中和数据中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的解决方案有两种:

  • 缓存空对象

    • 优点:实现简单,维护方便
    • 缺点:额外的内存消耗、可能造成短期的不一致(若之后有该数据插入到数据库,会造成缓存和数据库不一致问题)
  • 布隆过滤
    • 优点:内存占用较少,没有多余 key
    • 缺点:实现复杂,存在误判

解决方案:

3.4 缓存击穿解决方案

缓存击穿问题也叫热点 Key 问题,就是一个被高并发访问并且缓存冲击就按业务比较复杂的 Key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

3.4.1 基于互斥锁方式解决缓存击穿

获取的锁是分布式锁

3.4.2 基于逻辑过期方式解决缓存击穿

四、优惠券异步秒杀

优化前:查询MySQL速度慢,而且下一步操作需要等待上一步执行完成

 优化后:

(1)新增秒杀优惠券的同时,将优惠券信息保存到Redis中

(2)基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功

(3)如果抢购成功,将优惠券id和用户id封装后发送到消息队列

(4)开启线程任务,不断从消息队列中获取信息,实现异步下单功能

4.1 Lua脚本

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then-- 3.2.库存不足,返回1return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then-- 3.3.存在,说明是重复下单,返回2return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

4.2 秒杀代码

public Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();long orderId = redisIdWorker.nextId("order");// 1.执行lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r = result.intValue();// 2.判断结果是否为0if (r != 0) {// 2.1.不为0 ,代表没有购买资格return Result.fail(r == 1 ? "库存不足" : "不能重复下单");}// 3.返回订单idreturn Result.ok(orderId);
}

4.3 消费消息队列

   private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create("stream.orders", ReadOffset.lastConsumed()));// 2.判断订单信息是否为空if (list == null || list.isEmpty()) {// 如果为null,说明没有消息,继续下一次循环continue;}// 解析数据MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单createVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());} catch (Exception e) {log.error("处理订单异常", e);handlePendingList();}}}private void handlePendingList() {while (true) {try {// 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create("stream.orders", ReadOffset.from("0")));// 2.判断订单信息是否为空if (list == null || list.isEmpty()) {// 如果为null,说明没有异常消息,结束循环break;}// 解析数据MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单createVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());} catch (Exception e) {log.error("处理订单异常", e);}}}}

4.4 创建订单

    private void createVoucherOrder(VoucherOrder voucherOrder) {Long userId = voucherOrder.getUserId();Long voucherId = voucherOrder.getVoucherId();// 创建锁对象RLock redisLock = redissonClient.getLock("lock:order:" + userId);// 尝试获取锁boolean isLock = redisLock.tryLock();// 判断if (!isLock) {// 获取锁失败,直接返回失败或者重试log.error("不允许重复下单!");return;}try {// 5.1.查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 5.2.判断是否存在if (count > 0) {// 用户已经购买过了log.error("不允许重复下单!");return;}// 6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") // set stock = stock - 1.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0.update();if (!success) {// 扣减失败log.error("库存不足!");return;}// 7.创建订单save(voucherOrder);} finally {// 释放锁redisLock.unlock();}}

五、问题总结

5.1 为什么要释放分布式锁需要用Lua脚本

场景:线程1执行完业务,需要释放分布式锁时,先判断锁的标识为自己,但是释放锁的操作阻塞,此时锁被超时释放,被另外线程2获取之后,线程1已经认为是自己的锁,然后执行释放操作。因此使用Redis提供的Lua脚本,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性

5.2 Redis 中 List、PubSub 和 Stream三者的特点

黑马点评关键业务流程梳理一相关推荐

  1. Redis学习笔记②实战篇_黑马点评项目

    若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 资料链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA( ...

  2. 业务流程梳理与IT咨询

    一共四个要素: 1.业务流程 2.IT部署 3.接口 4.主数据 每个要素分四个阶段: 1.现状重现 2.需求调研 3.问题诊断 4.整改建议 也就是说4个要素x4个阶段=16个咨询成果 但范围太大, ...

  3. 黑马点评项目全部功能实现及详细笔记--Redis练手项目

    目录 一.项目详情 1.1 项目简介 1.2 数据库表设计 1.3 前端部署 1.4 后端搭建 二.短信登录 2.1 发送验证码 2.2 验证码登录 2.3 登录校验拦截器 2.4 退出登录(补充) ...

  4. 菜鸟项目练习:黑马点评项目总结

    目录 1. 项目介绍 2.各个功能模块 2.1  登录模块 2.1.1 实现短信登录 2.1.2 编写拦截器 2.2 查询商户模块 2.2.1 主页面查询商户类型 2.2.3 按距离查询商户 2.3 ...

  5. springboot-redis-mysql-nginx项目:黑马点评开发(更新中)

    springboot-redis-mysql-nginx项目:黑马点评开发 开篇导读 亲爱的小伙伴们大家好,希望通过此博客,小伙伴们就能理解各种redis的使用啦. 短信登录 这一块我们会使用redi ...

  6. 阿里云RPA(机器人流程自动化)干货系列之五:业务流程梳理方法...

    导读:本文是阿里云RPA(机器人流程自动化)干货系列之五,详细介绍了在开发阿里云RPA机器人应用之前对客户的业务流程进行详细.全面地梳理,并识别出流程中的关键点和换位思考点,为后续应用开发打好基础. ...

  7. 阿里云RPA(机器人流程自动化)干货系列之五:业务流程梳理方法

    导读:本文是阿里云RPA(机器人流程自动化)干货系列之五,详细介绍了在开发阿里云RPA机器人应用之前对客户的业务流程进行详细.全面地梳理,并识别出流程中的关键点和换位思考点,为后续应用开发打好基础. ...

  8. 黑马点评Redis实战(短信登录;商户查询缓存)

    黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...

  9. 黑马点评--优惠卷秒杀

    黑马点评–优惠卷秒杀 全局ID生成器: 是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性: 为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息: Re ...

最新文章

  1. python中matplotlib自定义设置图像标题使用的字体类型:获取默认的字体族及字体族中对应的字体、自定义设置图像标题使用的字体类型
  2. python语言可以在哪系统操作-python能检测到它运行的是哪个操作系统?
  3. python人脸识别环境搭建_怎样用3分钟搭建 Python 人脸识别系统
  4. Android开发之RadioButton位于文字右边的显示方法
  5. 程序模拟键盘鼠标操作
  6. BlackBerry 应用程序开发者指南 第一卷:基础--第7章 使用数据报(Datagram)连接...
  7. bzoj1192 [HNOI2006]鬼谷子的钱袋
  8. CoreAnimation-Layer详解
  9. PyTorch入坑(一)~(三): Tensor的概念,基本操作和线性回归
  10. 发一个成熟好用的电池供电切换电路
  11. 【C++函数】strcat()
  12. window下python2和python3的共存
  13. C++11强类型枚举——枚举类
  14. 网站部署——基于Django框架的天天生鲜电商网站项目系列博客(十六)
  15. 如何提取差异脑区的灰质体积与临床量表算相关?——基于体素的形态学方法(VBM)
  16. 自然语言(NLP)处理流程—IF-IDF统计—jieba分词—Word2Vec模型训练使用
  17. js:Cannot use import statement outside a module
  18. 微服务架构在区块链BaaS平台中的实践
  19. linux登出用户,Linux系统中用户的登入登出命令详解
  20. Google 应用与游戏出海 4 月刊: 带您连线 GDC,赢在发布前!

热门文章

  1. 微信QQ的二维码登录原理js代码解析
  2. 自我提升解决bug的能力(一)
  3. HIVE的搭建配置及关联MySQL
  4. c语言复合赋值表达式题目,C语言中复合赋值运算,表达式等知识.doc
  5. 《神经网络与深度学习》邱希鹏 学习笔记 (1)
  6. 后BT时代,我们该怎么办?
  7. 中国民营医院行业市场运营模式与发展动向展望报告2022~2027年
  8. SQL中对 datetime 类型操作
  9. QByteArray详解(qt)
  10. 使用Origin根据数据画二维图(单x,双y)