电子商务网站建设的开发背景/app推广刷量
缓存
开发中,凡是放入缓存中的数据都应该指定过期时间,使其可以在系统即使没有主动更新数据也能自动触发数据加载进缓存的流程。避免业务崩溃导致的数据永久不一致 问题。
redis作为缓存使用redisTemplate操作redis
分布式锁的原理和使用
分布式加锁:本地锁,只能锁住当前进程,所以我们需要分布式锁
分布式锁演进
基本原理:多个操作用户操作,抢占锁,获取到锁的用户执行业务,释放锁。
分布式锁演进阶段1:
redis获取锁:setnx(“lock”,1111) -->获取到锁->执行业务->删除锁->结束,未获取到锁的等待重试
代码:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {//1. 分布式锁 去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");if(lock){//加锁成功Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");return dataFromDb;}else {//加锁失败 重试 synchronizereturn getCatalogJsonFromDbWithRedisLock();//自旋的方式}
}
问题:
setnx占好了位,业务代码异常或者程序在页面过程中宕机,没有执行删除锁逻辑,造成死锁
解决:
设置锁的自动过期,即使没有删除,会自动删除
分布式锁演进阶段2:
redis获取锁:setnx(“lock”,1111) -->获取到锁->设置过期时间->执行业务->删除锁->结束,未获取到锁的等待重试
代码:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {//1. 分布式锁 去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");if(lock){//加锁成功//2. 设置过期时间redisTemplate.expire("lock",30,TimeUnit.SECONDS);Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");return dataFromDb;}else {//加锁失败 重试 synchronizereturn getCatalogJsonFromDbWithRedisLock();//自旋的方式}
}
问题:
setnx设置好,正要去设置过期时间,宕机,死锁。
解决:
设置过期时间和占位必须是原子的,redis支持使用setnx ex命令
分布式锁演进阶段3:
redis获取锁:setnxex(“lock”,1111,10s) -->获取到锁->执行业务->删除锁->结束,未获取到锁的等待重试
代码:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {//1. 分布式锁 去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",300,TimeUnit.SECONDS);if(lock){//加锁成功//2. 设置过期时间// redisTemplate.expire("lock",30,TimeUnit.SECONDS);Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");return dataFromDb;}else {//加锁失败 重试 synchronizereturn getCatalogJsonFromDbWithRedisLock();//自旋的方式}}
问题:
删除锁直接删除?由于业务时间很长,锁自己过期了,直接删除,有可能把别人正在持有的锁删除了。
解决:
占锁的时候,值指定为uuid,每个人匹配的是自己的锁才删除
分布式锁演进阶段4:
redis获取锁:setnxex(“lock”,uuid,10s) -->获取到锁->执行业务->如果当前锁的值是之前的uuid的锁–>删除锁->结束,未获取到锁的等待重试
代码:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {String uuid = UuidUtils.generateUuid().toString();//1. 分布式锁 去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);if(lock){//加锁成功//2. 设置过期时间// redisTemplate.expire("lock",30,TimeUnit.SECONDS);Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();String lockValue = redisTemplate.opsForValue().get("lock");if(lockValue.equals(uuid)) {//删除自己的锁redisTemplate.delete("lock");}return dataFromDb;}else {//加锁失败 重试 synchronizereturn getCatalogJsonFromDbWithRedisLock();//自旋的方式}}
问题:
如果正好判断当前值,正要删除锁的时候,锁已经过期别人已经设置到了新的值,删除的还是别人的锁
解决:
删除锁必须保证原子性,使用redis+lua脚本
分布式锁演进阶段5:
redis获取锁:setnxex(“lock”,uuid,10s) -->获取到锁->执行业务->脚本解锁保证原子性->结束,未获取到锁的等待重试
代码:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {String uuid = UuidUtils.generateUuid().toString();//1. 分布式锁 去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);if(lock){//加锁成功Map<String, List<Catelog2Vo>> dataFromDb = null;try {dataFromDb = getDataFromDb();}finally {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//删除锁Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);}// String lockValue = redisTemplate.opsForValue().get("lock");// if(lockValue.equals(uuid)) {// //删除自己的锁// redisTemplate.delete("lock");// }return dataFromDb;}else {//加锁失败 重试 synchronizereturn getCatalogJsonFromDbWithRedisLock();//自旋的方式}}String script = "if redis.call"('get',KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。更难的事情,锁的自动续期
锁
可重入锁(Reentrant Lock)
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁,再次获取锁的时候会判断当前线程是否是已经加锁的线程,如果是对锁的次数+1,释放锁的时候加了几次锁,就需要释放几次锁。
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
@ResponseBody@GetMapping("/hello")public String hello(){//1.获取一把锁,只要锁的名字一样,就是同一把锁RLock lock = redisson.getLock("my-lock");//2.加锁lock.lock();//阻塞式等待 默认加的锁是30s//1. 锁的自动续期 如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期会删除//2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除try {System.out.println("加锁成功,执行业务"+Thread.currentThread().getId());Thread.sleep(30000);}catch (Exception e){e.printStackTrace();}finally {//3.解锁System.out.println("释放锁"+Thread.currentThread().getId());lock.unlock();}return "hello";}
问题:负责存储分布式锁的Redission节点宕机后,这个锁正好处于锁住的状态时,这个锁会出现锁死的状态
解决:reddison内部提供了一个监控锁的看门狗,作用是在redission实例被关闭前,不断的延长锁的有效期,默认情况下,看门狗的检查锁的超时时间是30秒钟,可以通过Config.lockWatchdogTimeout。还通过加锁的方法提供了leaseTime的参数来指定加锁的时间,超过时间后锁便自动解开了。
读写锁
基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
@GetMapping("/write")@ResponseBodypublic String writeValue(){String s="";RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");RLock lock = readWriteLock.writeLock();try {//改数据加写锁 读数据加读锁lock.lock();s = UUID.randomUUID().toString();Thread.sleep(30000);redisTemplate.opsForValue().set("writeValue",s);}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}return s;}@GetMapping("/read")@ResponseBodypublic String readValue(){String s="";RReadWriteLock writeLock = redisson.getReadWriteLock("rw-lock");//加读锁Lock rLock = writeLock.readLock();try {rLock.lock();s = redisTemplate.opsForValue().get("writeValue").toString();Thread.sleep(30000);}catch (Exception e){e.printStackTrace();}finally {rLock.unlock();}return s;}
结论:
- 保证一定可以读到最新的数据,修改期间,写锁是一个排他锁(互斥锁,独享锁).读锁是一个共享锁
- 写锁没有释放 读就必须等待
- 读 + 读:相当于无锁并发读,只会的redis中记录好,所有当前的读锁,他们都会同时加锁成功
- 写 + 读:等待写锁释放
- 写 + 写:阻塞方式
- 读 + 写:有读锁也需要等待
- 只要有写锁的存在,都必须等待
信号量
基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
这里以停车位为例,当停车时,获取一个信号量,获取到信号量之后进行停车,车开走之后可以再释放一个信号量
/*** 车库停车* @return* @throws InterruptedException* 信号量 可以用作分布式限流*/
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {RSemaphore park = redisson.getSemaphore("park");park.acquire();//获取一个信号量,获取一个信号量占一个车位return "ok";
}@GetMapping("/go")
@ResponseBody
public String go(){RSemaphore park = redisson.getSemaphore("park");park.release();//释放一个车位return "ok";
}
闭锁
原理:闭锁相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭着的,没有任何线程可以通过,当到达结束状态时,这扇门才会打开并容许所有线程通过。它可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,初始化为一个正式,正数表示需要等待的事件数量。countDown方法递减计数器,表示一个事件已经发生,而await方法等待计数器到达0,表示等待的事件已经发生。CountDownLatch强调的是一个线程(或多个)需要等待另外的n个线程干完某件事情之后才能继续执行。
应用场景
10个运动员准备赛跑,他们等待裁判一声令下就开始同时跑,当最后一个人通过终点的时候,比赛结束。10个运动相当于10个线程,这里关键是控制10个线程同时跑起来,还有怎么判断最后一个线程到达终点。可以用2个闭锁,第一个闭锁用来控制10个线程等待裁判的命令,第二个闭锁控制比赛结束。
示例
5个班放学,当5个班的同学都走完之后,锁门
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {RCountDownLatch door = redisson.getCountDownLatch("door");door.trySetCount(5);door.await();return "放假了";
}@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id){RCountDownLatch door = redisson.getCountDownLatch("door");door.countDown();//计算减一return id+"班的人走完了";
}
数据一致性问题
- 双写模式
- 失效模式
- 解决方案
- 无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。
- 如果是用户维度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
- 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
- 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
- 通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略);
- 总结:
- 放入缓存的数据本不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
- 不应该过度设计,增加系统的复杂性 • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
Spring Cache
- Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合; Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache , ConcurrentMapCache 等
- 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已 经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓 存结果后返回给用户。下次调用直接从缓存中获取。
- 使用 Spring 缓存抽象时我们需要关注以下两点;
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
参考:缓存和分布式锁
相关文章:

缓存和分布式锁笔记
缓存 开发中,凡是放入缓存中的数据都应该指定过期时间,使其可以在系统即使没有主动更新数据也能自动触发数据加载进缓存的流程。避免业务崩溃导致的数据永久不一致 问题。 redis作为缓存使用redisTemplate操作redis 分布式锁的原理和使用 分布式加锁&…...

React笔记(七)Antd
一、登录功能 首先要使用antd,要先下载 yarn add antd 登录页面关键代码 import React from react /*1、如果要在react中完成样式隔离,需要如下操作1)命名一个xx.module.scss webpack要求2) 在需要的组件中通过ES6方式进行导入&#x…...

无涯教程-Android - RadioButton函数
RadioButton有两种状态:选中或未选中,这允许用户从一组中选择一个选项。 Radio Button 示例 本示例将带您完成一些简单的步骤,以展示如何使用Linear Layout和RadioButton创建自己的Android应用程序。 以下是修改后的主要Activity文件 src/MainActivity.java 的内容。 packa…...

kafka如何避免消费组重平衡
目录 前言: 协调者 重平衡的影响 避免重平衡 重平衡发生的场景 参考资料 前言: Rebalance 就是让一个 Consumer Group 下所有的 Consumer 实例就如何消费订阅主题的所有分区达成共识的过程。在 Rebalance 过程中,所有 Consumer 实例…...

浅谈一下企业信息化管理
企业信息化管理 企业信息化是指将企业的生产过程,物料,事务,财务,销售等业务过程数字化,通过各种信息系统网络价格成新的信息资源,提供给各层次的人们东西观察各类动态业务中的一切信息,以便于…...

北京APP外包开发团队人员构成
下面是一个标准的APP开发团队构成,但具体的人员规模和角色可能会根据项目的规模和需求进行调整。例如,一些小型项目或初创公司可能将一些角色合并,或者聘请外包团队来完成部分工作。北京木奇移动技术有限公司,专业的软件外包开发公…...

Node基础and包管理工具
Node基础 fs 模块 fs 全称为 file system,称之为 文件系统,是 Node.js 中的 内置模块,可以对计算机中的磁盘进行操作。 本章节会介绍如下几个操作: 1. 文件写入 2. 文件读取 3. 文件移动与重命名 4. 文件删除 5. 文件夹操作 6. …...

【python使用 Pillow 库】缩小|放大图片
当我们处理图像时,有时候需要调整图像的大小以适应特定的需求。本文将介绍如何使用 Python 的 PIL 库(Pillow)来调整图像的大小,并保存调整后的图像。 环境准备 在开始之前,我们需要安装 Pillow 库。可以使用以下命令…...

解决Ubuntu 或Debian apt-get IPv6问题:如何设置仅使用IPv4
文章目录 解决Ubuntu 或Debian apt-get IPv6问题:如何设置仅使用IPv4 解决Ubuntu 或Debian apt-get IPv6问题:如何设置仅使用IPv4 背景: 在Ubuntu 22.04(包括 20.04 18.04 等版本) 或 Debian (10、11、12)系统中,当你使用apt up…...

Xubuntu16.04系统中解决无法识别exFAT格式的U盘
问题描述 将exFAT格式的U盘插入到Xubuntu16.04系统中,发现系统可以识别到此U盘,但是打不开,查询后发现需要安装exfat-utils库才行。 解决方案: 1.设备有网络的情况下 apt-get install exfat-utils直接安装exfat-utils库即可 2.设备…...

Pygame中Trivia游戏解析6-1
1 Trivia游戏简介 Trivia的含义是“智力测验比赛中的各种知识”。Trivia游戏类似智力竞赛,由电脑出题,玩家进行作答,之后电脑对玩家的答案进行判断,给出结果并进行评分。该游戏的界面如图1所示。 图1 Trivia游戏界面 2 游戏流程 …...

idea中创建springboot项目显示Spring Initializr Error
很长时间不创建springboot项目了,今天发现创建完成idea显示: Spring Initializr Error error:status:500项目中没有pom.xml文件.检查了一下原因是在创建的时候类型没有创建正确(之前记得都是默认),默认如下 需要选择创建maven完整工程那种,最下面那种只会生成pom.xml不会…...

VScode 国内下载源 以及 nvm版本控制器下载与使用
VScode 国内下载源 进入官网 https://code.visualstudio.com/ 点击下载 复制下载链接到新的浏览器标签 将地址中的/stable前的az764295.vo.msecnd.net换成vscode.cdn.azure.cn,再回车就会直接在下载列表啦。 参考大神博客 2.使用nvm 对 node 和npm进行版本控制…...

GO|经典错误之回车与\n
学习go的输入输出语句,于是在笔记本上写了这么一段代码: func main() {reader : bufio.NewReader(os.Stdin)input, _ : reader.ReadString(\n)input input[:len(input)-1]i, _: strconv.Atoi(input)fmt.Println(i) } 运行,输入99ÿ…...

【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型(更新中)
【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型(更新中) 一、效果展示(多分类预测) 二、效果展示(回归预测) 三、代码获取 CSDN后台私信回复“71期”即可获取下…...

ARM编程模型-内存空间和数据
ARM属于RISC体系,许多指令单周期指令,是32位读取/存储架构,对内存访问是32位,Load and store的架构,只有寄存器对内存,不能内存对内存存储,CPU通过寄存器对内存进行读写操作。 ARM的寻址空间是线…...

leetcode原题: 最大数
题目: 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意:输出结果可能非常大 所以你需要返回一个字符串而不是整数。 示例1: 输入:nums [10,2] 输…...

docker 是什么
目录 docker是一个软件 Docker 是一种运行于 Linux 和 Windows 上的软件,用于创建、管理和编排容器。 为什么要使用 Docker? 1、 更快速的交付和部署 2、 更高效的虚拟化 3、 更轻松的迁移和扩展 4 、更简单的管理 docker是一个软件,是…...

基于Gin框架的HTTP接口限速实践
在当今的微服务架构和RESTful API主导的时代,HTTP接口在各个业务模块之间扮演着重要的角色。随着业务规模的不断扩大,接口的访问频率和负载也随之增加。为了确保系统的稳定性和性能,接口限速成了一个重要的话题。 1 接口限速的使用场景 接口…...

WSL中为Ubuntu和Debian设置固定IP的终极指南
文章目录 **WSL中为Ubuntu和Debian设置固定IP的终极指南****引言/背景****1. 传统方法****2. 新方法:添加指定IP而不是更改IP****结论**WSL中为Ubuntu和Debian设置固定IP的终极指南 引言/背景 随着WSL(Windows Subsystem for Linux)的普及,越来越多的开发者开始在Windows…...

axios+vite配置反向代理踩坑记录
aixosvite配置反向代理跨域踩坑记录 最近,实习中,一直在写公司的项目。因为公司的项目大多都已经将工程化的东西已经配置好了。导致我昨天自己写项目的时候配置工程化出错!其实,这是一个很简单的问题。之前熟练的时候能够很熟…...

Spring IOC的理解
总: 控制反转(IOC):理论思想,传统java开发模式,对象是由使用者来进行管理,有了spring后,可以交给spring来帮我们进行管理。依赖注入(DI):把对应的…...

2023年京东箱包行业数据分析(京东数据运营)
当前,旅游业全面复苏,这一现象也带动了周边产业的火爆。在全国游客的出行热带动下,箱包产业迎来消费热潮。 根据鲸参谋电商数据分析平台的相关数据显示,2023年7月,京东箱包大盘整体的销量为266万,同比增长…...

对称加密 非对称加密 AC认证 https原理
文章目录 对称加密及漏洞非对称加密及漏洞什么是数据摘要(也称数据指纹)什么是CA认证CA证书签发过程https通信方案 对称加密及漏洞 对称加密是一种加密算法,使用相同的密钥(也称为对称密钥)用于加密和解密数据。在对称…...

如何在PyQt应用程序中使用Qt Designer和Pyuic工具?
如果你想在PyQt应用程序中使用Qt Designer和Pyuic工具,那么首先你需要确保你已经安装了这些工具。你可以通过以下命令在你的Python环境中安装它们: pip install pyqt5 pip install pyqt5-tools安装完成后,你就可以开始使用Qt Designer设计…...

【云计算•云原生】5.云原生之初识DevOps
文章目录 1.DevOps背景2.DevOps概念3.DevOps工具链 1.DevOps背景 软件开发必须包含两个团队:开发团队和运维团队 开发团队负责开发项目,系统迭代更新运维团队负责项目测试以及部署上线,维持系统稳定运行 一个软件周期中是由这两个团队相互…...

20230830工作心得:巧用标记位和For循环遍历
1 巧用标记位和For循环遍历 您可以使用一个 Map<String, List<xxx>> 类型的数据结构来根据手机号分组并保存多条线索。然后,可以按照以下方式进行操作: 1. 设置一个标志位,比如一个布尔变量,用于记录是否已经成功推…...

AUTOSAR规范与ECU软件开发(实践篇)7.9 MCAL模块配置方法及常用接口函数介绍之Can的配置
目录 1、前言 2 、Can模块 (1) Can General配置 (2) CanConfigSet配置 (3) CanMainFunctionRWPeriods配置...

SpringBoot整合websockt实现消息对话
文章目录 前言websockt什么是websockt?websockt和Socket区别代码部分实战应用 前言 websockt 什么是websockt? WebSocket是一种在Web应用程序中实现实时双向通信的技术。Web应用程序通常是基于HTTP协议的,HTTP是一种请求/响应式的协议&…...

MIME类型(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型)MultipartFile 多媒体文件上传
目录 MIME媒体类型介绍MediaTypes 有哪些MultipartFile 类介绍MultipartFile 类 接收的文件是二进制嘛代码举例 上传的文件 实现接口 MultipartFile 类有哪些最终调用接口的方法时,会有哪个类实现 如何决定哪个类去实现呢 Spring 会根据运行环境自动选择合适的实现类…...