【2024最新版,redis7】redis底层的10种数据结构

前言:本文redis版本:7.2.4
本文语雀原文地址(首发更新):https://www.yuque.com/wzzz/redis/xg2cp37kx1s4726y
本文CSDN转载地址: https://blog.csdn.net/u013625306/article/details/136842107

1. 常见的数据结构

Redis常见的数据结构有5种: String,List, Set, ZSet, Hash,这只是对外的数据结构
Redis对内的底层数据结构具体实现方法还有如下几种:int, raw, embstr, linkedlist, ziplist, hashtable, intset, skiplist,quicklist,listpack
他们之间的映射关系如下
在这里插入图片描述

2. Redis对象基础知识

Redis对象,就是不管你是什么类型的redis变量,都必须要有这个对象,可以想象是一种对象头,就比如你java中所有的类,不管是系统的,新建的,他们都有一个Object父类
Redis对象的定义如下:

typedef struct redisobject{
    // 类型 4bit 即用于表示[String, List, Set, ZSet, Hash, Geo, HyperLogLog, Stream, Bitmaps]中的一种
    unsigned type:4;
    // 编码方式 4bit 即用于表示[int, embstr, raw, ziplist...]中的一种
    unsigned encoding:4;
    // LRU时间 24bit (相对于server.lrulock)
    unsigned lru:24;
    // 引用计数 32bit redis里面的数据可以通过引用计数进行共享
    int refcount:32;
    // 用于指向具体对象的指针 64bit,比如指向string, list等等
    void *ptr;
}

一个redisobject占用的存储空间:4+4+24+32+64=128bit/8=16字节

3. String底层数据结构

3.1 int类型

首先 object encoding key这个命令可以返回key在redis中内部的编码方式
当保存的字符串是数字类型时,长度≤19位,此时内部就会用int表示
image.pngimage.png
当保存的是个20位数字时
在这里插入图片描述

image.png
当保存的字符串首位数字是0时,不会用int表示,因为int前面的0存可以存,但读不出来呀
image.pngimage.png

3.2 embstr类型

embstr类型是redis专门用来保存短字符串的一种优化编码,其结构如下图所示
embstr底层编码方式
该结构中,redisobject是必须有的,每个redis对象都有。embstr使用时,只分配一次内存空间,因为redisobject和sdshdr是连续的,所以在一起分配,当embstr字符串扩大时,也就照成了要重新给embstr类型的字符串重新分配内存空间,所以redis中为了杜绝这种情况,embstr字符串是只读的,不能修改,如果修改了,就会变为raw编码方式,无论其长度是否达到了44字节

3.3 raw类型

raw类型是redis用来保存,可变长的,可修改的字符串,其结构如下图所示:
raw编码的字符串对象
如图所示,raw方式保存字符串时,会进行两次申请内存空间,第一次是给redisobject申请,第二次是给sdshdr申请。
这样的方式,虽然比embstr多申请一次内存空间,但是这种方式当字符串长度发生扩展时,只用重新申请sdshdr的内存就行了,然后修改ptr指针,可以有效防止内存碎片。

3.4 SDS 简单动态字符串

3.4.1 3.2版本之前

SDS的结构定义在sds.h文件中,以前版本的SDS比较简单,有三个属性,分别是len, free, buf数组

// 3.0
struct sdshdr {
    // 记录buf数组中已使用字节的数量,即SDS所保存字符串的长度
    unsigned int len;
    // 记录buf数据中未使用的字节数量
    unsigned int free;
    // 字节数组,用于保存字符串
    char buf[];
};

3.4.2 3.2版本及之后

在3.2版本之后,根据字符串长度的不同,分别初始化不同的sds

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

3.2版本之后,会根据字符串的长度来选择对应的数据结构

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)  // 32
        return SDS_TYPE_5;
    if (string_size < 1<<8)  // 256
        return SDS_TYPE_8;
    if (string_size < 1<<16)   // 65536 64k
        return SDS_TYPE_16;
    if (string_size < 1<<32)  // 4294967296 4G
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

以下是raw编码方式下sdshdr8的一个字符串示例:

3.5 SDS动态字符串与C语言字符串的区别

C语言使用长度N+1的字符数组来表示长度为N的字符串,字符数组的最后一位是’\0’, 用以表示字符串的结尾,但是这种方式不能满足redis对字符串的安全性,效率和功能上的要求,所以redis在字符串上面使用了sds动态字符串来保存字符串,相比与c语言字符串,sds动态字符串有以下优点

C语言字符串SDS动态字符串SDS的优点
C语言中没有保存字符串长度,当想获取字符串长度时,需要遍历字符串,统计长度,时间复杂度O(n)SDS结构中保存了字符串的长度len, 当程序想获取字符串的长度时,直接访问len属性即可,时间复杂度为O(1),确保了获取字符串长度不会成为redis的性能瓶颈常数复杂度获取字符串长度
  • C语言中如果对字符串进行操作,如果是增长字符串,会导致内存的重新分配。
  • 如果是截取字符串,未截取的地方需要被释放掉,如果没有释放,就会导致内存溢出
    | SDS结构中,对字符串数组采用预申请,len属性记录已经使用的长度,alloc属性记录所有的长度(在3.0版本使用free记录未使用空间,3.2版本则改为alloc记录总长度),sds采用这种空间预分配惰性空间释放两种策略,解决了字符串拼接和截取两种场景下的空间问题。
  • 空间预分配:当对一个SDS字符串进行增长操作且内存不够用时,SDS会进行内存的申请,申请时,如果len≤1MB,那么就申请len+len+1的内存,也就是额外申请已使用内存len的两倍+1。如果申请时len>1MB, 那么就申请len+1MB+1的内存,每次扩增1MB。
  • 惰性空间释放:用于优化SDS字符串缩短操作,当SDS的字符串进行缩短时,程序不会释放其内存,而是使用free字段记录下还有多少未使用的空间,等待以后使用
    | 杜绝缓冲区溢出
    减少修改字符串带来的重新分配内存的次数 |
    | 可以使用所有的<string.h>中的函数 | 可以使用部分<string.h>中的函数 | |
    | 只能保存文本数据 | 可以保存文本或二进制数据 | |

4. List底层数据结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.1 ziplist类型(压缩列表)

在说Redis的ziplist之前,先看一下什么是压缩列表。压缩列表是一种紧凑型的数据结构,目的就是为了节省内存空间,他借鉴了数组的存储思想,占用一块连续的内存空间,但又与数组有些许区别。因为数组要求每个元素占用相同的存储空间,且每个存储空间的大小依据最大的那个元素来计算。

上面图中所表示,前4个元素都浪费了存储空间,所以为了保证占用内存空间的连续性和节省更多的内存空间,可以对数组进行压缩,使每个元素的大小为实际存储的大小。但这样的话,不知道每个元素具体的大小了,代码不能判断下一个元素的起点,所以还要为每个元素增加一个字段用于记录每个元素占据的位置大小,用于标识当前元素的大小,这样代码在遍历的时候,就能判断下一个元素的起点,这就构成了最简单的压缩链表
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
redis的ziplist就是基于上面的压缩列表做了自己的封装,接下来我们看redis中ziplist具体的实际结构如何:

ziplist最大的好处就是节省了内存空间,但他的缺点也很多。

  • 不能存储太多的元素,否则遍历效率极低
  • 当新增或修改某个元素时,会重新分配内存,甚至会引起连锁更新的问题。

**连锁更新:**连锁更新就是我只更新了一个元素,但由于某个原因,导致所有元素都进行了内存重分配,导致所有元素都进行了更新,就会降低效率。
那么ziplist如何出现连锁更新问题的呢?这就要看entry节点中prevlen属性占用空间大小了,prevlen属性记录是上一个节点的大小,当上一个节点大小小于256字节时,prevlen属性需要1字节的空间来保存这个长度大小,当上一个节点大小超过256字节时,prevlen属性就需要5字节的空间来保存这个长度大小。如果当上一个节点增大超过256字节时,就会引发下一个节点的prevlen属性内存空间增大,需要为下一个节点重新分配额外的4字节空间。循环往复,有可能每一个下个节点都被导致重新分配内存空间,直至最后一个节点,就会导致连锁更新。

因此:ziplist只适合保存结点数量不多的场景。

4.2 linkedlist类型(双向链表)

redis中的linkedlist就是基于双向链表实现的。以下是redis中linkedlist具体结构图:

可以看到linkedlist中包含了3个属性:

  • head:用于指向链表中的第一个结点
  • tail:用于指向链表中的最后一个结点
  • len:用于记录链表中所有结点的数量

除此之外,还包含了3个函数:

  • dup:结点复制函数,用于复制节点
  • free:结点释放函数,用于释放节点的内存空间
  • match:结点比较函数,用于比较节点之间的值

linkedlist的优缺点:

linkedlist的优点linkedlist的缺点
  • 可以直接获取到头结点和尾结点,时间复杂度O(1)
  • 每个结点中都有next和prev属性,可以方便的获取下一个结点和上一个结点,时间复杂度也是O(1)
  • linkedlist中维护了链表的长度len,可以直接获取链表长度,时间复杂度也是O(1)
    |
  • 双向链表中的每个结点内存空间都是不连续的,无法很好的利用cpu缓存
  • 双向链表遍历的时间复杂度为O(n)
    |

4.3 quicklist类型

由于ziplist和linkedlist都存在着不可避免的缺陷,所以在redis3.2版本之后,引入了一种新的数据结构:QuickList(快速列表)。List对象的底层数据结构,也由ziplist和linkedlist变为QuickList。
QuickList结合了ziplist和linkedlist的优点,它是一个压缩列表ziplist为结点的双向链表,以下是它的数据结构图:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以看到quicklist的数据结构和linkedlist总体上比较相似,都是包含了一个双向链表,并且都维护了双向链表的head结点和tail结点,以及结点的数量,最大的区别就是在双向链表中存储的value值发生了变化,linkedlist中存储的值是实际的值,而quicklist中存储的值是一个指向ziplist的指针。该ziplist的最大大小由quicklist的fill属性进行限制,当某个节点的ziplist大小超出了该限制,就会在链表中新建一个ziplist保存到新的quicklist结点中。

4.4 listpack类型(紧凑列表)

5. Set底层数据结构

5.1 intset

intset适用于保存都是整数的集合,根据编码的不同可以保存不同大小范围的整数,如果三种编码都无法保存这个整数,或者有元素不是整数,就会升级为hashtable的编码方式进行编码。

5.2 hashtable

HashTable(哈希表)是一个保存key-value键值对的结构体,它与Java中的HashMap相似,每个key都是唯一的,可以通过key去查询和修改其value值。
在redis中,hashtable底层实现是基于数组的,数组的每个元素相当于一个bucket桶,当通过哈希散列函数计算出key对应的hash值(也就是数组下标)时,就会将该元素放入在数组中对应的下标位置,当发生hash冲突时,就会采用链地址法,在对应的bucket桶下形成一个链表来保存这些数据。

常见的解决hash冲突的方式还有以下几种:

  1. 链地址法:形成一个Entry链表来保存数据
  2. 二次hash:用另一种函数重新计算hash值,尝试映射到其他的bucket中
  3. 公共溢出区:将产生Hash冲突的元素统一存放到一个公共的区域
  4. 开放定址法:冲突的Entry按照一定的规则(线性探测、平方探测)在hash表中找到下一个空闲的bucket存放

下面是hashtable的结构图:

从结构中可以看到,映射到相同数组下标位置的元素,会形成链表,当redis再次查询时,会先计算数组下标位置,然后再沿着链表进行遍历查找,时间复杂度为O(1)+O(n)=O(n).
哈希表使用了链式哈希法虽然很好的解决了哈希冲突的问题,但随着结点越来越多,链表上的结点元素也越来越多,就会照成查询时的成本变高,会大大降低查询效率。
redis为了解决hash冲突,在数据量达到一定阈值的时候,就会对hash表进行rehash(重新计算hash)操作,会将数组进行扩容,对每个元素重新计算hash,然后重新映射到新位置。
触发rehash的条件有两个:

  1. 负载因子≥1,并且服务器没有执行bgsave命令或者bgrewriteaof命令,就会执行rehash操作,但并一定100%执行
  2. 负载因子≥5,说明此时的hash表冲突已经很严重了,将强制执行rehash操作

负载因子 = hash表中entry结点的总数量 / 数组大小

redis为了解决hash冲突,在进行rehash的时候,也设计了一种dict结构,dict中包含了两个dictht哈希表ht[0]和ht[1],默认情况下,这两个哈希表只使用一个。以下是dict的结构:

当我们第一次新增数据的时候,元素结点会存放在ht[0]中,当ht[0]中的结点负载因子超过阈值时,就会重新计算结点的hash值,将其放入到ht[1]中。
rehash步骤:

  1. 给哈希表ht[1]分配空间,分配空间的大小为 2n中第一个>ht[0].used * 2的n值。比如上图中ht[0].used * 2=22, 而25>22>24,所以n=5,即分配的空间大小为32.
  2. 将ht[0]中的entry结点经过rehash重新运算后,迁移到ht[1]中
  3. 迁移完成后,释放ht[0]的内存空间,并把ht[1]设置为ht[0]
  4. 创建一个新的ht[1],为下次扩容做准备

通过扩容操作,并进行rehash操作,可以有效地解决hash冲突的问题,但当hash表中的元素很大时,就会导致rehash动作耗时很长,而redis是单线程的,一旦耗时过长影响到后续的操作,那就是得不偿失的。因此在redis中没有采用直接全部rehash,而是采用渐进式rehash操作来进行。
**渐进式rehash:**不一次性把所有的数据rehash到新的哈希表中,而是在每次对哈希表的元素进行增删查改的时候,顺带把某个数组下标的链表上所有的结点进行rehash操作,直至把所有的元素rehash。在新增数据时,对于新增的数据将其放入ht[1]中,对于查询数据时,先去查询ht[0],再去查询ht[1]。在渐进式rehash操作时,两个dict中都会存储数据,但ht[0]中的数据会越来越少,直至全部rehash进入到ht[1]中。
以下是rehash的结构图:

6. ZSet底层数据结构

6.1 ziplist类型

ziplist的具体结构在4.1节已经介绍过了。但ZSet中具体的做法是将元素和其对应的分值分别保存到两个相邻的entry的content中。
当ziplist作为zset的底层数据结构时,每个集合元素使用两个紧挨在一起的压缩列表结点来保存。第一个结点保存元素成员,第二个结点保存结点的分值。
image.png

redis选择ziplist作为zset的底层数据结构也是有限制的:

  1. 有序集合中保存的元素数量<128个
  2. 有序集合保存的所有元素的长度小于64字节

否则就会使用skiplist作为zset的底层实现。

6.2 skiplist类型

ziplist俗称跳表,跳表是一个基于有序链表实现的可以快速查找元素的数据结构。下面是一个普通的有序链表的结构图:

该链表中如果想找到靠后的结点,就需要挨个遍历结点,时间复杂度为O(n)。那么有没有办法提高查询效率呢,当然是有的。既然我们的链表是有序的,我们可不可以跳过前面的结点呢?这时候我们的初级链表就出来了(一个含有两层链表的跳表, 相当于带了一层索引):

这样我们查询12的话:

  1. 只有一层链表:1->3->6->8->9->12,需要遍历6次,时间复杂度O(n)
  2. 有两层链表:1->6->9->12,需要遍历4次,时间复杂度O( n 2 \frac{n}{2} 2n)
  3. 同理,对于一个n层链表来说,时间复杂度为O( l o g n logn logn)

在上面的结构中,跳跃表遵守上一层结点是下一层结点的一半,但是在发生大量插入元素的时候,就会发生频繁调整跳跃表这个操作。
所以在redis中设计了一种升层算法,对于每一个新增的结点,初始化其层数为1,然后循环以下算法步骤:算法算出一个0~1之间随机数,如果随机数<0.25,则层数增加一层,然后循环这个步骤,直到生成的随机数> 1 4 \frac{1}{4} 41,如果循环到第三次退出循环时,那么这个新增节点的层数就是2。这个算法对于每个结点来说,新增一层的概率是0.25,层数越高,概率越小。对于新增的结点,只需要修改其前向指正和后向指针即可,其它结点不受影响。
综上所述,通过将有序集合的部分节点层,从最上层结点开始依次查找,如果本层的next结点大于我们想要找的结点,那么就自降一层再依次查找,如果找到了就返回其值,找不到就返回null。如果跳跃表的层数很多的话,那么就会跳过很多节点,从而提升查询的效率,这就是跳跃表的思想。
跳跃表有以下性质:

  1. 跳跃表有很多层结构组成,最底层的结点个数为跳跃表的长度
  2. 跳跃表有一个头结点,头结点中默认有一个32层的结构(redis默认层数最高32层),每层的结构都包含一个指向本层下一个元素的next指针
  3. 除了头结点外,有最高层的结点的层数称为跳跃表的高度
  4. 每层都是一个有序链表,随着数据score依次递增
  5. 除了头结点外,最底层的链表包含所有的元素

在跳跃表中,对于一个新增的元素来说,其新增结构如下:

7. Hash底层数据结构

7.1 ziplist类型

当元素较少时,使用ziplist保存hash结构的元素,当元素个数超过一定的限制后,使用hashtable来保存。

7.2 hashtable类型

hashtable在上述5.2章节中已经介绍。

7.3 listpack类型

本文参考文献:

  • https://blog.csdn.net/YTREE_BJ/article/details/119415521
  • https://zhuanlan.zhihu.com/p/619828101
  • https://www.jianshu.com/p/674c9635b2b9
  • https://baijiahao.baidu.com/s?id=1731524214636496899&wfr=spider&for=pc
  • https://www.jianshu.com/p/674c9635b2b9
  • https://blog.51cto.com/u_39029/6501913
  • https://blog.csdn.net/u013277209/article/details/125998869
  • https://blog.csdn.net/qq_32099833/article/details/133889188

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaScript】JavaScript 程序流程控制 ① ( 顺序流程控制 | 分支流程控制 )

文章目录 一、JavaScript 程序流程控制简介1、顺序流程控制2、分支流程控制3、分支流程控制 - 代码示例 一、JavaScript 程序流程控制简介 JavaScript 程序 执行过程中 , 不同的代码执行顺序 , 得到的结果是不同的 , 在编程中 经常 需要 根据 不同的条件 执行不同的代码块 , 或…

Redis数据结构对象中的对象共享、对象的空转时长

对象共享 概述 除了用于实现引用计数内存回收机制之外&#xff0c;对象的引用计数属性还带有对象共享的作用。 在Redis中&#xff0c;让多个键共享同一个值对象需要执行以下两个步骤: 1.将数据库键的值指针指向一个现有的值对象2.将被共享的值对象的引用计数增一 目前来说…

实验03-OSPF高级实验

1.实验拓扑 2.实验需求 3.配置思路 根据所给的IP地址配置完成后进行OSPF的配置&#xff1a; #R1 [r1]ospf 1 router-id 10.0.1.1 [r1-ospf-1]a 0 [r1-ospf-1-area-0.0.0.0]network 10.0.1.1 0.0.0.0 [r1-ospf-1-area-0.0.0.0]network 10.0.12.1 0.0.0.0 [r1-ospf-1-area-0.0.…

图书馆管理系统 1.架构项目以及加搭建项目

项目架构图 技术栈 后端 开发语言&#xff1a;java 开发环境&#xff1a;jdk11.0.12 开发工具&#xff1a;IntelliJ IDEA 2022.2.4 项目管理工具&#xff1a;maven 集成框架&#xff1a;springboot 权限控制框架&#xff1a;springSecurity 数据库&#xff1a;mysql 数据库框架…

QT-绘制动态曲线

QT-绘制动态曲线 pro文件中添加chart 在串口工程中添加控件 将控件功能提升为QChartView 点击添加 添加相关的头文件和变量

Selenium不同版本配置自动下载驱动及打包细节

Selenium配置浏览器驱动 自动下载浏览器驱动的方法 selenium4.7.0自动下载浏览器驱动的方法 selenium4.11.0 或4.11.1手动设置浏览器驱动路径的方法pyinstaller打包程序时同时打包ChromeDriverchromedriver路径需要sys._MEIPASS的路径进行引用方法一&#xff1a;通过–add-data…

【目标检测】图解 YOLOv3 的网络结构(Darknet-53 作为 backbone)

到了 YOLOv3&#xff0c;backbone 从 YOLOv2 的 Darknet-19 升级到了 Darknet-53。 下面一张完整的结构示意图来一起理解一下 YOLOv3 的网络结构。 我们怎么理解最后输出的 3 个特征图&#xff08;feature map&#xff09;的这个 255&#xff1f; 同 YOLOv2 一样&#xff0c;…

【蓝桥杯-单片机】基于定时器的倒计时程序设计

基于定时器的倒计时程序 题目如下所示&#xff1a; 实现过程中遇到的一些问题 01 如何改变Seg_Buf数组的值数码管总是一致地显示0 1 2 3 4 5 首先这个问题不是在main.c中关于数码管显示部分的逻辑错误&#xff0c;就是发生在数码管的底层错误。 检查了逻辑部分&#xff…

玩转C语言——深入理解指针

一、指针概念 1.1 内存和地址 在开始学习指针前&#xff0c;我们先来讲一个例子&#xff0c;假如你身处一栋楼中&#xff0c;你点了一份外卖&#xff0c;那么&#xff0c;外卖员如何能找到你&#xff1f;有两种方法。法一&#xff1a;直接一间一间找&#xff0c;这样做不仅消耗…

线程和进程的区别和联系

一、什么是进程 进程(Process), 是一个具有独立功能的程序关于某个数据集合的一次运行活动&#xff0c;是系统进行 【资源分配和调度】 的一个独立单位。 进程是【程序】的【一次执行】(是计算机中程序的执行过程&#xff0c;而不是计算机中的程序)进程是系统进行【资源分配和…

十二 超级数据查看器 讲解稿 详情7 其他功能

十二 超级数据查看器 讲解稿 详情7 其他功能 点击此处 以新页面 打开B站 播放当前教学视频 点击访问app下载页面 百度手机助手 下载地址 ​ 讲解稿全文&#xff1a; 其他操作&#xff0c;主要用来完成替换和批量修改&#xff0c; 这里&#xff0c;我们想给成语字段增…

YOLOv9改进策略:卷积魔改 | 分布移位卷积(DSConv),提高卷积层的内存效率和速度

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; YOLOv9如何魔改卷积进一步提升检测精度&#xff1f;提出了一种卷积的变体&#xff0c;称为DSConv&#xff08;分布偏移卷积&#xff09;&#xff0c;其可以容易地替换进标准神经网络体系结构并且实现较低的存…

二、Kubernetes(k8s)中部署项目wordpress(php博客项目,数据库mysql)

前期准备 1、关机顺序 2、开机顺序 (1)、k8s-ha1、k8s-ha2 (2)、master01、master02、master03 (3)、node01、node02 一、集群服务对外提供访问&#xff0c;需要通过Ingress代理发布域名 mast01上传 ingress-nginx.yaml node01、node02 上传 ingress-nginx.tar 、kube-webh…

【知识库系统】JWT实现前后端分离验证

本文会先从理论和实践两部分讲述如何去理解和实现通过JWT进行身份认证。 一、理论 1. SpringSecurity 默认的认证是需要通过 UsernamePasswordAuthenticationFilter 进行认证的&#xff0c;该过滤器认证前&#xff0c;会到 SecurityContextHolder 中寻找是否有符合的 Authent…

Issue 2046:Missing array size check in NewFixedArray

文章目录 环境搭建漏洞分析漏洞触发 漏洞利用总结参考 环境搭建 sudo apt install pythongit reset --hard 64cadfcf4a56c0b3b9d3b5cc00905483850d6559 export DEPOT_TOOLS_UPDATE0 gclient sync -D// debug version tools/dev/v8gen.py x64.debug ninja -C out.gn/x64.debug/…

java数据结构与算法刷题-----LeetCode135. 分发糖果

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 左右遍历2. 进阶&#xff1a;常数空间遍历&#xff0c;升序降…

Visual Studio - 添加快捷键图标

Visual Studio - 添加快捷键图标 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize2. Toolbars3. Commands -> Debug4. Add Command...References 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize 2. Toolbars B…

并发编程之synchronized的详细解析

4.2 synchronized 解决方案 应用之互斥 为了避免临界区的竞态条件发生&#xff0c;有多种手段可以达到目的。 阻塞式的解决方案&#xff1a;synchronized&#xff0c;Lock 非阻塞式的解决方案&#xff1a;原子变量 本次课使用阻塞式的解决方案&#xff1a;synchronized&am…

长安链智能合约标准协议第二草案——BNS与DID协议邀请社区用户评审

长安链智能合约标准协议 在智能合约编写过程中&#xff0c;不同的产品及开发人员对业务理解和编程习惯不同&#xff0c;即使同一业务所编写的合约在具体实现上也可能有很大差异&#xff0c;在运维或业务对接中面临较大的学习和理解成本&#xff0c;现有公链合约协议规范又不能完…

C++:类和对象(上篇)

目录&#xff1a; 一&#xff1a;面向对象和过程的介绍 二&#xff1a;类的引入 三&#xff1a;类的定义 四&#xff1a;类的访问限定符以及封装 五&#xff1a;类的作用域 六&#xff1a;类的实例化 七&#xff1a;类对象大小的计算 八&#xff1a;类成员函数的this指…