Redis 八股

目录

数据类型

字符串:

List:

HASH:

Set:

Zset:

BitMap:(这个及以下是后来新增的数据结构)

HyperLogLog:

GEO:

Stream:

主要数据结构

SDS

压缩列表

HASH

整数结合

跳表

quickList

listpack​编辑

持久化

AOF(Append only FIle)

AOF写回策略

AOF重写

aof后台重写

rdb快照

混合持久化

 布隆过滤器

分布式锁


数据类型

五种常用数据类型:字符串,List,HASH,Set,Zset(也叫sorted Set)

字符串:

key-Value形式储存,key可以理解为字符串的名字,Value是实际字符串的内容

底层有SDS实现(SDS:简单动态字符串),个人理解是基本字符串的扩展。短的时候是embstr,长了是raw。简单理解就是,Redis额外开辟了一或二个额外空间来储存字符串相关的内容,比如字符串的长度,字符串的实际地址等。

基本的使用语句是set,get,setNX等。

字符串可以用来当做分布式锁:很多个客户端共同使用一个Redis,在进行一个操作的时候,设置一个字符串:set lock_key unique_value NX PX 10000 后面的PX 10000 是超过这个时间自动释放锁(防止死锁)NX是not exit 不存在这个字符串时才会加锁。

字符串也可以用作缓存:储存数据就是了;也可以用作计数器:字符串有自增指令INCR和INCRBY,可以使字符串的值加1或加多,并且由于是单线程实现的,不需要考虑竞态问题

List:

Key-value储存形式,是一个双端队列

内部结构listPack

基本语法LPUSH,LPOP,RPUSH,RPOP等,并且有阻塞获取的语法BRPOP:阻塞,右边,取

可以用作简单的消息队列,但有些问题:List实现的消息队列不能实现持久化;不能多个消费者处理同一个消息;需要手动维护每个消息的唯一id

HASH:

Key-field-value储存形式, key是HASH的名称,filed可以类比哈希表的key,Value可以类比哈希表的Value

内部储存结构是listPack和哈希表

基本语法是HSET、HMSET等(M指的是Multi,一次处理多个)

Set:

Key-filed储存形式

内部实现时整数集合和哈希表

和普通的set一样,每个元素唯一,但是是无序存储。

基本语法是SADD,SREM,SCARD(cardinality,集合的势),SMEMBERS(获取集合中的全部元素)。并且SET支持SUNION,SINTER,SDIFF(并,交,差)集合运算

Zset:

key-score-value储存形式,集合中的元素按照score的顺序排序。

内部实现时listPack和跳表

基本语法是ZADD,ZREM等。支持交、并操作(不支持差了)

BitMap:(这个及以下是后来新增的数据结构)

每个元素都是二进制的(相当于维护了一个二进制的数组,但不需要实现设定大小)

基本语法SETBIT,GETBIT等,支持二进制运算:与&,或|,异或^,反~

主要用于制作布隆过滤器或记录一些二进制的状态(比如签到等)

HyperLogLog:

用处就是可以用一个相当小(12k)的内存来统计具体有多少个元素(能用,但是有误差0.81%)

常用指令PFADD(添加),PFCOUNT(计数),PFMERGE(合并)

比如向这个结构中PFADD100w个数据,可以用PFCOUNT快速得到大致的数量

GEO:

地图存储,底层用的是ZSET

Stream:

主要解决list实现消息队列时不能持久化,不能多个消费者处理的问题

基本语法XADD,XREAD等。可以定义XGROUP(消费者组),用消费者组读取内容(一组可以有多个消费者)。组内消费者不能读取同一条数据,但不同组的消费者可以处理同一条内容。

处理完成后向Stream返回XACK确认消息已经处理完毕。没处理完的数据可以用SPENDING获取,这样当Redis意外关闭时未处理的消息也不会丢失。

但是Stream存储的消息中间件可能会丢失,并且大小受内存大小的限制。如果有高要求的话还是用kafka等专业消息队列比较好

主要数据结构

我们之前说的,上面的数据类型都有一个key,以及对应的value。(key是数据的名称,value就是对应的数据)。而Redi是用一个哈希表来保存这些全部的数据的。在下图中可以看到,Redi维护了一个dictEntry,就是哈希表中储存的键值对。每个对有一个key指针和value指针,key指针指向一个String,value指针指向对应的具体数据类型。

Redis中存储的都是Redis对象。有一个type标志这个对象是哪一种数据类型(比如Zset),encoding表示底层数据结构。比如SDS会有embstr和raw两种选择。最后的就是一个指针,指向真正的数据

SDS

就是设计了一个头结构,记录了字符串的长度和已经分配的内存空间大小。这样获取长度的复杂度为O(1),并且执行字符串拼接的时候不用再考虑内存分配(C的问题),最后,由于已经标明了字符串的长度,因此不需要再为字符串添加‘/0’的终止符,使得字符串现在不止可以储存文本,也可以储存二进制数据了

压缩列表

结构的会储存结构整体占用的字节数,列表尾的偏移量,节点数和结束点。(不再是普通链表的分块存储,这样提高了CPU缓存的命中率)

每个节点会储存上一个节点的长度,本节点的数据类型和长度,实际数据内容。

压缩列表的两个问题就是:查询中间节点需要的复杂度是O(n);连锁更新问题(主要是在改变节点内容的时候需要改变下一个节点的prevlen值,可能会出现改一个之后后面的很多个都要改的情况。)

HASH

和java7及以前的逻辑是基本上一样的。不过是扩容的时候不是一次性复制,而是用到哪个bin,就把哪个bin的内容复制过来

整数结合

就是一块连续的内存空间,三个量:编码方式,元素个数和保存元素的数组。通过二分查找实现有序数组进而实现唯一。不过每个数字占的大小有限制(比如都要小于16位),如果有一个数超过这个限制,那么所有的数都要大小升级,数组整体扩容

跳表

很多层,每层维护一个链表。目的是为了实现链表的更快查询,大致结构长这个样子。链表的层数要事先设计好。节点按照一定的概率向上升(这个概率要人为给定)

跳表的主要优势是他结构简单易于实现,范围查询更快,插入删除需要的操作更少,每个节点储存的指针更少(跳表平均1/(1-p),平衡树两个)

quickList

添加元素的时候,不再直接新建一个节点,而是先看这个节点对应的压缩链表能否容纳这个节点(就是看列表中是否还有足够的空闲内存和可容纳节点个数),如果可以的话就放进去,不能的话才新建一个节点

listpack

和压缩链表相比,实际上就是把pre(上一个节点的长度)删掉,换成了当前节点的长度。相当于压缩链表的改进版。

持久化

Redis的持久化(其实就是将内容写入到磁盘)有AOF和RDB两种策略

AOF(Append only FIle)

就是写一个aof文件,这个文件保存在磁盘中。具体来说有以下处理过程:

Redis接受到写命令之后,先在内存中修改对应的数据,然后把这个修改数据的命令储存在aof缓冲区中,等待合适的时机把缓冲区中的内容写入到aof文件中。

AOF写回策略

所谓的合适的时机就是aof的刷盘策略,总共有三种:always、everysec和no。第一种就是每次有修改命令,都把内容写入到内核空间的page cache中同时写入到磁盘中。everysec是每一秒写一次,no是交给操作系统决定什么时候写入磁盘。(其实就是aof的三种刷盘策略,主要是标明主进程什么时候调用操作系统的fsync方法进行刷盘,aof缓冲区中的内容都是写完之后直接调用i/o系统的Write方法写入到page cache中)

AOF重写

如果aof文件太大,那么就会触发aof文件的重写。主进程先fork一个子进程(就是复制了一份页面给子进程),子进程根据页表内容读取内存并写入到新的aof文件中(实际上是生成一个写这条内容的指令,并将这条指令保存下来)。这期间父进程和子进程是共享物理内存的,当父进程对某一块内存进行修改的时候,才会将这块内存原有的内容复制给子进程,父进程修改内存(所谓的写时复制)。这样就保证了子进程写的内容都是父进程fork时内存的样子。

aof后台重写

当子进程生成新的aof文件时,主线程会正常执行内容(并不会阻塞),这些正常执行的操作将不止被缓存在aof缓冲区中,还会被写在aof重写缓冲区中。子进程将页表中的内容写好后,会告诉主进程已经重写好(发送一条信号)。主进程将aof重写缓冲区中的内容追加到新的aof文件中,然后将老aof文件覆盖。

rdb快照

有两种策略:save和bgsave。save是主进程完成快照生成工作,此时Redis会被阻塞,bgsave是fork一个子进程生成对应的快照。

其实逻辑跟aof重写差不多,只不过aof重写的时候是生成语句并保存,rdb重写是记录对应的数据内容,用的时候直接读取就可以了。并且rdb快照生成由于是全量复制,所以会很慢,需要平衡生成快照的时机和丢失内容多少的关系。(aof也是全量扫描,但有的内容可能在写的时候可以由一个可重复指令完成,因此某些情况下会比rdb快)

混合持久化

在aof重写时,不再生成读取内容生成指令,而是直接按照rdb的模式复制,在处理aof重写缓存的时候才是aof的记录语句的形式。

过期删除和内存淘汰

这两个操作并不相同:过期删除指的是我们可以对一个Redis对象设定存活时间,超出这个时间后删除对象时过期删除,内存淘汰则是当Redis的储存空间不足时,需要对一些对象进行删除

过期删除:

我们知道在设定一个Redis对象的时候,可以设置他的过期时间,那么如何对过期对象进行删除就有三种策略:

  1. 定时删除。这种策略指的是在设定一个可以过期的Redis对象时,同时也启动一个定时事件,事件到期后由一个专门的线程来对他进行删除。这种方法思路很简单,但实际使用会消耗太多资源,比如CPU时间等,并且也可能会导致一些热点数据同时大量被删除,因此这种删除策略并没有被采用
  2. 定期删除。这种策略说的是每过一段时间检查一部分的对象(一般是20个),删除过期的对象,如果说过期对象大于五个,就继续随机抽取然后删除,直到超时或者过期对象少于五个
  3. 惰性删除。这种指的是完全不检查,等到用的时候再看有没有过期,过期的话删除。这种策略的问题也十分明显:会有大量的过期对象没有被删除,占用空间

Redis使用的是定期和惰性结合,一方面会定期检查,另一方面使用对象的时候如果过期就删除

Redis实现了一个HashMap来储存每个可过期对象的对象地址和对应的创建时间,使用的时候比较这个创建时间。需要指明的是,这个HashMap和我们可使用的hash并不一样:我们使用的hash数据结构要求key是字符串,这个HashMap存的是地址,并且这个HashMap是Redis底层自己实现的,我们用不了

内存淘汰

当内存不够用的时候,就需要淘汰一些不常用的对象。淘汰手段一般是LRU和LFU。LRU指的是找到最久没有使用的,LFU指的是使用次数最少的。Redis对这两个都做了优化

  1. LRU:每个对象储存最近被使用的时间,删除的时候随机采样五个,删除最久没用的内个
  2. LFU:这个相对复杂一点,Redis给每个对象增加了一个LFU字段,这个字段记录了上次使用的时间和一个标志使用频次的数,这个频次的数初始为5,对象每次被使用时会先减1,然后根据上次使用时间和现在的时间算一个权重出来,频次再加上这个权重作为最终的结果

具体使用哪个Redis提供了一些参数,修改这些参数就行了

主从复制

具体应用:

 布隆过滤器

可以把他理解成一个只能添加和查找元素是否存在的数据结构(某种意义上的Hashmap?)

他的特点是可以实现快速查找,但查找只能保证他认为不在的不在,他认为在的也不一定在

布隆过滤器底层数据结构是一个长二值数组。同时设置了几个hash函数。

        每次插入元素时,就计算这个元素对应的hash值,并将关于数组长度取模后的对应位置的值设为1。

        在查询的时候,计算这个元素在数组中对应的几个位置(也是根据hash值取模得到的),如果全为1,那么认为他存在(当然也可能不在)。

应用场景:解决redis缓存穿透

实际使用:Redis其实并没有实现所谓的BitMap数据结构,他是依赖字符串实现的。在我的项目中,布隆过滤器是这样设计的:

首先根据公式计算出需要的哈希函数数量和数组长度:n是预估的数量个数,p是误判率

计算得到的m就是需要的布隆过滤器长度(指定的字符串所占的比特数),k是需要的哈希函数个数,取整。

选择一个哈希函数(我选的是一个,不是k个!)比如md5,计算当前元素的哈希值。将计算的到的哈希值拆成k分,每份再计算一个hashCode(),对长度取模就得到一个数组,长度为k。加入的时候将这个数组每一位上的值设置为true。查找的时候就看对应位有没有false,只要有一个就说明当前元素没有被加入过。

分布式锁

分布式锁的实现也是字符串。具体来说,每个进程(或者线程)公用一个Redis的时候就可以设计分布式锁,使得只有一个线程可以使用资源。

分布式锁主要依赖字符串的setIfAbsent和get方法。当一个线程尝试获取锁的时候,就会在Redis中尝试设置一个字符串,设置成功的话就相当于成功获取了锁,否则就是获取锁失败,任务结束之后删除字符串就相当于释放了锁。

单一Redis节点需要考虑这几个问题:

  1. 某个线程在删除字符串时,要确保这个字符串是他自己设置的
  2. 要保证删除操作是原子的
  3. 释放锁之前锁不能自动过期

对于第一个问题,我们考虑这个场景:两个线程都想要获取锁,线程A获得锁,但超时了,锁自动释放,线程B新建了锁,线程A执行完任务删除了锁,但这时他删除的是线程B新建的锁。解决思路就是用字符串的key当做锁名,value当做线程对锁的持有证

第二个问题,要先检查锁存在才能释放,这中间存在时间,这段时间里可能会出现锁过期然后另外一个线程新建锁的问题,因此要让Redis一次性检查并且释放。具体实现思路就是使用lua脚本。Spring中,用字符串写好脚本然后提交execute。

第三个问题,采用看门狗机制,对所有的任务线程设计一个定时任务线程池,新建任务线程的时候就把自己线程的信息传递给看门狗,看门狗定期用收到的信息执行锁延时任务:只要线程还在运行,就延长锁。任务线程结束之后再中断这个定期任务。(注意这里要用到线程的ThreadLocal来记录每个线程独立的信息,比如他的锁名,锁持续时间等等)

如果多Redis节点的话需要考虑redLock,但我这里是单节点就没有实现

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

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

相关文章

基于协同过滤的文学推荐系统设计【源码+文档+部署】

基于协同过滤的文学推荐系统设计 摘要 随着信息技术的飞速发展和文学阅读需求的日益多样化,构建一个高效、精准的文学推荐系统变得尤为重要。本文采用Spring Boot框架,结合协同过滤算法,设计并实现了一个基于用户借阅行为和社交论坛互动的文学…

鸿蒙电脑:五年铸剑开新篇,国产操作系统新引擎

出品 | 何玺 排版 | 叶媛 前不久,玺哥发布的《鸿蒙电脑,刺向垄断的利刃,将重塑全球PC市场格局》发布后,获得了读者朋友的积极反馈,不少都期望鸿蒙电脑早日发布。 如今,它真来了! 5月8日&…

EWOMAIL

1、错误 Problem: problem with installed package selinux-policy-targeted-3.14.3-41.el8.noarch package fail2ban-server-1.0.2-3.el8.noarch requires (fail2ban-selinux if selinux-policy-targeted), but none of the providers can be installed - package fail2ban-…

qt5.14.2 opencv调用摄像头显示在label

ui界面添加一个Qlabel名字是默认的label 还有一个button名字是pushButton mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <opencv2/opencv.hpp> // 添加OpenCV头文件 #include <QTimer> // 添加定…

Spring三级缓存的作用与原理详解

在Spring框架中&#xff0c;Bean的创建过程涉及到了三级缓存机制。这个机制主要是为了提高单例模式下bean实例化和依赖注入的效率。本文将深入探讨Spring中的三级缓存&#xff0c;以及其在bean生命周期中的重要作用。 首先&#xff0c;让我们理解什么是三级缓存。Spring中的三…

IoTDB集群的一键启停功能详解

IoTDB&#xff08;Internet of Things Database&#xff09;作为一种专为物联网设计的高性能时序数据库&#xff0c;支持单机与分布式等多种部署模式。随着节点数量的增加&#xff0c;手动管理集群的启动与停止过程变得繁琐。为了提升部署效率&#xff0c;IoTDB 提供了一键启停…

Oracle学习日记--Oracle中使用单个inert语句实现插入多行记录

目录 前言&#xff1a; 问题现象&#xff1a; 问题分析&#xff1a; 解决方法&#xff1a; 1、insert into ... union all句式 2、insert all into ...select 1 from dual句式 总结&#xff1a; 前言&#xff1a; 最近项目中使用到了Oracle数据库&#xff0c;由于Oracle数…

LabVIEW 程序运行时内存不足报错原因

在 LabVIEW 程序开发与运行过程中&#xff0c;内存不足报错并退出是常见且棘手的问题。这不仅影响程序稳定性&#xff0c;还可能导致数据丢失与系统崩溃。以下从程序设计、硬件资源、系统环境等多维度深入剖析其成因&#xff0c;帮助开发者准确定位并解决问题。 ​ 一、程序设…

【GAN网络入门系列】一,手写字MINST图片生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

Baklib加速企业AI数据智理转型

Baklib智理AI数据资产 在AI技术深度渗透业务场景的背景下&#xff0c;Baklib通过构建企业级知识中台架构&#xff0c;重塑了数据资产的治理范式。该平台采用智能分类引擎与语义分析模型&#xff0c;将分散在邮件、文档、数据库中的非结构化数据转化为标准化的知识单元&#xf…

如何在Windows右键新建菜单中添加自定义项,将notepad添加到新建菜单

一、简介 Windows 右键新建菜单的核心管理机制隐藏在注册表的 HKEY_CLASSES_ROOT 根键中。这里存在两种关键注册表项&#xff1a;文件扩展名项和文件类型项&#xff0c;它们共同构成了新建菜单的完整控制体系。 以常见的.txt文件为例&#xff0c;系统通过以下机制实现新建菜单…

中大型水闸安全监测系统建设实施方案

一、方案背景 随着科技的不断进步&#xff0c;水利工程的数字化转型已经成为提升城市水资源管理效率和增强防洪能力的关键。今天&#xff0c;我们将引导您深入了解我国大中型水闸安全监测管理系统的构建方案&#xff0c;探讨如何运用先进技术确保国家水安全&#xff0c;提升水利…

Gartner《如何有效融合Data Fabric 与Data Mesh数据战略》学习心得

在当今数字化时代,数据已成为企业最为重要的战略资产之一。企业对于高效的数据管理架构的需求日益迫切,以确保能够从海量数据中提取有价值的信息,支持业务决策和创新。近年来,数据编织(Data Fabric)和数据网格(Data Mesh)成为了数据管理领域的两个热门概念,在行业内引…

matlab建立整车模型,求汽车的平顺性

在MATLAB中建立整车模型评估汽车平顺性&#xff0c;通常采用多自由度振动模型。以下是基于四分之一车模型的详细步骤和代码示例&#xff0c;可扩展至整车模型。 1. 四分之一车模型&#xff08;简化版&#xff09; 模型描述 自由度&#xff1a;2个&#xff08;车身垂直位移 z2…

探究电阻分压的带负载能力

我们经常使用两个电阻去分压来获得特定的电压,那么我是两个大阻值电阻分压获得的电压驱动能力强,还是小阻值电阻分压得到的电压驱动能力强呢? 一、电压相同时,电流的大小 下面是两个阻值分压得到的仿真图 电路分析: VCC都是5V,探针1和探针2测到的电压都是1.67V; 根据…

牛客网NC22222:超半的数

牛客网NC22222:超半的数 题目描述 输入输出格式 输入格式&#xff1a; 第一行包含一个整数 n (1 ≤ n ≤ 1000)第二行包含 n 个整数 a_i (1 ≤ a_i ≤ 10^9) 输出格式&#xff1a; 输出一个整数&#xff0c;表示出现次数超过一半的那个数 解题思路 这道题目有多种解法&a…

开发日常中的抓包工具经验谈:Charles 抓包工具与其它选项对比

开发日常中的抓包工具经验谈&#xff1a;HTTPS调试怎么选&#xff1f; 在移动开发或Web API联调时&#xff0c;网络请求常常成为问题定位的第一难题。尤其是面对加密的 HTTPS 请求&#xff0c;传统浏览器调试工具已显得力不从心。 我们团队最近在排查一个安卓应用中的支付延迟…

哈希表实现(1):

1. 哈希&#xff1a; 之前我们的红黑数的查找是由于左边小右边大的原则可以快速的查找&#xff0c;我们这里的哈希表呢&#xff1f; 这里是用过哈希函数把关键字key和存储位置建立一个关联的映射。 直接定址法&#xff08;函数函数定义的其中一种&#xff09;&#xff1a; 直…

泰迪杯特等奖案例深度解析:基于多级二值化与CNN回归的车牌识别系统设计

(第八届泰迪杯数据挖掘挑战赛特等奖案例全流程拆解) 一、案例背景与核心挑战 1.1 行业痛点与场景需求 在智慧交通与无感支付场景中,车牌识别是核心环节。传统车牌识别系统在复杂光照、污损车牌、多角度倾斜等场景下存在显著缺陷。根据某智慧油站2024年运营数据显示,高峰期…

光学变焦和数字变倍模块不同点概述!

一、光学变焦与数字变倍模块的不同点 1. 物理基础 光学变焦&#xff1a;通过调整镜头组中镜片的物理位置改变焦距&#xff0c;实现无损放大。例如&#xff0c;上海墨扬的MF-STAR吊舱采用30倍光学变焦镜头&#xff0c;焦距范围6~180mm&#xff0c;等效焦距可达997mm。 数字…