八股文10:redis学习+Elasticsearch学习(针对项目)

本文深入探讨了Redis的数据结构,包括SDS、IntSet、Dict、ZipList、QuickList和SkipList,以及BigKey问题。接着分析了Redis的过期策略、淘汰策略和缓存异常,如缓存不一致、雪崩、击穿和穿透,提出解决方案。此外,介绍了Redis的分布式锁和事务管理,并讨论了持久化机制AOF和RDB。最后,分享了如何在实际项目中使用Redis缓存,如论坛项目的点赞数、粉丝数、登录凭证等,并简要提及Spring整合Redis和Elasticsearch解决缓存异常问题。
摘要由CSDN通过智能技术生成

文章目录

Spring 整合redis(细致)

在这里插入图片描述
在这里插入图片描述

0. redis基本的架构

在这里插入图片描述
在这里插入图片描述

1. 数据结构(面试重点)

在这里插入图片描述

基本数据类型

底层的数据结构

在这里插入图片描述

在这里插入图片描述

1. SDS动态字符串 && 源码解读

在这里插入图片描述
在这里插入图片描述

动态字符串的扩展与申请内存空间(sdshdr32最大为512MB,可是sdshdr64是干嘛的)
在这里插入图片描述

在这里插入图片描述

动态字符串的特征与优势(查询长度快速+不会发生缓冲区溢出+动态扩容)
在这里插入图片描述

2. IntSet(整数数组)

其实这个和SDS很像,只不过数组存放的是int整数(但是不同的是:这里元素的存储是有序的,按照二分搜索进行判断,同时是不会出现重复的)
在这里插入图片描述

在这里插入图片描述

编码的升级过程:注意只能升级不能进行降级(2–>4,4–>8的两种情况),一旦有一个新加入的元素编码的等级更高,此时整个数组都需要升级(为保证有序性,按照倒序往后更新原来每个数的位置)
在这里插入图片描述

redis使用二分查找来确保intset数组的有序性与不重复(居然用的不是hash)

在这里插入图片描述

确保数据唯一的判断方式的源码

在这里插入图片描述

3. Dict(字典结构/hashmap结构)

为了实现渐进式的rehash操作,采用两个hash表结构来进行工作。
实际使用的dict结构 {两个hash表:dictht(由桶+链表实现,每个链表上是dictEntry节点)}

在这里插入图片描述

在这里插入图片描述

新元素插入在冲突链表的队首位置(因为这样方便移动计算)

在这里插入图片描述

rehash触发的条件(已保存节点数/哈希表的桶的数量==used/size)

在这里插入图片描述

在这里插入图片描述

对比一下java中的hashmap扩容的情况,java中的负载因子更小只有0.75(此外当链表长度超过8&&数组长度小于64也会触发扩容)

在这里插入图片描述

渐进式rehash操作

在这里插入图片描述

一般使用的是dict结构中dictht[0]哈希表1(利用rehashidx来表示是否处于rehash的阶段,如果等于-1说明rehash结束,此外这个rehashidx也会作为hash表1数据迁移的数组位置的index指针记录—rehash的初始化值为0,每次迁移完一个位置就会进行++自增操作)

在这里插入图片描述

rehash渐进式实现(没有采用子进程来完成扩容,而是采用了主线程进行缓慢的渐进式扩容)
在这里插入图片描述

注意新增的操作只会在hash表2进行,但是查删改会同时在两个表并行操作(保证了表1的只减不增)
在这里插入图片描述

在这里插入图片描述

4. ZipList(压缩列表)

在这里插入图片描述

在这里插入图片描述

连锁更新问题

使用“记录前一个节点的长度”这种结构可以同时推断出前一个后一个节点的起始的地址位置(注意:引发连锁更新空间的分配—但是这种情况比较极端类似“多米诺牌效应”—针对有多个251-253字节之间的entry排列&&有一个entry发生长度变化)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5. QuickList(双端链表—节点是ziplist)

在这里插入图片描述

quicklist的节点quickListNode节点内部都会指向一个压缩列表的结构(控制了压缩列表的长度,既解决了寻址的问题,有解决了连锁更新的问题)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意:虽然quicklist减小了连锁更新的长度,但是还是会出现连锁更新的问题,这是无法避免的

在这里插入图片描述

6. SkipList(跳表—zset的底层结构–有score)

跳跃表首先必须按照顺序存储的链表结构,同时每个节点的指针层数是随机生成的,不同层级的指针对应的下一个节点的跨越维度是不同的(将查询的复杂度平均降到了log(N))
在这里插入图片描述

首先按照节点的score分数排序,之后(针对相同的score)按照ele元素值的字典排序----限制了查询也是这种顺序(先按score,再按ele)
在这里插入图片描述

注意是多级的索引指针,不是多级的链表,每个节点含有zskiplistNode结构的level数组【层级随机生成,(0,1)随机数小于0.25层级就加1,直到遇到的随机数大于0.25停止)------高低层之间的节点数量尽量维持1:2的关系】

在这里插入图片描述

对于同一个节点,不同层级的索引,对应的下一个节点和相应的跨度是不一样的
在这里插入图片描述

RedisObject的封装(恰好封装为5种基本的数据类型)

总结一下:string采用int(整数)和SDS的动态字符串的结构,List结构采用的链表+压缩列表的结构(前一个节点的长长度+本节点的内容及长度),set结构采用的intset(数据较少)/dict结构(数据较多),hash结构压缩列表(数据较少)/dict结构(数据较多时),zset结构采用压缩列表/ skipList+dict(这两个同时需要,一个按照分数排序,一个按照键值对存储键与分数的关系)
RedisObject组成:type对象类型(5种),encoding底层编码方式,lru表示对象最后一次访问的时间,recount对象引用计数器,指针(指向实际的数据空间,数据据结构就是那5种)
在这里插入图片描述

在这里插入图片描述

string–string

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

编码方式:针对字符串是否是数字,是否字符串长度小于或大于32字节(这个32按照具体的redis的版本来规定),可以分为3种编码方式。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
默认是使用raw的编码方式,字符串长度最大为512MB
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

string–list(双向链表)值

使用quicklist实现:本质是双向链表+ziplist

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现队列和栈的不同场景在队列的场景下,可以实现消息队列的功能,但是不能实现多个消费者消费同一条消息,即不能实现消费者组的功能-----除非使用redis的stream)在这里插入图片描述在这里插入图片描述

string–hash(string,string)值

数据较多时,使用dict结构实现
在这里插入图片描述

数据较少时,使用ziplist结构实现

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

string–set(无序集合)值

正常情况下,使用dict实现;只有元素比较少而且存储的是整数,才会采用Intset结构实现!!!!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

string-- zset(有序集合,含权重)值 [有序指针对权重的排序]—跳表

redis 跳表实现zset

同时使用两种的数据存储结构—skipList(按分数排序查询)+dict(用于键值对的保存与去重),占用你内存很高,但是支持快速查询和分数排名的操作(但是数据比较少时,直接使用压缩列表)
注意:ele不能重复(相当于key),score当然可以重复(相当于value)

在这里插入图片描述

可以看出zset是由跳表和dict两种数据结构构成的

在这里插入图片描述

不需要排序时—直接使用dict结构:key–val(分数)
需要排序时—直接使用跳表结构

在这里插入图片描述

元素较少且每个元素的长度比较少时,采用压缩列表实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

BigKey的问题

在这里插入图片描述

在这里插入图片描述

高级数据类型(主要是应用场景)

HyperLogLog(统计不重复数据的个数–不精确)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

注意m/sigma的那个分式项就是调和平均数(m就是16384,14个bit位对应的十进制数),其中的kmax就是统计的hash值的从第15位开始往左的连续0的个数的最大值(历次增加数的最大值,其实最大为50,所以需要最多6bit来统计这个count),其中的A修正常数可能是Redis的员工自己做了无数次的实验搞出来的一个修正常数。

在这里插入图片描述

为什么选择这样的调和级数

在这里插入图片描述

Bitmap(bit数组读取,bit设置)

在这里插入图片描述

s实际上字符数组本质还是保存的位数组二进制的数据

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意bitmap的底层数据结构就是sds,本质是一个二进制的数组,统计数组位置上的1的个数(设置对应位置的index为0或1,这个index或者偏移量必须是整数)

在这里插入图片描述

敖丙三太子的解释

在这里插入图片描述

键与bitmap数组偏移量位置映射关系的设计

将键(实际一般我们实际业务的中就是"数值类型",不是字符串—需要自己设计映射的关系)定位为bit数组相关的位置并置该位置为1,之后利用工具统计相应的整个数组的1的总数(注意可能需要新加入的key对应的offset大于了原始的数组的长度,此时需要对数组进行扩容)

在这里插入图片描述

牛客网的项目的键就是用户的id,直接id就是相应的偏移量

在这里插入图片描述

根据bitmap的offset(比如字符串的形式,需要先进行hash映射成为数值型的偏移量)如何定位在这个bit数组的位置呢??(可以使用hash算法,或者使用多层的hash算法解决hash碰撞)

在这里插入图片描述

这个总结的很好(偏移量:1.直接利用“数值的表面值”;2。对这个数值重新做一层hash的映射再来定位为数值的偏移量)
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
布隆过滤器使用的是多个hash函数,需要将多个hash函数对应的位置置为1
在这里插入图片描述

如何统计redis中bitcount数组中的1的个数(查表法+SWAR算法)

查表法(使用少量位数的情况):

在这里插入图片描述

SWAR算法(针对32位–4字节的情况进行判断):
知乎大神讲解,有空看一下
在这里插入图片描述

跑了一下

在这里插入图片描述

redis的统计维数组的bit个数采用的是查表法(不足28字节,每个字节按照查表法,即8比特共256种可能)和SWAR算法(每4个字节进行处理–共读取7组)相结合的方法

long long redisPopcount(void *s, long count) {
   
    long long bits = 0;
    unsigned char *p = s;
    uint32_t *p4;
    // 为查表法准备的表
    static const unsigned char bitsinbyte[256] = {
   0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值