黑马点评关键业务流程梳理一
一、基于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三者的特点
黑马点评关键业务流程梳理一相关推荐
- Redis学习笔记②实战篇_黑马点评项目
若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 资料链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA( ...
- 业务流程梳理与IT咨询
一共四个要素: 1.业务流程 2.IT部署 3.接口 4.主数据 每个要素分四个阶段: 1.现状重现 2.需求调研 3.问题诊断 4.整改建议 也就是说4个要素x4个阶段=16个咨询成果 但范围太大, ...
- 黑马点评项目全部功能实现及详细笔记--Redis练手项目
目录 一.项目详情 1.1 项目简介 1.2 数据库表设计 1.3 前端部署 1.4 后端搭建 二.短信登录 2.1 发送验证码 2.2 验证码登录 2.3 登录校验拦截器 2.4 退出登录(补充) ...
- 菜鸟项目练习:黑马点评项目总结
目录 1. 项目介绍 2.各个功能模块 2.1 登录模块 2.1.1 实现短信登录 2.1.2 编写拦截器 2.2 查询商户模块 2.2.1 主页面查询商户类型 2.2.3 按距离查询商户 2.3 ...
- springboot-redis-mysql-nginx项目:黑马点评开发(更新中)
springboot-redis-mysql-nginx项目:黑马点评开发 开篇导读 亲爱的小伙伴们大家好,希望通过此博客,小伙伴们就能理解各种redis的使用啦. 短信登录 这一块我们会使用redi ...
- 阿里云RPA(机器人流程自动化)干货系列之五:业务流程梳理方法...
导读:本文是阿里云RPA(机器人流程自动化)干货系列之五,详细介绍了在开发阿里云RPA机器人应用之前对客户的业务流程进行详细.全面地梳理,并识别出流程中的关键点和换位思考点,为后续应用开发打好基础. ...
- 阿里云RPA(机器人流程自动化)干货系列之五:业务流程梳理方法
导读:本文是阿里云RPA(机器人流程自动化)干货系列之五,详细介绍了在开发阿里云RPA机器人应用之前对客户的业务流程进行详细.全面地梳理,并识别出流程中的关键点和换位思考点,为后续应用开发打好基础. ...
- 黑马点评Redis实战(短信登录;商户查询缓存)
黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...
- 黑马点评--优惠卷秒杀
黑马点评–优惠卷秒杀 全局ID生成器: 是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性: 为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息: Re ...
最新文章
- python中matplotlib自定义设置图像标题使用的字体类型:获取默认的字体族及字体族中对应的字体、自定义设置图像标题使用的字体类型
- python语言可以在哪系统操作-python能检测到它运行的是哪个操作系统?
- python人脸识别环境搭建_怎样用3分钟搭建 Python 人脸识别系统
- Android开发之RadioButton位于文字右边的显示方法
- 程序模拟键盘鼠标操作
- BlackBerry 应用程序开发者指南 第一卷:基础--第7章 使用数据报(Datagram)连接...
- bzoj1192 [HNOI2006]鬼谷子的钱袋
- CoreAnimation-Layer详解
- PyTorch入坑(一)~(三): Tensor的概念,基本操作和线性回归
- 发一个成熟好用的电池供电切换电路
- 【C++函数】strcat()
- window下python2和python3的共存
- C++11强类型枚举——枚举类
- 网站部署——基于Django框架的天天生鲜电商网站项目系列博客(十六)
- 如何提取差异脑区的灰质体积与临床量表算相关?——基于体素的形态学方法(VBM)
- 自然语言(NLP)处理流程—IF-IDF统计—jieba分词—Word2Vec模型训练使用
- js:Cannot use import statement outside a module
- 微服务架构在区块链BaaS平台中的实践
- linux登出用户,Linux系统中用户的登入登出命令详解
- Google 应用与游戏出海 4 月刊: 带您连线 GDC,赢在发布前!