【Redis】黑马点评笔记:使用redis解决各种分布式/并发问题

1、系统架构

2、基于session登录

用户的 session 是由服务器(如 Tomcat)自动管理和维护的,每个用户在访问 Web 应用时都会拥有一个独立的 session 对象。这个对象是通过浏览器和服务器之间的 HTTP 协议自动绑定的。


1. 如何区分不同用户的 Session?
每个用户的 session 是通过 Cookie 中的 JSESSIONID 来区分的。
当用户第一次访问服务器时,服务器会创建一个唯一的 HttpSession 对象,并生成一个唯一的标识符 JSESSIONID。
这个 JSESSIONID 会被写入到客户端的 Cookie 中。
后续每次请求中,客户端会将 JSESSIONID 发送到服务器端,服务器根据这个 ID 找到对应的 HttpSession 实例。


示例流程:

  • 用户 A 第一次请求 /user/code:
    • 服务器创建 HttpSession 实例 A,并分配 JSESSIONID=abc123。
    • 将 JSESSIONID=abc123 写入用户 A 的 Cookie。
  • 用户 B 第一次请求 /user/code:
    • 服务器创建另一个 HttpSession 实例 B,并分配 JSESSIONID=xyz456。
    • 将 JSESSIONID=xyz456 写入用户 B 的 Cookie。
  • 用户 A 再次请求 /user/code:
  • 浏览器自动携带 Cookie 中的 JSESSIONID=abc123。
  • 服务器找到对应的 HttpSession 实例 A 并使用它处理请求。

如下登录代码,校验验证码:

通过取出该用户对应session中保存的验证码和用户传递的验证码对比进行校验。其中session中的验证码实在给用户发送短信的同时保存在session中的。

校验通过,登录成功。服务器将用户信息存储在该session中。之后每次用户请求,将会通过cookie辨认对应的session,然后得知该用户已登录。

拦截器实现登录校验

集群session共享问题

Redis解决集群的session问题

一半选择使用hashmap来存储用户信息(因为用户信息往往是一个对象)

登录逻辑

校验逻辑

前端每次请求会在请求头携带token

拦截器的使用

3、缓存

是什么?

缓存作用模型

缓存更新策略

主动更新策略

一般选择方案1。后两个方案都不成熟

先删除缓存还是先操作数据库?

方案二发生概率更低,因为查询数据库之后写入缓存之间间隔往往很小(微秒级别);而更新数据库并更新所需要的时间比较长。因而方案二发生数据不一致的概率比较小。

小结

缓存穿透

布隆过滤器里面就是一些二进制位。数据库中的数据通过哈希算法映射到布隆过滤器中的某个位置上。

方法一:缓存空对象

缓存穿透的解决方案有哪些?

缓存雪崩

如果大量缓存的key的过期时间一样,会导致他们同时失效;解决方案为下图第一种。

缓存击穿

解决方案

互斥锁解决

使用redis的setnx命令实现逻辑上的自定义互斥锁。

为了避免获取锁的进程出现故障而导致锁无法被该进程释放,一般还会为这个锁设置有效期。

redis实现互斥锁

逻辑过期解决

使用这种方法需要进行缓存预热,也就是提前将热点信息加入缓存当中。

redis里面设置逻辑过期字段可以参考以下代码:

封装缓存工具类

@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//普通插入redispublic void set(String key, Object value, Long time, TimeUnit unit){stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);}//带逻辑过期时间插入redispublic void setWithLogicalExpire(String key,Object value,Long time,TimeUnit unit){//设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//写入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));}//解决缓存穿透的查询(返回空值)public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){String key=keyPrefix+id;//1.尝试从Redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断缓存是否存在if(StrUtil.isNotBlank(json)) { //判断字符串既不为null,也不是空字符串(""),且也不是空白字符//3.存在,返回商铺信息return JSONUtil.toBean(json, type);}//判断是否为空值if(json!=null){return null;}//4.不存在,根据id查询数据库R r = dbFallback.apply(id);//5.判断数据库中是否存在if(r==null){//6.不存在,返回错误状态码stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}//7.存在,写入redis,返回商铺信息this.set(key,r,time,unit);return r;}private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);//解决缓存击穿的查询(逻辑过期)public <R,ID> R queryWithLogicalExpire(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback,Long time,TimeUnit unit){String key=keyPrefix+id;//1.尝试从Redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断缓存是否存在if(StrUtil.isBlank(json)) { //判断字符串既不为null,也不是空字符串(""),且也不是空白字符//3.不存在,返回商铺信息return null;}//4.存在,将json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R shop = JSONUtil.toBean((JSONObject) redisData.getData(),type);LocalDateTime expireTime = redisData.getExpireTime();//5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {//5.1.未过期,直接返回店铺信息return shop;}//5.2.已过期,需要返回缓存重建//6.缓存重建//6.1.获取互斥锁String lockKey=RedisConstants.LOCK_SHOP_KEY+id;boolean isLock = tryLock(lockKey);//6.2.判断是否获取锁成功if(isLock){//  6.3.成功,开启独立线程实现缓存重建CACHE_REBUILD_EXECUTOR.submit(()->{try {//查询数据库R r1= dbFallback.apply(id);//写入redisthis.setWithLogicalExpire(key,r1,time,unit);} catch (Exception e) {throw new RuntimeException(e);}finally {//释放锁unLock(key);}});}//6.4.返回过期的商铺信息return shop;}/*** 创建锁* @param key* @return*/private boolean tryLock(String key){Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}/*** 封闭锁* @param key*/private void unLock(String key){stringRedisTemplate.delete(key);}
}

4、JMeter-模拟高并发的工具

5、全局唯一ID

要求递增性是为了数据库查询的高效。

6、秒杀优惠券

业务流程

超卖问题

解决办法

其中使用悲观锁解决会导致性能严重下降。

乐观锁-版本号法

由于stock肯定是递减的,因此将它作为“版本号”不会导致ABA问题的发生。就有如下解法:

一人一单业务

7、集群模式下的秒杀-分布式锁

动机

分布式锁

redis实现分布式锁

使用set命令同时设置互斥的(NX)key和它的过期时间

误删别人锁的问题

解决办法

key对应value标识当前获取锁的对象

Lua脚本解决多条命令原子性问题

极端情况

假如某线程在刚刚判断完当前锁是不是自己的锁之后,线程发生堵塞,则该线程在被唤醒之后还是有可能误删其他线程的锁。

->要保证判断锁和释放锁这两个动作共同形成一个原子操作。

Redis的Lua脚本

8、Redisson

事实上分布式锁无需自己实现,会使用框架即可,如Redisson

快速入门

Redisson可重入锁的原理

获取锁

释放锁

整体流程

Resson的multiLock

9、redis优化秒杀

redis判断秒杀资格

优化后,下单的核心流程仅仅是以下:可见变得非常短且执行速度会非常快

异步下单-阻塞队列

10、异步下单-消息队列

redis消息队列

redis消息队列-list

下图缺点“无法避免消息丢失”,因为有可能刚刚pop出来还没处理就宕机了。

redis消息队列-PubSub

“不支持数据持久化”——发布者发布消息的瞬间如果没有任何订阅者,这条消息就会消失。

“消息堆积有上限”——订阅者有一个消息缓存区,存放收到了但是还没来得及处理的消息。

redis消息队列-Stream

写消息

读消息

“消息可回溯”——消息永远会保存在队列中

消费者组-基于stream

一个消费者组里面,消息被任意一个消费者读取过就算被消费了。

小结对比

11、点赞功能

以博客id为key,点赞用户的set集合为value存储。

点赞排行榜

显示最新点赞的n个用户。使用SortedSet,score值设置为时间戳。

12、共同关注

以某个用户为key,该用户关注的所有用户的set为value,存到redis。

共同关注取交集即可。

但是关注信息在mysql数据库中也有一个表维护。(树树:不然怎么查看粉丝数……)

13、关注推送-feed流

拉模式

只有赵六准备读取的时候才从他关注的up主的发件箱读取消息——延时高

推模式

up主不保存信息,他一旦发布就将信息保存在所有粉丝的收信箱——保存多个副本,如有大v有几千万粉丝,则内存消耗巨大

推拉结合

新博客推送给粉丝-推模式

用户发送博客,查询粉丝列表,将新博客id存到所有粉丝的收件箱。

滚动分页

【豆包】

滚动分页(Scroll Pagination)是一种基于用户滚动行为的分页加载方式,常见于移动端应用、网页信息流等场景,核心特点是当用户滚动到页面底部时,自动加载下一页数据,无需手动点击页码或 “加载更多” 按钮,从而实现 “无限滚动” 的连续浏览体验。

与传统分页的区别:

传统分页(如页码分页)需要用户通过点击 “上一页 / 下一页” 或直接输入页码跳转,每次加载固定页数的数据(如第 1 页 10 条、第 2 页 10 条),依赖 “页码” 作为分页标识。

而滚动分页不依赖页码,而是通过记录 “上一页最后一条数据的标记”(如 ID、时间戳、游标等)作为下一页的查询起点,动态加载后续内容。例如:

  • 首次加载第 1-10 条数据,记录第 10 条的 ID 为lastId=10
  • 用户滚动到底部时,自动请求lastId=10之后的 11-20 条数据,再记录第 20 条的 ID 为lastId=20,以此类推。

传统分页的弊端

如果在查询的同时有人插入了数据,就会发生重复读取情况。

使用滚动分页,每次记住上一次查询到哪一条数据,下次查询从这条数据往后查。

实现方法:

每次从用户邮箱读取的时候,记住max,并计算出offset,传递给前端。前端下次查询会携带这两个数据。

优化点?

b站弹幕:我觉得可以用户在线推到redis,不在线就不推了,用户在线下拉刷新是去读redis,向上滑读mysql

树树:这么做是因为会导致redis内存压力太大?我感觉可以这样:用户上线的时候/刷新的时候将数据库所有关注的人的笔记加载到redis(当然还要设置一个过期时间);然后后面再……

14、附近商铺

GEO-地理坐标

添加坐标点

计算两个点之间的距离

计算一个圆内所有点,圆心是给定的

上面这些命令中,g1是key,代表一个存放了若干个地理坐标点的数据集。

实现

不同类型的商铺分开存。redis存的只有商户id和对应经纬度,而没有全部具体的商铺信息。

15、用户签到-BitMap

实现

位运算遍历每个bit位。

16、UV统计

UV、PV是什么?

HyperLogLog

重复插入的元素只记录一次

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88678.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88678.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Javaweb- 11 MVC架构模式

MVC&#xff08;Model View Controller&#xff09; 是软件工程中一种软件架构模式&#xff0c;它把软件系统分为模型&#xff0c;视图&#xff0c;控制器&#xff0c;三个基本部分。用一种业务逻辑&#xff0c;数据&#xff0c;界面显示分离的方法组织代码&#xff0c;将业务逻…

【电脑】主板的基础知识

主板&#xff08;Motherboard&#xff09;是计算机的核心组件之一&#xff0c;它将所有其他硬件部件连接在一起并协调它们的工作。以下是关于主板的详细知识&#xff1a;1. 架构组成一个典型的主板通常由以下几个主要部分构成&#xff1a;芯片组&#xff08;Chipset&#xff09…

【飞算JavaAI】一站式智能开发,驱动Java开发全流程革新

【作者主页】Francek Chen 【专栏介绍】⌈⌈⌈人工智能与大模型应用⌋⌋⌋ 人工智能&#xff08;AI&#xff09;通过算法模拟人类智能&#xff0c;利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络&#xff08;如ChatGPT&#xff09…

STM32中的RTC(实时时钟)详解

前言&#xff1a;为什么需要RTC&#xff1f; 在嵌入式系统中&#xff0c;时间记录是一项基础且关键的功能。想象一下&#xff1a;智能家居设备需要按时间触发开关灯&#xff0c;工业仪表需要记录传感器数据的采集时刻&#xff0c;物联网终端需要同步服务器时间戳……这些场景都…

Python技巧记录

空格拼接数组格式化显示 一维数组 arr [1, 2, 3, 4, 5] print( .join(map(str, arr))) # 直接转换并连接二维数组 for row in arr:print( .join(map(str, row)))for row in arr: 此循环会遍历矩阵arr中的每一行。这里的arr是一个二维列表&#xff0c;每一行代表一个子列表。m…

next.js打包后的前端资源如何进行部署和访问,为什么没有index.html

在 Next.js 项目中&#xff0c;打包后的部署方式和传统单页应用&#xff08;SPA&#xff09;有所不同&#xff0c;尤其是没有直接生成 index.html 这一点。以下是详细解释和部署指南&#xff1a;为什么没有 index.html 文件&#xff1f; Next.js 采用 混合渲染策略&#xff0c;…

Qt+FFmpeg网络视频流播放

init 函数用于初始化 FFmpeg&#xff0c;包括设置参数、打开输入、初始化视频和音频等。initOption 函数用于设置 FFmpeg 的参数选项。bool FFmpegThread::init() {if (url.isEmpty()) {return false;}//判断该摄像机是否能联通if (checkConn && isRtsp) {if (!checkUr…

【SpringBoot】Spring Boot 高并发优化终极指南,涵盖线程模型、JVM 调优、数据库访问、缓存策略等 15+ 核心模块

Spring Boot 高并发优化终极指南&#xff0c;涵盖线程模型、JVM 调优、数据库访问、缓存策略等 15 核心模块一、线程模型深度调优&#xff08;核心瓶颈突破&#xff09;1. Tomcat 线程池原子级配置2. 异步任务线程池隔离策略二、JVM 层终极调参&#xff08;G1GC 深度优化&#…

linux(CentOS-7-x86_64:NAT模式下解决yum无法使用:更新yum源的详细操作步骤2025)

目录 一、CentOS-7-x86_64的NAT模式下解决yum无法使用。&#xff08;更新可用的yum&#xff09; &#xff08;1&#xff09;首先保证能够ping通&#xff0c;也就是NAT模式下虚拟机有网络。 &#xff08;2&#xff09;错误&#xff1a;无法使用yum。比如我现在无法yum search if…

C++11的整理笔记

Lambda 表达式Lambda 表达式是 C11 引入的一种强大的功能&#xff0c;它允许你在代码中直接定义匿名函数对象。Lambda 表达式可以捕获上下文中的变量&#xff0c;并在需要时使用它们。它们通常用于简化代码&#xff0c;尤其是那些需要传递函数对象作为参数的场景&#xff08;如…

MS1826+MS9332 4K@30Hz HD4×2视频分割器

MS1826MS9332是一款支持4K30Hz分辨率的HD42视频分割器方案。支持四路HD输入两路HD输出&#xff0c;最高支持4K30Hz分辨率。该方案具有Scaler、OSD、画面分割、无缝切换、淡入淡出及旋转等功能。该方案现已实现量产&#xff0c;并提供完善的技术支持&#xff0c;适用于各类高清视…

用 MATLAB 模拟传染病传播:从 SI 模型到 SIS 模型的可视化之旅

在公共卫生研究中&#xff0c;数学模型是理解传染病传播规律的重要工具。通过数值模拟&#xff0c;我们能直观看到 “易感人群” 和 “感染人群” 随时间的变化趋势&#xff0c;甚至能预测疫情发展的关键节点。今天就带大家用 MATLAB 实现两个经典的传染病模型 ——SI 模型和SI…

Ruby如何采集直播数据源地址

在当今数字化的时代&#xff0c;实时获取并处理信息变得尤为重要。特别是在体育赛事、新闻报道等领域&#xff0c;及时获取最新的直播数据源对于提升用户体验至关重要。本文将介绍如何使用Ruby语言来采集特定网站的数据源地址 一、准备工作 首先&#xff0c;确保你的环境中已…

【fitz+PIL】PDF图片文字颜色加深

文章目录0 引言1 解决思路及流程1.1 思路1.2 代码实现2 完整代码与效果3 总结0 引言 没错&#xff0c;这是连续剧。女友对上一篇【fitzOpenCV】去除PDF图片中的水印得到的去水印效果很满意&#xff0c;于是问我可不可以再帮她处理一下另一个PDF文件&#xff0c;我二话不说答应…

tp8.0\jwt接口安全验证

环境&#xff1a;php8.3\tp8.1\firebase-jwt6.1app\middleware\JwtAuth<?php namespace app\middleware;use app\common\library\JwtHandler; use think\Request; use think\facade\Env;class JwtAuth {public function handle(Request $request, \Closure $next){// 获取当…

ReactNative【实战系列教程】我的小红书 5 -- 文章详情(含轮播图 ImageSlider,点亮红心动画 Heart,嵌套评论等)

最终效果 安装依赖 npm i dayjs用于对时间进行格式化 必备组件 轮播图 ImageSlider https://blog.csdn.net/weixin_41192489/article/details/149224510 点亮红心动画 Heart components/Heart.tsx import AntDesign from "expo/vector-icons/AntDesign"; import …

哔哩哔哩第三方TV-BBLL最新版

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://pan.xunlei.com/s/VOUtUcaymd9rpgurgDKS9pswA1?pwdp76n# 【​本章下载二】&#xff1a;https://pan.xunlei.com/s/VOUtUcaymd9rpgurgDKS9pswA1?pwdp76n# 【百款黑科技】&#xff1a;https://uc…

用YOLOv5系列教程(1)-用YOLOv5轻松实现设备状态智能监控!工业级教程来了

用YOLOv5轻松实现设备状态智能监控&#xff01;工业级教程来了设备运维革命&#xff1a;15分钟教会你的摄像头看懂指示灯状态工业现场各种设备状态指示灯是工程师的"眼睛"——红灯报警、绿灯运行、黄灯待机。但人工巡检耗时费力&#xff0c;关键故障容易漏检&#xf…

笔记-分布式计算基础

Distributed Computing 划分数据并行&#xff08;DataParallelism&#xff09;将数据分为n份&#xff0c;发送到n个GPU上&#xff0c;每个GPU上都存在一个完整的大模型 缺点&#xff1a; 模型太大Pipeline Parallelism&#xff08;串行的&#xff09;将模型做split,每个GPU负责…

Android Studio 2024,小白入门喂饭级教程

一、下载Android Studio 1、从安卓官网安卓官网下载Android Studio。 ​ ​ 二、安装Android Studio 1、双击android-studio-2024.3.2.15-windows.exe。 ​ ​​ ​ ​ ​ ​ 三、新建工程 1、双击桌面图标​ 打开Android Studio。 ​ 选“Empty Views Activity…