Redis的设计与实现(1):5种基本数据结构的底层实现

article/2025/10/14 5:05:28

一、简单的动态字符串(SDS)

Redis没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将SDS作为Redis默认的字符串表示。

在Redis里,C字符串只会作为字符串字面量用在一些无须对字符串值进行修改的地方,比如打印日志。

SDS的定义:

/** 保存字符串对象的结构*/
struct sdshdr {// buf 中已占用空间的长度int len;// buf 中剩余可用空间的长度int free;// 数据空间char buf[];
};

示例:
image.png
SDS遵循了C字符串以空字符串结尾的惯例,以便可以复用<string.h>函数库的部分函数保存空字符的一个字节空间不计算在SDS的len属性里。为空字符分配额外的空间以及添加到字符串末尾等操作,都是有SDS函数自动完成的。

SDS与C字符串的区别:

  • 常数复杂度获取字符串长度

    C 字符串并不记录自身的长度信息,必须遍历整个字符串, 对遇到的每个字符进行计数, 直到遇到代表字符串结尾的空字符为止, 这个操作的复杂度为O(n)。 SDS 在 len 属性中记录了 SDS 本身的长度, 所以获取一个 SDS 长度的复杂度仅为 O(1)。

  • 杜绝缓冲区溢出

    因为 C 字符串不记录自身的长度, 所以 strcat 假定用户在执行这个函数时, 已经为 dest 分配了足够多的内存, 可以容纳 src 字符串中的所有内容, 而一旦这个假定不成立时, 就会产生缓冲区溢出。 SDS API 需要对 SDS 进行修改时, API 会先检查 SDS 的空间是否满足修改所需的要求, 如果不满足的话, API 会自动将 SDS 的空间扩展至执行修改所需的大小, 然后才执行实际的修改操作。

  • 减少修改字符串时带来的内存重分配次数

    因为 C 字符串的长度和底层数组的长度之间存在着这种关联性, 所以每次增长或者缩短一个 C 字符串, 程序都总要对保存这个 C 字符串的数组进行一次内存重分配操作。SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联: 在 SDS 中, buf 数组的长度不一定就是字符数量加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由 SDS 的 free 属性记录。

  • 空间预分配

    当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为SDS 分配额外的未使用空间。在扩展 SDS 空间之前, SDS API 会先检查未使用空间是否足够, 如果足够的话,API 就会直接使用未使用空间,而无须执行内存重分配。通过这种预分配策略,SDS 将连续增长 N 次字符串所需的内存重分配次数从必定 N 次降低为最多 N 次。

  • 惰性空间释放

    惰性空间释放用于优化 SDS 的字符串缩短操作: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。

  • 二进制安全

    C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据,所以 Redis 不是用这个数组来保存字符, 而是用它来保存一系列二进制数据。

总结:

C 字符串SDS
获取字符串长度的复杂度为 O(n) 。获取字符串长度的复杂度为 O(1) 。
API 是不安全的,可能会造成缓冲区溢出。API 是安全的,不会造成缓冲区溢出。
修改字符串长度 N次必然需要执行 N次内存重分配。修改字符串长度 N次最多需要执行 N 次内存重分配。
只能保存文本数据。可以保存文本或者二进制数据。
可以使用所有 <string.h>库中的函数。可以使用一部分 <string.h>库中的函数。

二、其他数据结构

2.1链表

当一个列表键包含了数量比较多的元素,或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。

2.1.1链表和链表节点的实现

typedef struct listNode{//前置节点struct listNode *prev;//后置节点struct listNode *next;//节点的值void *value;
}listNode;

节点由前驱后继组成,多个节点组成的链表为双端链表。
使用adlist.h/list来持有,操作链表:

typedef struct list{//表头节点listNode *head;//表尾节点listNode *tail;//链表所包含的节点数量unsigned long len;//节点值复制函数void *(*dup)(void *ptr);//节点值释放函数void (*free)(void *ptr);//节点值对比函数int (*match)(void *ptr,void *key);
}list;

整个链表串起来后,如下图:
image.png
Redis的链表特性可以总结如下:
双端:链表节点带有prev和next指针,获取前置和后置节点的复杂度都是O(1)。
无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。 带表头指针和表尾指针,带链表长度计数器 。
头尾指针:将程序获取头尾节点的复杂度降为O(1)。
长度计数器:将程序获取表长的复杂度降为O(1)。
多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

2.2字典

字典又称符号表关联数组映射,用于保存键值对的抽象数据结构。当一个哈希键包含的键值对比较多时,或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。

2.2.1 字典的实现

Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存了字典中的一个键值对。使用dict.h/dictht结构定义:

typedef struct dictht{//哈希表数组dictEntry **table;//哈希表大小unsigned long size;//哈希表大小掩码,用于计算索引值//总是等于size-1unsigned long sizemask;//该哈希表已有节点的数量unsigned long used;
}dictht;

数组中的每个元素都是指向dict.h/dictEntry的结构,dictEntry就是一个键值对

typedef struct dictEntry{//键void *key;//值union{void *val;uint64_t u64;int64_t s64;} v;//指向下个哈希表节点,形成链表struct dictEntry *next;
} dictEntry;

键值对的值可以是一个指针,或一个uint64_t整数,或一个int64_t整数。next是指向另一个哈希节点的指针,可将多个哈希值相同的键值对连接在一起,以此来解决冲突。如图,k0和k1的索引值相同。
image.png

Redis中的字典由dict.h/dict实现

typedef struct dict{//类型特定函数dictType *type;//私有数据void *privdata;//哈希表dictht ht[2];//rehash索引//当rehash不在进行时,值为-1int rehashidx;
} dict;
  • type属性是一个指向dictType的结构指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis为用途不同的字典设置不同类型特定函数。
typedef struct dictType{//计算哈希值的函数unsigned int (*hashFunction)(const void *key);//复制键的函数void *(*keyDup)(void *privdata,const void *key)...
}
  • privdata属性保存了需要传给那些类型特定函数的可选参数。
  • ht属性是包含两个项的数组,每项都是一个哈希表,ht[0]平时使用,而ht[1]仅在rehash时使用。
  • rehashidx记录了rehash的进度,初始为-1。

image.png

2.2.2哈希算法

Redis计算哈希值方法:hash=dict->type->hashFunction(key);
计算索引值的方法:index=hash & dict->ht[x].sizemask;
当字典被用作数据库的底层实现或哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。优点在于即使输入的键是有规律的,算法仍然能给出很好的随机分布性,并且计算速度很快

2.2.3 解决键冲突

当有两个或以上的键被分配到哈希表的同个索引,那么就发生了冲突。Redis使用链地址法来解决冲突,被分配到索引的多个节点使用链表连接。为了提高速度,每次都是将新节点添加到链表的表头位置。

2.2.4 rehash

为了让哈希表的负载因子维持在一个合理的范围内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行响应的扩容或缩容。扩容和缩容通过执行rehash来完成,Redis中重新散列的步骤如下:

  1. 为字典ht[1]哈希表分配空间,大小取决于要执行的操作与ht[0]当前键值对的数量
  2. 将保存在ht[0]中的所有键值对存放到ht[1]指定的位置
  3. 当ht[0]的所有键值对都迁移完毕后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]上创建一个空的哈希表,为下次rehash准备。

2.2.5扩容与缩容场景

扩容操作场景:

  • 服务器目前没有在执行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的负载因子>=1
  • 服务器正在执行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的负载因子>=5

负载因子=哈希表已存储节点数/哈希表大小
load_factor=ht[0].used/ht[0].size

为什么根据BGSAVE命令或BGREWRITEAOF命令来判断是否扩展?
因为执行这些命令时,Redis需要创建当前服务器进程的子进程,大多数操作系统采用写时复制技术来优化子进程使用效率,此时提高负载因子,可以尽量避免子进程对哈希表扩展,避免不必要的内存写入操作,节约内存。
缩容操作场景:负载因子<0.1时,自动对哈希表执行收缩操作。

2.2.6 渐进式rehash的过程

rehash时会将ht[0]中所有的键值对rehash到ht[1],如果键值对很多并且一次性操作的话,容易导致服务器在一段时间内停止服务。为避免这种情况,Redis采用渐进式rehash,将ht[0]中的键值对分多次,慢慢的rehash到ht[1]之中。
步骤:

  1. 为ht[1]分配空间,让字典同时持有两个哈希表。
  2. 在字典中维持一个索引计数器变量rehashidx,将其设置为0,表示rehash正式开始。
  3. 在rehash进行期间,每次对字典进行添加,删除,查找或更新操作时,程序除了执行指定的操作外,还会将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成后,将rehashidx++。
  4. 某个时刻,ht[0]中的所有键值对都被rehash至ht[1],此时设置rehashidx=-1时,表示rehash操作已经完成。

这种方式的rehash的好处在于采用了分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个操作中,从而避免集中式rehash带来庞大计算量。
在rehash的期间,字典同时使用ht[0],ht[1]两个哈希表。对哈希表的操作会在两个表上进行,比如查找键时,先在ht[0]里面查找,如果为空,就继续到ht[1]里查找。在此期间,新增的键值对都会被添加到ht[1]中,ht[0]不承担任何添加操作,保证ht[0]中的键值对只能是越来越少

2.3跳跃表

跳跃表是一种有序的数据结构,通过在每个节点维持多个指向其他节点的指针,达到快速访问节点的目的。
如果一个有序集合中包含的元素数量比较多,又或者有序集合中元素的成员是较长的字符串,Redis就会使用跳跃表来作为有序集合键的底层实现。Redis只有在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中作为内部数据结构。

2.3.1.1 跳跃表的实现
Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个数据结构定义

typedef struct zskiplist{//表头节点和表尾节点structz zskiplistNode *header,* tail;//表中节点的数量unsigned long length;//表中层数最大的节点的层数int level;
} zskiplist;

跳跃表由zskiplist组织,通过多个跳跃表节点zskiplistNode组成一个跳跃表。值得注意的是,记录level时,表头节点的层高不会记录在内。
image.png
每个节点的结构是zskiplistNode

typedef struct zskiplistNode{//后退指针struct zskiplistNode *backward;//分值double score;//成员对象robj *obj;//层struct zskiplistlevel{//前进指针struct zskiplistNode *forward;//跨度unsigned int span;}level[];
} zskiplistNode;
  • level

    跳跃表的每个节点都会包含多个层,每次创建一个新跳跃表时,都会根据幂次定律,随机生成一个1~32之间的数作为层的大小。每个层都会包含前进指针和跨度。
    前进指针(forword)用于访问下一个节点。
    跨度表示两个节点之间的距离,指向NULL的所有前进指针的跨度为0。跨度用于计算排位,访问某一结点的经过的跨度之和就是当前节点的排位。
    注:幂次定律也是28法则。最重要的只占一小部分,越大的数出现的概率越小。Redis中对level的随机获取实现是:

int zslRandomLevel(void) {int level = 1;while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))level += 1;return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
  • 后退指针–backward

    用于从表尾向表头方向访问节点,前进指针可以一次跳过多个节点,后退指针只能后退至前一个节点,因为每个节点只有一个后退指针。

  • 分值–score

    分值是一个double类型的浮点数,跳跃表中节点都按照分值排序。

  • 成员对象–obj

    是一个指针,指向字符串SDS对象。一个跳跃表中,对象必须是唯一的,但分值可以相同。相同时按对象字典序(对象大小)来排序。

通过幂次定律能保证越高level的结点数量越少 。保证索引等级越高,参与索引建立的元素越少,如果每层都有很多level,那么这个索引建立的就没有意义了。那么,为什么不用最均衡的方式,按照节点分数的排序情况均匀建立索引?考虑到下一个插入的元素具有随机性,这样设计不容易出现最坏的情况。如果每次都以均匀固定的方式建索引,维护的成本很高,跳跃表的优点就是维持结构平衡的成本低,完全依靠随机。跳跃表相比二叉树有一个优势就在于不需要主动rebalance去维护平衡。

2.4整数集合

当一个集合只包含整数元素,并且元素不多时,Redis就会使用整数集合作为集合键的底层实现。

2.4.1 整数集合的实现

整数集合是Redis中用于保存整数值的集合抽象数据结构,可以保证集合有序不重复。每个intset.h/intset结构来表示一个整数集合:

typedef struct intset{//编码方式uint32_t encoding;//集合包含的元素数量uint32_t length;//保存元素的数组int8_t contents[];
} intset;

length属性记录了整数集合包含的元素数量,contents是整数集合的底层实现。contents存储元素的真实类型取决于encoding,比如encoding==INT_ENC_INT16时,contents数组中每个向都是int16_t类型的整数。可以为int16_t,int32_t或int64_t。
image.png

2.4.2升级

当我们要将一个新元素添加至集合时,并且新元素的类型比现有集合类型都长时,整数集合就要升级。
步骤:

  1. 根据新元素类型,扩展数组空间,为新元素分配空间。
  2. 将底层数组现有所有元素都转为新元素相同类型,并将类型转换后的元素放到正确位置。
  3. 将新元素添加到底层数组。

由于每次向整数集合添加新元素都可能会引起升级,而每次升级都需要对底层数组中已有元素进行类型转换,所以添加的时间复杂度为O(N)

升级的好处
有两个好处,可以提升整数集合的灵活性,也能尽可能地节约内存
C语言是静态类型语言,一般数组中的元素类型都相同,使用升级可以不用担心类型兼容问题,提升灵活性。元素统一以最大类型存储,而不是都用int64_t,可节约内存。

2.4.3降级

整数集合不支持降级,一旦升级就不能降级。

2.5压缩列表

压缩列表是列表键哈希键底层实现之一。当一个列表键只包含少量列表项,且每个列表项要么是小整数,要么是长度比较短的字符串,Redis就使用压缩列表来做列表键的底层实现。

2.5.1 压缩列表的构成

为节约内存而开发的,由一系列特殊编码连续内存块组成的顺序型数据结构。

image.png
各部分详细解释:
image.png

2.5.2压缩列表节点的组成

每个压缩列表节点可以是一个字节数组,也可以是一个整数。由previous_entry_length,encoding,content组成。
image.png
previous_entry_length
单位是字节,记录压缩列表前一个节点的长度。该属性长度为1字节或5字节,前两位表示该属性长度为2位还是10位。

  • 前一个节点的长度<254字节时,该属性只有2位,且前一节点的长度就保存在这两位。如0x05,表示前一个字节长度为5字节。
  • 前一个节点的长度>=254字节时,该属性有10位,且前两位表示这是一个5字节的长度,后8位表示前一个节点的长度。如0xFE0000,表示前一个字节长度为0x00002766,换算为10进制10086。

encoding
encoding记录了节点的content属性所保存数据类型长度高两位表示存储的是字节数组还是整数。

content
存储节点的值。

2.5.3连锁更新

多个连续的长度介于250字节到253字节之间的节点,插入新的头节点(长度大于等于254字节),后面节点的previous_entry_length就要新增4字节的空间(1字节变成5字节),需要进行内存重分配,由于前一个节点的变更,每个节点的previous_entry_length属性也需要记录之前的长度而发生相应的变更,所以会出现连锁更新。除了新增节点,删除节点也可能会遇到这种情况。
因为连锁更新在最坏情况下需要对压缩列表执行N次空间重分配操作,每次重分配的的最坏时间复杂度为 O(N) ,所以连锁更新的最坏时间复杂度为 O(N2)
虽然代价很高,但是出现的几率比较低,而且只要更新节点的数量不多,就不会对性能产生影响。因此ziplistPush命令的平均复杂度为 O(N)

2.6对象

Redis没有直接使用前文的数据结构来实现键值对数据库,而是基于这些数据结构构建了一个对象系统,通过对象组织数据结构,包括字符串对象,列表对象,哈希对象,集合对象有序集合对象这5种对象。
使用对象的一个好处是可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

2.6.1 对象的结构

Redis使用对象来表示数据库的键和值。每个对象都是一个redisObject结构。

typedef struct redisObject{//类型unsigned type :4;//编码unsigned encoding:4;//指向底层实现数据结构的指针void *ptr;...
} robj;

type类型
image.png
encoding

image.png
每种类型的对象最少可以用2种编码
image.png
redis会根据value的类型、大小来选择合适的编码,比如SET类型,如果它的值只有整数,那么编码为整数集,如果值包含非数字,那么编码会升级为字典表。
在这里插入图片描述
而像String类型,在3.2版本之后,如果字符串的长度超过44就会选择ssd,如果少于44且不是纯数字就会选择embstr,这种编码在内存上ResObject与Strings的地址是连续的。具体可参考:Redis里String的编码
![在这里插入图片描述](https://img-blog.csdnimg.cn/1fb95da2d393492a8777ac0a9d4b4e25.png

2.7一些补充

类型检查与命令多态
Redis中用于操作键的命令可分为两种类型。一种是可对任何类型执行的,如del,expire,rename等。另一种命令只能对特定类型的键执行,如set,get,hdel,hset,rpush等。如果对特定类型使用其他类型的命令,那么就会报错。

类型检查的实现
为了确保只有制定类型的键可以执行某些特定命令,在执行前,Redis会先通过RedisObject的type属性检查输入键的类型是否正确。

多态命令的实现
Redis除了根据值对象判断键是否能够执行制定命令外,还会根据值对象的编码方式,选择正确的命令实现代码来执行。比如基于编码的多态,列表对象的编码可能是ziplist或linkedlist,所以需要多态命令执行对应编码的API。基于类型的多态是一个命令可以同时处理多种不同类型的键

内存回收
由于C语言没有内存回收机制,Redis在对象系统中构建了引用计数器技术实现内存回收机制。每个对象的引用计数器信息由redisObject的refcount来记录。当对象的引用计数值为0时,所占用的内存会被释放

对象共享
引用计数器还有共享对象的作用。如果两个不同键的值都一样(必须是整数值的字符串对象),则将数据库键的值指针指向一个现有的值对象,然后将被共享对象的引用计数加一。如果不是整数值的对象,则需要耗费大量的时间验证共享对象和目标对象是否相同,复杂度较高,消耗CPU时间,所以Redis不会共享包含字符串的对象
Redis在初始化服务时,会创建很多字符串对象,包含0~9999的整数(和Integer的常量池有点像),当需要时,就能直接复用。
image.png
对象的空转时长
redisObject还包含了lru属性,记录对象最后一个被命令程序访问的时间。object idletime命令可打印键的空转时长,就是当前时间减去lru时间计算得到的。

注:内容是从语雀上的学习笔记迁移过来的,主要参考自《Redis的设计与实现》有些参考来源已经无法追溯,侵权私删。


http://chatgpt.dhexx.cn/article/86ef2euW.shtml

相关文章

Redis设计与实现总结

本文总结自《Redis设计与实现》一书&#xff0c;只打算总结Redis底层数据结构的实现。Redis的使用参考我的另一篇笔记Redis操作指南。 1 Redis概览 Redis是一个C语言编写的开源、非关系型内存数据库。它底层属于单线程、全内存操作&#xff0c;提供对象共享、引用计数和对象回…

Redis设计与实现

文章目录 第一部分&#xff1a;内部数据结构简单动态字符串(simple dynamic string)双端链表字典跳跃表 第二部分&#xff1a;内存映射数据结构整数集合intset压缩列表 redis数据类型对象处理机制(redisObject)字符串string哈希表hash列表list集合set有续集zset 第四部分&#…

redis的设计与实现

redis的设计和实现 第一部分、数据结构与对象 一、简单动态字符串&#xff1a; 在大多数情况下redis只会使用c字符串作为字面量&#xff0c;在大多情况下&#xff0c;redis使用SDS作为字符串表示。 比起C字符串&#xff0c;SDS具有五种优点&#xff1a; SDS结构里面会有一…

虚拟IP注册Nacos的问题

虚拟IP注册Nacos的问题 问题&#xff1a; A服务器有两个网卡&#xff0c;网卡 lo 绑定了 127.0.0.1 和一个虚拟IP&#xff0c;网卡 eth0 绑定了本地公网IP和一个虚拟IP。同样B服务器的网卡也是相同的配置&#xff0c;A、B服务器拥有的虚拟IP都是同一个地址。 当将A、B服务器部…

天翼云高可用虚拟IP(HAVIP)实践

产品概述 天翼云高可用虚拟IP&#xff08;High-Availability Virtual IP Address&#xff0c;简称HAVIP&#xff09;是一种可用独立创建和删除的私有网络IP地址资源。通过在VIP CIDR中申请一个私有网络IP地址&#xff0c;然后与高可用软件&#xff08;如高可用软件Keepalived&…

云服务器虚拟ip绑定主机,如何在云平台上给云主机中的Keepalived的虚拟IP绑定弹性IP?...

1、 查看Keepalived和网卡配置文件中虚拟IP地址 查看虚拟机keepalived.config配置文件可以看到本地IP地址为172.16.100.109&#xff0c;虚拟IP地址为172.16.100.104。 (图1 Keepalived配置文件) 查看虚拟机网卡的IP地址情况&#xff0c;可以看到本地IP和虚拟IP。 (图2 查看虚拟…

EasyConnect虚拟IP地址未分配

工作中遇到EasyConnect虚拟IP地址未分配&#xff0c;导致无法正常连接服务器进行调测工作。 检查是否安装成功

蒲公英联机平台的服务器虚拟IP,蒲公英客户端如何使用固定虚拟IP管理虚拟局域网的步骤是什么?...

蒲公英异地组网分为路由器成员与客户端成员两种。其中路由器成员下的电脑&#xff0c;可通过本地连接获取的局域网IP进行组网通信访问&#xff1b;而安装并登录了蒲公英客户端成员&#xff0c;则是通过系统随机分配的临时虚拟IP&#xff0c;来进行组网成员的通讯。当成员移除原…

服务器怎么做虚拟ip,如何在服务器上添加虚拟IP?看完原来如此简单!!

写在前面最近&#xff0c;有位小伙伴为了实现Nginx的高可用&#xff0c;在自己的服务器上搭建了一套Nginx集群&#xff0c;Nginx节点的服务器总共有3台。那么问题来了&#xff1a;如何对外只使用一个IP地址&#xff0c;通过某种策略来访问三个服务器节点上的Nginx&#xff1f;答…

LNMP详解(九)——Nginx虚拟IP实战

今天继续给大家介绍Linux运维的相关知识&#xff0c;本文主要内容是Nginx的虚拟IP实战。 一、实战背景 在LNMP详解&#xff08;七&#xff09;——Nginx反向代理配置实战一文中&#xff0c;我们实现了如下所示的架构&#xff1a; 在该架构中&#xff0c;Nginx作为反向代理&a…

centos7 配置虚拟ip

环境概览 master&#xff1a;192.168.46.26 slave1&#xff1a;192.168.46.27 测试机&#xff1a;192.168.46.22&#xff08;用于ping机器&#xff09; 安装keepalived yum install -y keepalived修改master keepalived.conf 配置文件 vim /etc/keepalived/keepalived.confi…

计算机 修改 虚拟ip,怎么样在电脑中设置虚拟IP地址?

满意答案 wtc6981 2020.03.01 采纳率&#xff1a;56% 等级&#xff1a;9 已帮助&#xff1a;114人 更改IP地址 广域IP: 1、如果是PPOE上网只需断开连接再重新连上就好了,服务器会从IP地址池中随机分配一个IP地址给你。 2、固定IP上网那你要找运营商更改了,这样改是快不了的。…

虚拟服务器的真实ip,虚拟ip和真实ip区别(图文)

【导读】虚拟ip和真实ip区别&#xff0c;下面就是191路由网整理的网络知识百科&#xff0c;来看看吧&#xff01; 大家好&#xff0c;我是191路由器网小编&#xff0c;上述问题将由我为大家讲解。 虚拟ip和真实ip区别是真实IP是网络运营商提供的所以不能自己变更&#xff0c;虚…

keepalived配置虚拟IP

YUM安装 # yum安装 yum -y install keepalived # 查看安装版本 rpm -qa keepalived # 查看安装路径 rpm -ql keepalived或是使用源码安装 到这里下载 https://www.keepalived.org/download.html # 安装依赖 yum -y install gcc openssl-devel libnfnetlink-devel 下载源码包…

虚拟ip的概念

1.虚拟IP是什么&#xff1f; 要是单讲解虚拟 IP,理解起来很困难,所以干脆把 动态 IP 、固定 IP 、实体 IP 与虚拟 IP都讲解一下,加深理解和知识扩展 实体 IP&#xff1a;在网络的世界里&#xff0c;为了要辨识每一部计算机的位置&#xff0c;因此有了计算机 IP 位址的定义。一…

虚拟IP简介

什么是虚拟IP 虚拟IP&#xff08;Virtual IP Address&#xff0c;简称VIP&#xff09;是一个未分配给真实弹性云服务器网卡的IP地址。弹性云服务器除了拥有私有IP地址外&#xff0c;还可以拥有虚拟IP地址&#xff0c;用户可以通过其中任意一个IP&#xff08;私有IP/虚拟IP&…

浮点数的表示及其运算

目录 浮点数一般表示 科学计数法 浮点数一般表示 浮点数表示范围 浮点数规格化 IEEE 754 浮点数一般表示 科学计数法 科学记数法的形式是由两个数的乘积组成的。表示为a10^b&#xff0c;例如电子质量9 x 10^28kg 浮点数一般表示 对于浮点数也是类似 ,S&#xff1a;正…

CPU算力(cpu理论浮点运算值)

最近因碰到CPU的算力是多少&#xff1f;所以研究了一下CPU的浮点计算理论值&#xff0c;做个笔记 FLOPS&#xff0c;即每秒浮点运算次数, 是每秒所执行的浮点运算次数&#xff08;Floating-point operations per second&#xff1b;缩写&#xff1a;FLOPS&#xff09;的简称&a…

CPU和GPU浮点运算方法-公式

处理器和GPU的计算能力如何计算&#xff1f; &#xff08;一&#xff09; CPU的浮点计算性能公式 我们常用双精度浮点运算能力衡量一个处理器的科学计算的能力&#xff0c;就是处理64bit小数点浮动数据的能力 intel的最新cpu支持高级矢量指令集AVX2、AVX512&#xff0c; 其中…

定点运算,浮点运算,算术逻辑单元

定点运算 &#xff08;一&#xff09;移位运算 1、移位运算的数学意义 先举一个例子&#xff1a;15m 1500 cm&#xff0c;在这个变换过程中&#xff0c;就可以通过移位运算进行实现&#xff0c;实际上在这个等式中&#xff0c;小数点被隐含了&#xff0c;在15m和1500cm数值最…