当前位置: 首页 > news >正文

redis(7)

全局ID生成器: 

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足以下特性

  1. 唯一性
  2. 高可用(随时访问随时生成)
  3. 递增性
  4. 安全性(不能具有规律性)
  5. 高性能(生成ID的速度快)

为了增加ID的安全性,我们不会使用redis自增的数值,而是使用拼接一些其他的信息

就算时间戳相同,也是可以在每一秒内支持2^32个订单

唯一ID组成部分:

符号位:1bit,永远为0,表示此ID永远是正数

时间戳:31bit,以秒为单位,可以使用69年

序列号:32bit,秒内的计数器,可以支持2^32个不同ID

  • Redis自增ID策略
    • 每天一个key,方便统计订单量
    • ID构造是时间戳+计数器
@Component
public class RedisIDWorker {@Autowiredprivate StringRedisTemplate template;public long NextID(String keyPrefix){//1.生成时间戳long current=System.currentTimeMillis();//2.生成序列号//2.1获取到日期的时间,让这一天的订单自增,还可以方便做统计SimpleDateFormat format=new SimpleDateFormat("yyyy:MM:dd");String data=format.format(System.currentTimeMillis());long count= template.opsForValue().increment("increment:"+keyPrefix+":"+data);System.out.println(count+"jjjj");//3.拼接并且返回return current<<32|count;}}

 

@SpringBootTest
class RedisIDWorkerTest {@Autowiredprivate RedisIDWorker worker;@Testvoid nextID() throws InterruptedException {CountDownLatch latch=new CountDownLatch(300);Runnable runnable=new Runnable() {@Overridepublic void run() {Long ID= worker.NextID("order业务");System.out.println(ID);latch.countDown();}};ExecutorService service= Executors.newFixedThreadPool(1);for(int i=0;i<300;i++){service.submit(runnable);}latch.await();System.out.println("线程池中的任务已经全部完成");}
}

实现获取优惠卷:

1)秒杀是否开始或结束,如果尚未开始或已经结束则无法下单

2)库存是否充足,不足则无法下单

加上事务的原因是因为想要进行修改优惠卷的剩余个数的数据库操作和新增订单的操作要么全部执行成功,要么全部执行失败;

@Controller
public class UserController {@Autowiredprivate DemoMapper mapper;@Autowiredprivate RedisIDWorker worker;@RequestMapping("/GetCard")@ResponseBody@Transactionalpublic String GetOrder(Integer cardID,Integer userID){//在这里面还应该加上用户ID对应的用户是否存在//1.进行判断优惠卷ID是否存在Card card=mapper.SelectCardByID(cardID);if(cardID==null){return "当前优惠卷不存在";}//2.判断秒杀是否开始if(new Timestamp(System.currentTimeMillis()).compareTo(card.getStartTime())<0){return "秒杀活动还没有开始";}//2.判断秒杀活动是否结束
//        if(new Timestamp(System.currentTimeMillis()-500000).compareTo(card.getStartTime())>0){
//            return "秒杀活动已经结束";
//        }//3.判断代金卷是否还充足if(card.getCards()<1){return "当前优惠卷已经没有库存了";}//4.进行扣减库存int data= mapper.DecrmentCardData(cardID);//5.进行新创建订单if(data<1){return "获取优惠卷失败,优惠卷已经没有库存了";}Order order=new Order();order.setOrderID((int) worker.NextID("优惠卷秒杀"));//在实际开发中其实可以在session中获取到userID,当前为了实现方便只是在方法中传递了userIDorder.setUserID(userID);order.setCardID(cardID);mapper.InsertOrder(order);return "获取优惠卷成功";}
}

关于超卖(优惠卷被减成负数),超卖问题就是典型的多线程并发安全问题,针对这一问题的常见解决方案就是加锁:

假设线程1再进行查询库存和删除库存的过程中,还没有删除库存,那么有其他线程在中间插入一些逻辑就会造成线程安全问题 

1)线程1进行查询库存发现库存中只剩下一个优惠卷了

2)此时在线程1进行查询库存之后更新库存中的数据之前(cards=cards-1)

3)此时线程2进行尝试获取优惠卷,判断当前库存中只是剩下一个优惠卷了,此时线程2的时间片用完了,进入到休眠操作

4)此时线程1开始进行更新库存数据,此时优惠卷已经为0了

5)但是此时线程2被唤醒,因为唤醒之前已经判断过优惠卷还有1个,感知不到线程1进行更新了库存,此时线程2更新数据库,于是优惠卷就被减为了1

update card set cards=cards-1 where cardID=#{cardID}

 1)悲观锁:

        认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行

        例如Synchronized、Lock都属于悲观锁
 2)乐观锁:

        认为线程安全不一定会发生,因此不加锁,只是在更新数据时取判断有没有其他线程对数据做了修改

        如果没有修改则认为时安全的,自己才更新数据,一般用于更新数据
        如果已经被其他线程修改说明发生了安全问题,此时可以重试或异常
关于超卖我们可以加一个乐观锁,乐观锁的关键是判断之前查询得到的数据有被修改过,常见的方式有两种:

1)版本号法:顾名思义,就是在修改数据时加入一个version,如果修改的时候version与自己得到的version 不相同,那么就修改失败,可以尝试重试或异常
2)CAS法:就是更改数据,或者删除库存的时候,判断库存是否大于0,如果大于零,则扣除成功

如果说在我们查询库存到真正的修改库存的过程中发现库存数也就是优惠卷的个数没有发生过改变,说明在这段期间没有其他线程来进行插入修改其逻辑,那么就可以执行扣减库存操作

但是如果这样当我们进行模拟100个用户进行并发访问的时候,就会发现:很少的用户能够抢到优惠卷,这是不符合业务逻辑的,但是此时还发现优惠卷的剩余次数还是大于0的,但是用户还抢不到优惠卷,这又是怎么回事呢?

1)假设此时线程1查询库存发现现在库存还有100个优惠卷

2)此时线程2也进行查询库存发现库存还有100个优惠卷

3)此时线程2的时间片用完,进入到阻塞状态

4)此时线程1的用户执行完获取到优惠卷的操作,库存中的优惠卷总数-1

5)此时线程2的用户开始被唤醒,执行获取到优惠卷操作,此时SQL语句执行失败

update card set cards=cards-1 where cardID=#{cardID} and cards=#{count}

因为此时总的库存数已经不和刚才线程2查询出来的库存数相等了,所以线程2执行数据库操作失败,所以线程2的用户获取到优惠卷失败,解决方案还是进行修改SQL语句;

update card set cards=cards-1 where cardID=#{cardID}and cards>0

一人一单的功能:在进行更新优惠卷数目的时候根据优惠卷ID和用户ID来进行查询订单,如果查询的订单不为空,说明之前用户已经下过单了,那么直接返回false

但是此时做多线程并发访问的时候(用户传入了userID和CardID来去访问请求,结果又发现相同的用户ID下了很多单,于是又发生了线程安全问题;


@Controller
public class UserController {@Autowiredprivate DemoMapper mapper;@Autowiredprivate RedisIDWorker worker;@RequestMapping("/GetCard")@ResponseBody@Transactionalpublic String GetOrder(Integer cardID,Integer userID){//在这里面还应该加上用户ID对应的用户是否存在//1.进行判断优惠卷ID是否存在Card card=mapper.SelectCardByID(cardID);if(cardID==null){return "当前优惠卷不存在";}//2.判断秒杀是否开始if(new Timestamp(System.currentTimeMillis()).compareTo(card.getStartTime())<0){return "秒杀活动还没有开始";}//2.判断秒杀活动是否结束
//        if(new Timestamp(System.currentTimeMillis()-500000).compareTo(card.getStartTime())>0){
//            return "秒杀活动已经结束";
//        }//3.判断代金卷是否还充足Order orderDemo=mapper.SelectOrder(userID,cardID);if(orderDemo!=null){return "您当前已经下过单了,无法再次进行购买";}int count=card.getCards();if(count<1){return "当前优惠卷已经没有库存了";}//4.进行扣减库存int data= mapper.DecrmentCardData(cardID);//update card set cards=cards-1 where cardID=#{cardID} and cards=#{count}//在这里面说明如果如果cards的值和上面第三部查询出优惠卷的count值是相等的,那么就直接进行更新操作,否则就执行失败//5.进行新创建订单if(data<1){return "获取优惠卷失败,优惠卷已经没有库存了";}Order order=new Order();order.setOrderID((int) worker.NextID("优惠卷秒杀"));order.setUserID(userID);order.setCardID(cardID);mapper.InsertOrder(order);return "获取优惠卷成功";}
}

这个线程安全问题就很好理解了,假设有100个线程来同时进行访问我们的代码,这100个线程同时并行执行(100个线程同时执行代码的每一句),那么就又会同时插入多个订单了

所以我们要给查询出订单到判断订单到插入订单的这些逻辑进行从加上悲观锁

 此时我们把新增订单和查询订单和修改优惠卷的业务给加锁,但是上面锁的是this锁的是当前对象,但是锁的粒度很大,其实只有当两个userID相同的用户来进行竞争锁就可以了,下面的代码锁定范围就变小了,程序的执行性能也就变高了

但是上面的代码仍然是存在线程安全问题的,由于此方法是添加事务操作的,必须是先释放锁,再去提交事务,但是释放锁并提交事务的这个操作并不是原子的,所以说在释放锁并提交事务的过程中该线程还没有提交订单如果有其他线程(相同的userID)来进行抢优惠卷的操作,那么一定会发生线程安全问题,所以提交的方案应该是该线程提交事务之后再去释放锁

但是上面的代码还是存在着问题,在单体服务项目中,加锁方式是可行的,但是在分布式环境中,这种方式就是不可行的,锁在JVM底层是依靠ObjectMonitor来进行实现的,因为在分布式项目中,有着不同的机器不同的机器存在着不同的JVM,同时不同的机器就有不同的分布式锁,所以要想解决这个问题,只能让多台机器使用同一把锁,多个JVM使用同一把锁

相关文章:

redis(7)

全局ID生成器: 全局ID生成器&#xff0c;是一种在分布式系统下用来生成全局唯一ID的工具&#xff0c;一般要满足以下特性 唯一性高可用(随时访问随时生成)递增性安全性(不能具有规律性)高性能(生成ID的速度快) 为了增加ID的安全性&#xff0c;我们不会使用redis自增的数值&am…...

互联网从业者高频单词 300个

测试 (Test) 软件 (Software) 用例 (Test Case) 缺陷 (Defect) 提交 (Submit) 回归测试 (Regression Testing) 验收测试 (Acceptance Testing) 单元测试 (Unit Testing) 集成测试 (Integration Testing) 性能测试 (Performance Testing) 负载测试 (load Testing) 压…...

初始化vue中data中的数据

当组件的根元素使用了v-if的时候, 并不会初始化data中的数据 如果想完全销毁该组件并且初始化数据,需要在使用该组件的本身添加v-if 或者是手动初始化该组件中的数据 初始化化数据的一些方法 Object.assign(this.$data, this.$options.data()) this.$data&#xff1a;当前的da…...

神经网络的建立-TensorFlow2.x

要学习深度强化学习&#xff0c;就要学会使用神经网络&#xff0c;建立神经网络可以使用TensorFlow和pytorch&#xff0c;今天先学习以TensorFlow建立网络。 直接上代码 import tensorflow as tf# 定义神经网络模型 model tf.keras.models.Sequential([tf.keras.layers.Dense…...

python基于卷积神经网络实现自定义数据集训练与测试

注意&#xff1a; 如何更改图像尺寸在这篇文章中&#xff0c;修改完之后你就可以把你自己的数据集应用到网络。如果你的训练集与测试集也分别为30和5&#xff0c;并且样本类别也为3类&#xff0c;那么你只需要更改图像标签文件地址以及标签内容&#xff08;如下面两图所示&…...

跟着LearnOpenGL学习3--四边形绘制

文章目录 一、前言二、元素缓冲对象三、完整代码四、绘制模式 一、前言 通过跟着LearnOpenGL学习2–三角形绘制一文&#xff0c;我们已经知道了怎么配置渲染管线&#xff0c;来绘制三角形&#xff1b; OpenGL主要处理三角形&#xff0c;当我们需要绘制别的图形时&#xff0c;…...

c#笔记-结构

装箱 结构是值类型。值类型不能继承其他类型&#xff0c;也不能被其他类型继承。 所以它的方法都是确定的&#xff0c;没有虚方法需要在运行时进行动态绑定。 值类型没有对象头&#xff0c;方法调用由编译器直接确定。 但是&#xff0c;如果使用引用类型变量&#xff08;如接…...

Es分布式搜索引擎

目录 一、什么是ES&#xff1f; 二、什么是elk&#xff1f; 三、什么是倒排索引&#xff1f; 四、正向索引和倒排索引的优缺点对比 五、mysql数据库和es的区别&#xff1f; 六、索引库&#xff08;es中的数据库表&#xff09;操作有哪些&#xff1f; 八、ES分片存储原理 …...

open3d 裁剪点云

目录 1. crop_point_cloud 2. crop 3. crop_mesh 1. crop_point_cloud 关键函数 chair vol.crop_point_cloud(pcd) # vol: SelectionPolygonVolume import open3d as o3dif __name__ "__main__":# 1. read pcdprint("Load a ply point cloud, crop it…...

如何对第三方相同请求进行筛选过滤

文章目录 问题背景处理思路注意事项代码实现 问题背景 公司内部多个系统共用一套用户体系库&#xff0c;对外(钉钉)我们是两个客户身份(这里是根据系统来的)&#xff0c;例如当第三方服务向我们发起用户同步请求&#xff1a;是一个更新用户操作&#xff0c;它会同时发送一个 d…...

Go RPC

目录 文章目录 Go RPCHTTP RPCTCP RPCJSON RPC Go RPC Go 标准包中已经提供了对 RPC 的支持&#xff0c;而且支持三个级别的 RPC&#xff1a;TCP、HTTP、JSONRPC。但 Go 的 RPC 包是独一无二的 RPC&#xff0c;它和传统的 RPC 系统不同&#xff0c;它只支持 Go 开发的服务器与…...

真正的智能不仅仅是一个技术问题

智能并不是单一的技术问题&#xff0c;而是一个包括技术、人类智慧、社会制度和文化等多个方面的综合体&#xff0c;常常涉及技术变革、系统演变、运行方式创新、组织适应。智能是指人类的思考、判断、决策和创造等高级认知能力&#xff0c;可以通过技术手段来实现增强和扩展。…...

【数据结构】复杂度包装泛型

目录 1.时间和空间复杂度 1.1时间复杂度 1.2空间复杂度 2.包装类 2.1基本数据类型和对应的包装类 2.2装箱和拆箱 //阿里巴巴面试题 3.泛型 3.1擦除机制 3.2泛型的上界 1.时间和空间复杂度 1.1时间复杂度 定义&#xff1a;一个算法所花费的时间与其语句的执行次数成…...

Ae:绘画面板

Ae菜单&#xff1a;窗口/绘画 Paint 快捷键&#xff1a;Ctrl 8 绘画工具&#xff08;画笔工具、仿制图章工具及橡皮擦工具&#xff09;仅能工作在图层面板上。在使用绘画工具之前&#xff0c;建议先在绘画 Paint面板中查看或进行相关设置。 说明&#xff1a; 如果要在绘画描边…...

常见的锁和zookeeper

zookeeper 本文由 简悦 SimpRead 转码&#xff0c; 原文地址 zhuanlan.zhihu.com 前言 只有光头才能变强。 文本已收录至我的 GitHub 仓库&#xff0c;欢迎 Star&#xff1a;https://github.com/ZhongFuCheng3y/3y 上次写了一篇 什么是消息队列&#xff1f;以后&#xff0c;本来…...

经验总结:(Redis NoSQL数据库快速入门)

一、Nosql概述 为什么使用Nosql 1、单机Mysql时代 90年代,一个网站的访问量一般不会太大&#xff0c;单个数据库完全够用。随着用户增多&#xff0c;网站出现以下问题 数据量增加到一定程度&#xff0c;单机数据库就放不下了数据的索引&#xff08;B Tree&#xff09;,一个机…...

form表单与模板引擎

文章目录 一、form表单的基本使用1、什么是表单2、表单的组成部分3、 <form>标签的属性4、表单的同步提交及缺点&#xff08;1&#xff09; 什么是表单的同步提交&#xff08;2&#xff09; 表单同步提交的缺点&#xff08;3&#xff09; 如何解决表单同步提交的缺点 二、…...

医院检验信息管理系统源码(云LIS系统源码)JQuery、EasyUI

云LIS系统是一种医疗实验室信息管理系统&#xff0c;提供全面的实验室信息管理解决方案。它的主要功能包括样本管理、检测流程管理、报告管理、质量控制、数据分析和仪器管理等。 云LIS源码技术说明&#xff1a; 技术架构&#xff1a;Asp.NET CORE 3.1 MVC SQLserver Redis等…...

React 组件

文章目录 React 组件复合组件 React 组件 本节将讨论如何使用组件使得我们的应用更容易来管理。 接下来我们封装一个输出 “Hello World&#xff01;” 的组件&#xff0c;组件名为 HelloMessage&#xff1a; React 实例 <!DOCTYPE html> <html> <head> &…...

硕士学位论文的几种常见节奏

摘要: 本文描述硕士学位论文的几种目录结构, 特别针对机器学习方向. 1. 基础版 XX算法及其在YY中的应用 针对情况: 只有一篇小论文支撑. 第 1 章: 引言 ( > 5页) 1.1 背景及意义 (应用背景、研究意义, 2 页) 1.2 研究进展及趋势 (算法方面, 2 页) 1.3 论文结构 (1 页) 第 …...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...