Redis...2
优惠卷秒杀问题01.优惠券秒杀-全局唯一ID1.id是会展示给用户的如果id的规律性太强会让用户观察出一定的信息2.受表单数据的限制订单量是和容易积累的在多年的运营下订单量很可能超过了单表所能存储的最大数量此时开始新的一个表来存储的时候自增的ID就会出现重复的情况确保安全性前三十一位为时间戳记录下单时间以秒为单位序列号秒内的计数器支持每秒产生2的三十二次方个id确保唯一性的方法不同时间不在同一秒的id依靠时间戳来决定相同时间在同一秒的id依靠序列号来分辨02-Redis实现全局唯一id变化的key1.因为如果时间的key都是同一个那么同一秒内的时间戳包括时间戳和序列号可能会重复造成key相同所以每一天的key的前缀都不同的话就不会重复2.便于统计年月日是变化的前缀可以统计某年某月某日的订单量public class RedisWorker { /* 时间戳 */ private static long BEGIN_TIME 1640995200; /* 序号号位数 */ private static long COUNT_BIT 32; private StringRedisTemplate stringRedisTemplate; //全局ID生成器 public Long nextId(String prefix){ //1.生成时间戳 LocalDateTime now LocalDateTime.now(); long epochSecond now.toEpochSecond(ZoneOffset.UTC); long timestamp epochSecond - BEGIN_TIME; //2.生成序列号 //2.1生成当前的日期--需要传入的参数 String date now.format(DateTimeFormatter.ofPattern(yyyyMMdd)); //2.2自增长 long increment stringRedisTemplate.opsForValue().increment(icr prefix date); //3.拼接 return timestamp COUNT_BIT | increment; } public static void main(String[] args) { //生成当前时间的时间戳 LocalDateTime localDateTime LocalDateTime.of(2022, 1, 1, 0, 0, 0); //确定时区--不确定时区无法确定时间戳 long epochSecond localDateTime.toEpochSecond(ZoneOffset.UTC); //打印时间戳 System.out.println(epochSecond); } }03-添加优惠券{ shopId: 1, title: 100元代金卷, subtitle: 周一到周日均可使用, rules: 全场通用, payValue: 8000, actualValue: 10000, type: 1, stock: 100, beginTime: 2022-01-01T10:09:17, endTime: 2030-01-01T10:09:10 }04-实现秒杀下单05-库存超卖问题分析库存超卖问题出现的原因资源临界时同时访问都判断为可以执行结果超卖了悲观锁认为线程安全问题一定会发生因此在操作数据之前先获取锁确保线程串行执行synchronizedlock都属于悲观锁乐观锁认为线程安全问题不一定会发生只是在更新数据的时候判断有没有其他线程对数据作出修改没有修改则认为是安全的自己才更新数据如果已经被其他线程修改则说明发生了线程安全问题此时可以重试或者异常1.版本号法2.简化方法--用库存代替版本号--看前后两次查到的库存是否一致06-乐观锁解决超卖只加入这一句会导致在多个请求同时发生的时候只有一个能成功应该在库存前后不同的时候应该重试或者可以理解为在库存大于零的时候不用管前后库存是否一致只要0就可以卖出去07-实现一人一单功能在扣减库存之前加一步根据优惠卷id和用户id查询订单---根据订单是否存在判断该用户是否下过单/** * 优惠卷秒杀 * param voucherId * return */ Override public Result seckillVoucher(Long voucherId) { //1.查询优惠卷 SeckillVoucher voucher seckillVoucherService.getById(voucherId); //2.判断优惠卷是否存在 if(voucher null){ return Result.fail(优惠卷信息不存在); } //3.判断优惠卷是否过期或者未开始 if(voucher.getBeginTime().isAfter(LocalDateTime.now())){ return Result.fail(优惠卷还未开始); } if(voucher.getEndTime().isBefore(LocalDateTime.now())){ return Result.fail(优惠卷已经结束); } //4.判断优惠卷库存是否充足 if(voucher.getStock()1){ return Result.fail(优惠卷库存不足); } Long id UserHolder.getUser().getId(); //锁加在外面可以在事务提交之后再释放锁防止事务未提交订单未新增锁就释放导致不一致 synchronized (id.toString().intern()) { return createVoucherOrder(voucherId); } Transactional private Result createVoucherOrder(Long voucherId) { //5.判断当前用户购买的优惠卷是否超过单个用户的购买限制 User user UserHolder.getUser(); Long userId user.getId(); Integer count query().eq(voucher_id, voucherId).eq(user_id, userId).count(); if (count 0) { return Result.fail(该用户已经下过单了); } //6.扣减库存 if (!seckillVoucherService.update() .setSql(stock stock - 1) .eq(voucher_id, voucherId) .gt(stock, 0).update()) { return Result.fail(扣减库存失败); } //7.新建订单 VoucherOrder voucherOrder new VoucherOrder(); //7.1新建订单信息---订单id Long l redisWorker.nextId(SECKILL_STOCK_KEY); voucherOrder.setId(l); //7.2订单id voucherOrder.setVoucherId(voucherId); //7.3用户id voucherOrder.setUserId(userId); //8.将订单保存到数据库 save(voucherOrder); return Result.ok(); }不在方法上加锁的原因在方法上加锁会让锁的对象固定为this一个对象一个锁不满足我们对锁的要求08-集群下的线程并发安全问题1.修改服务的端口号编辑配置修改选项添加虚拟机选项2.修改nginx.conf文件配置反向代理和负载均衡分布式锁01-基本原理和不同实现方式对比在分布式锁中的锁监视器--确保多个进程看到同一个锁监视器02-Redis的分布式锁实现思路分布式锁要监视到所有的线程包括多个进程内的线程这个时候就要使用别的应用来监视线程如果在添加锁和添加锁过期时间之间出现了问题还是会导致死锁所以要保证添加锁和添加锁的过期时间的原子性可以把两步合成一步在获取锁失败之后有两种方式1.阻塞式等待在等待其他线程释放锁2.非阻塞式等待尝试获取锁失败之后立即返回一个结果而不是一直等待或者一直获取03-实现Redis分布式锁版本1ILook接口public interface ILook { /** * 尝试获取锁 * param timeOut * return */ boolean tryLook(Long timeOut); /** * 尝试删除锁 */ void delLook(); }ILook的实现类public class SimpleRedisLock implements ILook { private String name; private StringRedisTemplate stringRedisTemplate; private String prefix lock:; //锁的名称和StringRedisTemplate都是需要外面传递过来的 public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate stringRedisTemplate; this.name name; } Override public boolean tryLook(Long timeOut) { long threadId Thread.currentThread().getId(); //包装类有为null的风险此时直接传递给基本数据类型boolean就会报空指针 Boolean b stringRedisTemplate.opsForValue().setIfAbsent(prefix name, threadId , timeOut, TimeUnit.SECONDS); //所以选择判断是否为true return Boolean.TRUE.equals(b); } Override public void delLook() { stringRedisTemplate.delete(prefix name); } }修改代码synchronized只对单一进程有效redis的setnx可以对多个进程有效04-Redis分布式锁误删问题会导致的问题把别人的锁给删掉了导致别的线程获取到了锁开始执行业务造成多个线程并行执行任务根本原因线程一在删除锁的时候把线程二的锁给删了解决思路在删除锁的时候做一个判断判断是不是自己的锁05-解决Redis分布式锁误删问题不同的jvm虚拟机容易出现相同的线程的id所以要用uuid对虚拟机作出区分再拼接上不同的线程的idpublic class SimpleRedisLock implements ILook { private String name; private StringRedisTemplate stringRedisTemplate; private String KEY_PREFIX lock:; //不同的id前缀区分不同的虚拟机 private String ID_PREFIX UUID.randomUUID().toString()-; //锁的名称和StringRedisTemplate都是需要外面传递过来的 public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate stringRedisTemplate; this.name name; } Override public boolean tryLook(Long timeOut) { long threadId Thread.currentThread().getId(); //包装类有为null的风险此时直接传递给基本数据类型boolean就会报空指针 Boolean b stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, ID_PREFIX threadId , timeOut, TimeUnit.SECONDS); //所以选择判断是否为TRUE return Boolean.TRUE.equals(b); } Override public void delLook() { //删除锁的逻辑需要都修改为如下逻辑 //1.获取redis中的线程标识--也就是获取锁内的threadId String threadId stringRedisTemplate.opsForValue().get(KEY_PREFIX name); //2.获取当前线程的threadId long id Thread.currentThread().getId(); String nowId ID_PREFIX id; //3.比较当前的threadId与锁内的是否一致 if (threadId ! null threadId.equals(nowId)) { //如果相同则删除 stringRedisTemplate.delete(KEY_PREFIX name); } } }06-分布式锁的原子性问题判断锁标识和释放是两个动作之间产生了阻塞产生阻塞后相当于没有写判断锁误删的代码07-Lua脚本解决多条命令原子性问题redis的事务和mysql的事务不同redis的事务可以保证原子性但是保证不了一致性因为redis是把所有动作一次性处理没办法保证先查询再释放---做查询的时候拿不到结果只有所有动作全完成的时候才能拿到结果推荐使用lua脚本可以在一个lua脚本中执行多条redis指令确保多条命令执行时的原子性脚本需要的key类型的参数个数key中有含参的参数需要传入参数--参数为0就是没有含参的参数脚本的参数包括两个类型key类型和其它类型数字只表示key类型的参数的数量后面紧跟着的是key类型的参数key类型的参数后面是其他参数调用参数的时候key[]和argv[]不用加 只有命令部分需要用引起来参数部分不需要1. 第一条报错EVAL return redis.call(set,key[1],jack) 1 name # ERR user_script:1: Script attempted to access nonexistent global variable key原因Lua 脚本里必须用KEYS[1]而不是key[1]大小写敏感。修正EVAL return redis.call(set, KEYS[1], jack) 1 name2. 第二条报错EVAL return redis.call(set,name,jack) # ERR wrong number of arguments for eval command原因EVAL命令必须指定「key 的数量」即使你不用KEYS。修正EVAL return redis.call(set,name,jack) 00表示本次脚本没有通过KEYS传递的 key3. 第三条报错redis.call(set,name,jack) # ERR syntax error原因redis.call只能在EVAL/EVALSHA脚本中执行不能直接在命令行运行。修正EVAL return redis.call(set,name,jack) 0✅ 最终正确写法两种推荐方式规范写法推荐用 KEYS 传递 keyEVAL return redis.call(set, KEYS[1], ARGV[1]) 1 name jack这种写法通过KEYS和ARGV传递参数符合 Redis 的最佳实践支持集群环境。简单写法直接写死 key适合测试EVAL return redis.call(set,name,jack) 0关键要点总结EVAL命令格式EVAL lua脚本 key数量 [key1 key2 ...] [arg1 arg2 ...]Lua 脚本中访问 key 必须用KEYS[1]大写不能用key[1]即使不传递 key也必须写0作为 key 数量redis.call只能在EVAL脚本中执行不能直接在命令行调用动态传递参数08-Java调用lua脚本改造分布式锁--这是一个lua脚本 --redis.call(set,name:06:23,jack) --获取锁的key local lockKey KEY[1]; --获取当前线程的id local threadId ARGV[1]; --查询锁内的线程id local id redis.call(get,lockKey); --判断当前线程的id和锁内线程id是否一致 if (id threadId) then --一致则删除 return redis.call(del,locakKey); end return 0;private final String name; private final StringRedisTemplate stringRedisTemplate; private final String KEY_PREFIX lock:; //定义lua脚本 private static final DefaultRedisScriptLong UNLOCK_SCRIPT; //初始化lua脚本 static { UNLOCK_SCRIPT new DefaultRedisScript(); //设置lua脚本的位置 UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.luau)); //设置lua脚本的返回值类型 UNLOCK_SCRIPT.setResultType(Long.class); } //不同的id前缀区分不同的虚拟机 private final String ID_PREFIX UUID.randomUUID().toString()-; //锁的名称和StringRedisTemplate都是需要外面传递过来的 public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate stringRedisTemplate; this.name name; } Override public boolean tryLook(Long timeOut) { long threadId Thread.currentThread().getId(); //包装类有为null的风险此时直接传递给基本数据类型boolean就会报空指针 Boolean b stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, ID_PREFIX threadId , timeOut, TimeUnit.SECONDS); //所以选择判断是否为TRUE return Boolean.TRUE.equals(b); } Override public void delLook() { //redis调用lua脚本 stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIXname), Thread.currentThread().getId() ); }09-Redisson功能介绍

相关新闻

生产环境的问题之缓存小磁盘频繁换页

生产环境的问题之缓存小磁盘频繁换页

「缓存小 + 磁盘频繁换页」,本质是内存容量不足以承载热点数据,操作系统/进程被迫不断在「内存 ↔ 磁盘」之间搬运数据页,专业上称为内存颠簸(Thrashing),直接后果是磁盘IO拉满、CPU空等IO、业务吞吐量暴跌、响应耗时出现大量毛刺。 “缓存”在不同层级有不同含义,对应…

2026/6/24 5:28:01阅读更多 →
用 NestJS 从零开发一个完整的小项目:图书管理系统(第七阶段:RBAC(Role Based Access Control)基于角色的权限控制)

用 NestJS 从零开发一个完整的小项目:图书管理系统(第七阶段:RBAC(Role Based Access Control)基于角色的权限控制)

基于角色的权限控制例如:角色权限admin增删改查user只能查看guest无权限先理解 RBAC当前:登录 ↓ JWT ↓ 认证成功只能证明:你是谁但是不知道:你能干什么RBAC解决的是:你能访问哪些资源最终效果普通用户:DE…

2026/6/24 5:28:01阅读更多 →
【硬核长文】万字拆解无线网络核心:AP(无线访问接入点)从底层原理到企业级实战调优指南

【硬核长文】万字拆解无线网络核心:AP(无线访问接入点)从底层原理到企业级实战调优指南

【硬核长文】万字拆解无线网络核心:AP(无线访问接入点)从底层原理到企业级实战调优指南 作者寄语: 做为研究了这么多年网络工程师的博主,我发现一个有趣的现象:大家对路由器、交换机的原理往往能倒背如流&a…

2026/6/24 5:28:01阅读更多 →
开源库存管理终极指南:现代化供应链的技术实现方案

开源库存管理终极指南:现代化供应链的技术实现方案

开源库存管理终极指南:现代化供应链的技术实现方案 【免费下载链接】InvenTree Open Source Inventory Management System 项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree InvenTree作为企业级开源库存管理系统,为制造业和供应链管…

2026/6/24 9:08:40阅读更多 →
ArcReel容器化部署指南:如何快速搭建AI视频生成工作台

ArcReel容器化部署指南:如何快速搭建AI视频生成工作台

ArcReel容器化部署指南:如何快速搭建AI视频生成工作台 【免费下载链接】ArcReel AI Agent 驱动的开源视频生成工作台 — 小说→角色/场景/道具设计→剧本→分镜图→视频,跨镜头角色与场景一致 | Open-source AI video workspace powered by AI Agents, N…

2026/6/24 9:08:40阅读更多 →
试试连Claude Code团队都在使用的终端软件Ghostty

试试连Claude Code团队都在使用的终端软件Ghostty

终端性能卡顿,长输出直接掉帧Claude 一次输出几百行,滚屏飞快,信息一晃而过无法做到分屏,窗口管理混乱,多窗口需要来回切换 正所谓工欲善其事,必先利其器,那么极需要一款能满足使用终端场景的工…

2026/6/24 9:08:39阅读更多 →
小鹿管家-三大自动化功能让全域投放更轻松,告别“盯屏焦虑“

小鹿管家-三大自动化功能让全域投放更轻松,告别“盯屏焦虑“

小鹿管家千投助手,这次我们带来了三大重磅功能,让你的千川投放从此告别人工盯盘的痛苦,实现真正的智能化、自动化管理。 🕐 功能一:定时启停计划 —— 让计划按计划运行 还在手动开关计划?太out了&#x…

2026/6/24 9:08:39阅读更多 →
课时8:C 语言 for 循环详解:从入门到精通

课时8:C 语言 for 循环详解:从入门到精通

摘要 本文全面介绍了 C 语言中 for 循环的语法、用法和常见应用场景。主要内容包括: 循环的必要性:解释了为什么需要循环结构来避免重复代码。 for 循环基本语法:详细讲解了 for 循环的三个表达式(初始化、条件、更新)及其执行顺序。 基础示例:通过打印 1 到 10 的示例,…

2026/6/24 9:08:39阅读更多 →
微信小程序虚拟支付与广告转化回传实战记录

微信小程序虚拟支付与广告转化回传实战记录

微信小程序虚拟支付与广告转化回传实战记录 写在前面 最近在开发微信小程序时,接连遇到了两个非常典型的问题:一是虚拟支付道具图片上传失败,二是巨量引擎广告转化回传后不知道去哪里查看结果。这两个问题看似不相关,但背后都涉及…

2026/6/24 9:03:39阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/24 7:33:03阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/24 2:12:09阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/24 7:37:00阅读更多 →
TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理 【免费下载链接】TaskJuggler TaskJuggler - Project Management beyond Gantt chart drawing 项目地址: https://gitcode.com/gh_mirrors/ta/TaskJuggler TaskJuggler是一款强大的开源项目管理工具&#…

2026/6/24 0:02:41阅读更多 →
终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果 【免费下载链接】angular-mobile-nav An angular navigation service for mobile applications 项目地址: https://gitcode.com/gh_mirrors/an/angular-mobile-nav angular-mobile-nav是一款专为…

2026/6/24 0:02:41阅读更多 →
Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作 【免费下载链接】Wan2.1-Fun-V1.1-1.3B-InP 项目地址: https://ai.gitcode.com/hf_mirrors/PAI/Wan2.1-Fun-V1.1-1.3B-InP Wan2.1-Fun-V1.1-1.3B-InP是一款强大的AI视频创作工具,…

2026/6/24 0:02:41阅读更多 →