Redis实战—黑马点评项目—优惠券秒杀
一、全局ID生成器
对于优惠券业务,亦即订单业务,其中的优惠券订单id存储到数据库中时将不采用mysql自增,因为这是一种不安全且当有庞大的订单时又不能保证分布式全局唯一性的做法。因此,就需要有一这样一个生成全局id的角色。
在分布式系统下,为了保证id的唯一性、高可用、高性能、递增性以及安全性,可以采用redis来实现全局id的生成。
基于redis的id生成器,将生成这样的复杂的id:
@Component
public class RedisIdWorker {private static final long BEGIN_TIMESTAMP=1640995200L;//开始时间戳private static final int COUNT_BITS=32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate=stringRedisTemplate;}public long nextId(String keyPrefix){//时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timeStamp = nowSecond - BEGIN_TIMESTAMP;//序列号String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);//拼接返回return timeStamp<<COUNT_BITS|count;}}
测试
@Testpublic void testIdWorker() throws InterruptedException {CountDownLatch latch = new CountDownLatch(300);Runnable task=()->{for(int i=0;i<100;i++){long id=redisIdWorker.nextId("order");System.out.println("id="+id);}latch.countDown();};long begin = System.currentTimeMillis();for(int i=0;i<300;i++){es.submit(task);}latch.await();long end = System.currentTimeMillis();System.out.println("time:"+(end-begin));}
二、添加秒杀券
使用postman发送添加优惠券请求,模拟管理平台。
三、秒杀券订单
@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {//查优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始 是否结束LocalDateTime beginTime = voucher.getBeginTime();LocalDateTime endTime = voucher.getEndTime();//秒杀未开始或结束 返回是错误if(beginTime.isAfter(LocalDateTime.now())){return Result.fail("秒杀尚未开始");}if (endTime.isBefore(LocalDateTime.now())){return Result.fail("秒杀已结束");}//秒杀开始 判断库存是否充足//不足 返回错误if (voucher.getStock()==0){return Result.fail("库存不足");}//优惠券足够 扣减库存boolean success = seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).update();if(!success){return Result.fail("库存不足");}//创优惠券订单VoucherOrder voucherOrder = new VoucherOrder();long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//订单idvoucherOrder.setUserId(UserHolder.getUser().getId());//用户idvoucherOrder.setVoucherId(voucherId);//优惠券idsave(voucherOrder);//返回订单idreturn Result.ok(orderId);}
四、线程安全——超卖
使用jmeter模拟高并发的秒杀场景,添加200线程。测试发现,优惠券数量出现负数,即超卖问题,因此考虑加锁。
测试结果出现了少量的超卖问题,故可以考虑采用加乐观锁。有两种做法:一是采用加版本号,而是CAS。
修改操作其实是先查询后修改,每次查询同时获取版本号,修改时先对比版本号,若不同则报错,相同则修改同时版本号+1。
也可以不加版本号。用修改字段数据代替版本号,即先对比数据,未修改过再修改。
但是,对于业务来说,只要库存大于0这种并发修改就是没有问题的,而乐观锁则会将并发修改的其余线程全部失效,因此,对于业务来说,要对乐观锁进行修改:在修改数据时不判断数据是否被修改过,而是查询优惠券库存其是否>0.
//优惠券足够 扣减库存boolean success = seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock",0)//大于0.update();
五、线程安全——一人一单
一人一单的实现:当查询完库存充足后,查看券订单中的用户id是否已经存在。
但是同时,当秒杀刚开始时,众多线程同时涌入判断,将都会判定为第一次下单,因此,将整个查询订单—创建订单—扣减库存都加悲观锁。同时进行事务的处理。
@Overridepublic Result seckillVoucher(Long voucherId) {//查优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始 是否结束LocalDateTime beginTime = voucher.getBeginTime();LocalDateTime endTime = voucher.getEndTime();//秒杀未开始或结束 返回是错误if(beginTime.isAfter(LocalDateTime.now())){return Result.fail("秒杀尚未开始");}if (endTime.isBefore(LocalDateTime.now())){return Result.fail("秒杀已结束");}//秒杀开始 判断库存是否充足//不足 返回错误if (voucher.getStock()<1){return Result.fail("库存不足");}//优惠券足够 创建订单Long userId = UserHolder.getUser().getId();synchronized(userId.toString().intern()){//拿到这个类的代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}//先提交事务再释放悲观锁}//若将事务注解加在此处:// 事务是采用的mapper代理方式实现;// 调用此方法时 是采用this.方法()是非代理对象 是没有事务功能的//因此需要在调用处拿到这个方法的代理对象 才能使事务生效@Transactionalpublic Result createVoucherOrder(Long voucherId) {//优惠券足够 查看该用户是否已经下过单Long userId = UserHolder.getUser().getId();int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if(count>0){return Result.fail("已经购买过这个券了");}// 扣减库存boolean success = seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock",0)//大于0.update();if(!success){return Result.fail("库存不足");}//创优惠券订单VoucherOrder voucherOrder = new VoucherOrder();long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//订单idvoucherOrder.setUserId(userId);//用户idvoucherOrder.setVoucherId(voucherId);//优惠券idsave(voucherOrder);//返回订单idreturn Result.ok(orderId);}
Redis实战—黑马点评项目—优惠券秒杀相关推荐
- 【Redis】Redis实战:黑马点评之优惠券秒杀
Redis实战:黑马点评之优惠券秒杀 1 全局唯一ID 1.1全局唯一ID 每个店铺都可以发布优惠券: 当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据 ...
- Redis:黑马点评项目之用户短信登录
一.表结构 建表sql语句: /*Navicat Premium Data TransferSource Server : localSource Server Type : MySQLSource ...
- Redis学习笔记②实战篇_黑马点评项目
若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 资料链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA( ...
- 黑马点评项目全部功能实现及详细笔记--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 ...
- 黑马点评项目-短信登录功能
一.导入黑马点评项目 1.代码下载 视频资源链接:P25 实战篇-02.短信登录-导入黑马点评项目 代码可以直接去黑马微信公众号上搜索,或者从下面的网盘链接中下载:链接: https://pan.ba ...
- 黑马点评--优惠卷秒杀
黑马点评–优惠卷秒杀 全局ID生成器: 是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性: 为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息: Re ...
- 黑马点评项目全面业务总结
1 黑马点评项目 1.1 短信登陆 1.1.1 短信登陆简介 session共享问题:多台服务器并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题. 在进行短信登录时 ...
- 黑马点评项目笔记(四)社交、附近人、数据统计功能实现
目录 达人探店 查看博文 点赞博文 点赞排行榜 好友关注 关注和取关 共同关注 关注推送(Feed流) Feed流的两种模式 Timeline 三种实现模式 基于推模式实现消息推送 滚动分页 附近商户 ...
最新文章
- 吴恩达神经网络和深度学习——第二周笔记
- 开源 免费 java CMS - FreeCMS1.5 标签 guestbookPage
- 前端零基础教学开始第一天 01 -day
- javese 5 中的枚举类及单例模式
- UI设计干货素材|滑动动效设计模板
- ps练习实例_拥有一份史上最全面的50集ps抠图学习教程是什么一种体验?
- 深入剖析Android音频(三)AudioPolicyService
- pygame游戏开发-简介
- Ancient Knight(打造Windows Mobile平台最专业的游戏修改器)
- 把 mysql sql_mode 设置为严格模式的方法
- 私域运营是不是就是社群运营?
- 只要5个步骤,教你开发一个自己的chrome扩展程序
- OSPF特殊区域TOTAL STUB配置实验
- Qt5 源代码自动跳转
- python3 文字合成语音MP3
- 智能家居DIY之智能吸顶灯
- lol美服一直显示链接服务器,LOL美服注册怎么一直显示账号已被使用
- 用命令行下载百度网盘数据的方法
- 题解 CF1100E 【Andrew and Taxi】
- [安全检测]诺顿在线安全检测