Redis从基础到进阶篇(二)----内存模型与内存优化

目录

一、缓存通识

1.1 ⽆处不在的缓存

1.2 多级缓存 (重点)

二、Redis简介

2.1 什么是Redis

2.2 Redis的应用场景

三、Redis数据存储的细节

3.1 Redis数据类型

3.2 内存结构

3.3 内存分配器

3.4 redisObject

3.4.1 type

3.4.2 encoding

3.4.3 ptr

3.4.4 refcount

3.4.5 lru

3.4.6 ⼩结

3.5 SDS

3.5.1 SDS内存结构

 3.5.2 SDS与C字符串的⽐较

四、Redis的对象类型与内存编码

4.1 字符串 

4.1.1 概况

4.1.2 内部编码

4.2 列表

4.2.1 概况

4.2.2 内部编码

4.3 哈希

4.3.1 概况

4.3.2 内部编码

4.3.3 编码转换

4.4 集合

4.4.1 概况 

4.4.2 内部编码

4.4.3 编码转换

4.5 有序集合

4.5.1 概况

4.5.2 内部编码

4.5.3 编码转换

4.5.4 跳跃表

五、Redis 设计优化

5.1 估算Redis内存使⽤量

5.2 优化内存占用

5.2.1 利用jemalloc特性进行优化

5.2.2 使用整型/长整型

5.2.3 共享对象

5.2.4 缩短键值对的存储长度

六、Reids 内存⽤量统计

6.1 used_memory

6.2 used_memory_rss

6.3 mem_fragmentation_ratio

6.4 mem_allocator

七、Redis内存划分

7.1 数据内存

7.2 进程内存

7.3 缓冲内存

7.4 内存碎⽚


一、缓存通识

缓存:存储在计算机上的⼀个原始数据复制集,以便于访问。 

缓存是介于数据访问者和数据源之间的⼀种⾼速存储,当数据需要多次读取的时候,⽤于加快读取的速 度。

缓存(Cache) 和 缓冲(Buffer) 的分别? 

缓存:⼀般是为了数据多次读取。

缓冲:⽐如CPU要把数据先硬盘,因为硬盘⽐较慢,先到缓冲设备Buffer,⽐如内存,Buffer读和写都需要。

1.1 ⽆处不在的缓存

CPU 缓存

操作系统缓存

数据库缓存

JVM 编译缓存

CDN 缓存

代理与反向代理缓存

前端缓存

应⽤程序缓存

分布式对象缓存

1.2 多级缓存 (重点)

二、Redis简介

2.1 什么是Redis

Redis是⽤C语⾔开发的⼀个开源的⾼性能键值对(key-value)的NoSQL数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求。

Redis作为⼀个单线程的应⽤,为什么处理请求性能如此NB?IO多路复⽤ 

NoSQL,泛指⾮关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充。

2.2 Redis的应用场景

缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使⽤)

分布式集群架构中的session分离。

聊天室的在线好友列表。

任务队列。(秒杀、抢购、12306等等)

应⽤排⾏榜。

⽹站访问统计。

数据过期处理(可以精确到毫秒)

三、Redis数据存储的细节

3.1 Redis数据类型

Redis整体上是⼀个KV结构,但是它的Value⼜可以分⽂以下五种数据类型。

⽬前为⽌Redis⽀持的键值数据类型如下:

字符串类型(string)                    set key value

散列类型(hash)                         hset key field value

列表类型(list)                            lpush key a b c d

集合类型(set)                            sadd key a b c d

有序集合类型(zset/sortedset) zadd key a score b score

3.2 内存结构

下图是执⾏set hello world时,所涉及到的数据模型。

1. dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有⼀个dictEntry,⾥⾯存储了指向Key和Value的指针;next指向下⼀个dictEntry,与本Key-Value⽆关。

2. Key:图中右上⻆可⻅,Key(”hello”)并不是直接以字符串存储,⽽是存储在SDS结构中。

3. redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key⼀样直接存储在SDS中,⽽是存储在redisObject中。实际上,不论Value是5种类型的哪⼀种,都是通过redisObject来存储的;⽽redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。实际上,redisObject除了type和ptr字段以外,还有其他字段图中没有给出,如⽤于指定对象内部编码的字段;后⾯会详细介绍。

4. jemalloc:⽆论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如

jemalloc)分配内存进⾏存储。

3.3 内存分配器

Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。

jemalloc作为Redis的默认内存分配器,在减⼩内存碎⽚⽅⾯做的相对⽐较好。jemalloc在64位系统中,将内存空间划分为⼩、⼤、巨⼤三个范围;每个范围内⼜划分了许多⼩的内存块单位;当Redis存储数据时,会选择⼤⼩最合适的内存块进⾏存储。

在 jemalloc 类⽐过来的物流系统中,同城仓库相当于 tcache —— 线程独有的内存仓库;区域仓库相当于 arena —— ⼏个线程共享的内存仓库;全国仓库相当于全局变量指向的内存仓库,为所有线程可⽤。

在 jemalloc 中,整块批发内存,之后或拆开零售,或整块出售。整块批发的内存叫做 chunk,对于⼩件和⼤件订单,则进⼀步拆成 run。Chunk 的⼤⼩为 4MB(可调)或其倍数,且为 4MB 对⻬;⽽ run ⼤⼩为⻚⼤⼩的整数倍。

在 jemalloc 中,⼩件订单叫做 small allocation,范围⼤概是 1-57344 字节。并将此区间分成 44 档,每次⼩分配请求归整到某档上。例如,⼩于8字节的,⼀律分配 8 字节空间;17-32分配请求,⼀律分配32 字节空间。

对于上述 44 档,有对应的 44 种 runs。每种 run 专⻔提供此档分配的内存块(叫做 region)。

⼤件订单叫做 large allocation,范围⼤概是 57345-4MB不到⼀点的样⼦,所有⼤件分配归整到⻚⼤⼩。

jemalloc划分的内存单元如下图所示:

 例如,如果需要存储⼤⼩为130字节的对象,jemalloc会将其放⼊160字节的内存单元中。

3.4 redisObject

Redis对象有5种类型;⽆论是哪种类型,Redis都不会直接存储,⽽是通过redisObject对象进⾏存储。

redisObject对象⾮常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject⽀持,下⾯将通过redisObject的结构来说明它是如何起作⽤的。

Redis中的每个对象都是由如下结构表示(列出了与保存数据有关的三个属性)

{unsigned type:4;//类型 五种对象类型unsigned encoding:4;//编码void *ptr;//指向底层实现数据结构的指针//...int refcount;//引⽤计数//...unsigned lru:24;//记录最后⼀次被命令程序访问的时间//...
}robj;

3.4.1 type

type字段表示对象的类型,占4个⽐特;⽬前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。当我们执⾏type命令时,便是通过读取RedisObject的type字段获得对象的类型;如下图所示:

3.4.2 encoding

encoding表示对象的内部编码,占4个⽐特。

对于Redis⽀持的每种类型,都有⾄少两种内部编码,例如对于字符串,有int、embstr、raw三种编码。通过encoding属性,Redis可以根据不同的使⽤场景来为对象设置不同的编码,⼤⼤提⾼了Redis的灵活性和效率。以列表对象为例,有压缩列表和双端链表两种编码⽅式;如果列表中的元素较少,Redis倾向于使⽤压缩列表进⾏存储,因为压缩列表占⽤内存更少,⽽且⽐双端链表可以更快载⼊;当列表对象元素较多时,压缩列表就会转化为更适合存储⼤量元素的双端链表。

通过object encoding命令,可以查看对象采⽤的编码⽅式,如下图所示:

5种对象类型对应的编码⽅式以及使⽤条件,将在后⾯介绍。 

3.4.3 ptr

ptr指针指向具体的数据,如前⾯的例⼦中,set hello world,ptr指向包含字符串world的SDS。

3.4.4 refcount

refcount与共享对象

refcount记录的是该对象被引⽤的次数,类型为整型。refcount的作⽤,主要在于对象的引⽤计数和内存回收。

当创建新对象时,refcount初始化为1;当有新程序使⽤该对象时,refcount加1;当对象不再被⼀个新程序使⽤时,refcount减1;当refcount变为0时,对象占⽤的内存会被释放。

共享对象的具体实现

Redis的共享对象⽬前只⽀持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度O(n);⽽对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使⽤共享对象(如哈希、列表等的元素可以使⽤)。

共享对象池

共享对象池是指Redis内部维护[0-9999]的整数对象池。

创建⼤量的整数类型redisObject存在内存开销,每个redisObject内部结构⾄少占16字节,甚⾄超过了整数⾃身空间消耗。

所以Redis内存维护⼀个[0-9999]的整数对象池,⽤于节约内存。

除了整数值对象,其他类型如list、hash、set、zset内部元素也可以使⽤整数对象池。

因此开发中在满⾜需求的前提下,尽量使⽤整数对象以节省内存。

就⽬前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使⽤值为0~9999的字符串对象时,可以直接使⽤这些共享对象。10000这个数字定义在源码的 OBJ_SHARED_INTEGERS 常量中定义。共享对象的引⽤次数可以通过object refcount命令查看,如下图所示。命令执⾏的结果⻚佐证了只有0~9999之间的整数会作为共享对象。

3.4.5 lru

 lru记录的是对象最后⼀次被命令程序访问的时间,占据的⽐特数不同的版本有所不同(2.6版本占22⽐特,4.0版本占24⽐特)。

通过对⽐lru时间与当前时间,可以计算某个对象的闲置时间;object idletime命令可以显示该闲置时间(单位是秒)。object idletime命令的⼀个特殊之处在于它不改变对象的lru值。

lru值除了通过object idletime命令打印之外,还与Redis的内存回收有关系:如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占⽤超过maxmemory指定的值时,Redis会优先选择空转时间最⻓的对象进⾏释放。

3.4.6 ⼩结

综上所述,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;⼀个redisObject对象的⼤⼩为16字节:

4bit(类型)+4bit(编码)+24bit(lru)+4Byte(refcount)+8Byte(指针)=16Byte

3.5 SDS

3.5.1 SDS内存结构

Redis没有直接使⽤C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,⽽是使⽤了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。

(1) 3.2之前

struct sdshdr{//记录buf数组中已使⽤字节的数量//等于 SDS 保存字符串的⻓度int len;//记录 buf 数组中未使⽤字节的数量int free;//字节数组,⽤于保存字符串char buf[];
}

其中,buf表示字节数组,⽤来存储字符串;len表示buf已使⽤的⻓度,free表示buf未使⽤的⻓度。下⾯是两个例⼦。

通过SDS的结构可以看出,[buf数组的⻓度=free+len+1(其中1表示字符串结尾的空字符);所以,⼀个SDS结构占据的空间为:free所占⻓度+len所占⻓度+ buf数组的⻓度+1=4+4+字符串⻓度+1=字符串⻓度+9。

(2) 3.2之后 

typedef char *sds; 
struct __attribute__ ((__packed__)) sdshdr5 { // 对应的字符串⻓度⼩于 1<<5 32字 节unsigned char flags; /* 3 lsb of type, and 5 msb of string length int
embstr*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 { // 对应的字符串⻓度⼩于 1<<8 256uint8_t len; /* used */ //⽬前字符创的⻓度 ⽤1字节存储uint8_t alloc; //已经分配的总⻓度 ⽤1字节存储unsigned char flags; //flag⽤3bit来标明类型,类型后续
解释,其余5bit⽬前没有使⽤ embstr rawchar buf[]; //柔性数组,以'\0'结尾
};
struct __attribute__ ((__packed__)) sdshdr16 { // 对应的字符串⻓度⼩于 1<<16uint16_t len; /*已使⽤⻓度,⽤2字节存储*/uint16_t alloc; /* 总⻓度,⽤2字节存储*/unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 { // 对应的字符串⻓度⼩于 1<<32uint32_t len; /*已使⽤⻓度,⽤4字节存储*/uint32_t alloc; /* 总⻓度,⽤4字节存储*/unsigned char flags;/* 低3位存储类型, ⾼5位预留 */char buf[];/*柔性数组,存放实际内容*/
};
struct __attribute__ ((__packed__)) sdshdr64 { // 对应的字符串⻓度⼩于 1<<64uint64_t len; /*已使⽤⻓度,⽤8字节存储*/uint64_t alloc; /* 总⻓度,⽤8字节存储*/unsigned char flags; /* 低3位存储类型, ⾼5位预留 */char buf[];/*柔性数组,存放实际内容*/
};

flag属性保存的是当前使⽤的SDS类型:

 3.5.2 SDS与C字符串的⽐较

获取字符串⻓度:SDS是O(1),C字符串是O(n)

缓冲区溢出:使⽤C字符串的API时,如果字符串⻓度增加(如strcat操作)⽽忘记重新分配内存,很容易造成缓冲区的溢出;⽽SDS由于记录了⻓度,相应的API在可能造成缓冲区溢出时会⾃动重新分配内存,杜绝了缓冲区溢出。

修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串⻓度增⼤时会造成内存缓冲区溢出,字符串⻓度减⼩时会造成内存泄露。⽽对于SDS,由于可以记录len和free,因此解除了字符串⻓度和空间数组⻓度之间的关联,可以在此基础上进⾏优化:空间预分配策略(即分配内存时⽐实际需要的多)使得字符串⻓度增⼤时重新分配内存的概率⼤⼤减⼩;惰性空间释放策略使得字符串⻓度减⼩时重新分配内存的概率⼤⼤减⼩。

存取⼆进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,⽽对于⼀些⼆进制⽂件(如图⽚等),内容可能包括空字符串,因此C字符串⽆法正确存取;⽽SDS以字符串⻓度len来作为字符串结束标识,因此没有这个问题。

四、Redis的对象类型与内存编码

Redis⽀持5种对象类型,⽽每种结构都有⾄少两种编码;

这样做的好处在于:

⼀⽅⾯接⼝与实现分离,当需要增加或改变内部编码时,⽤户使⽤不受影响;

另⼀⽅⾯可以根据不同的应⽤场景切换内部编码,提⾼效率。

Redis各种对象类型⽀持的内部编码如下图所示(只列出重点的):

4.1 字符串 

4.1.1 概况

字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他⼏种复杂类型的元素也是字符串。字符串⻓度不能超过512MB。

4.1.2 内部编码

字符串类型的内部编码有3种,它们的应⽤场景如下:

int:8个字节的⻓整型。字符串值是整型时,这个值使⽤long整型表示。

embstr:<=44字节的字符串。embstr与raw都使⽤redisObject和sds保存数据,区别在于,embstr的使⽤只分配⼀次内存空间(因此redisObject和sds是连续的),⽽raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相⽐,embstr的好处在于创建时少分配⼀次空间,删除时少释放⼀次空间,以及对象的所有数据连在⼀起,寻找⽅便。⽽embstr的坏处也很明显,如果字符串的⻓度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。

raw:⼤于44个字节的字符串

3.2之后 embstr和raw进⾏区分的⻓度,是44;是因为redisObject的⻓度是16字节,sds的⻓度是4+字符串⻓度;因此当字符串⻓度是44时,embstr的⻓度正好是16+4+44 =64,jemalloc正好可以分配64字节的内存单元。

3.2 之前embstr和raw进⾏区分的⻓度,是39,因为redisObject的⻓度是16字节,sds的⻓度是9+字符串⻓度;因此当字符串⻓度是39时,embstr的⻓度正好是16+9+39 =64,jemalloc正好可以分配64字节的内存单元。

4.2 列表

4.2.1 概况

列表(list)⽤来存储多个有序的字符串,每个字符串称为元素;

⼀个列表可以存储2^32-1个元素。

Redis中的列表⽀持两端插⼊和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。

4.2.2 内部编码

Redis3.0之前列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist)。选择的折中⽅案是两种数据类型的转换,但是在3.2版本之后 因为转换也是个费时且复杂的操作,引⼊了⼀种新的数据格式,结合了双向列表linkedlist和ziplist的特点,称之为quicklist。所有的节点都⽤quicklist存储,省去了到临界条件是的格式转换。

(1) 压缩列表

当⼀个列表只包含少量列表项时,并且每个列表项时⼩整数值或短字符串,那么Redis会使⽤压缩列表来做该列表的底层实现。

压缩列表(ziplist)是Redis为了节省内存⽽开发的,是由⼀系列特殊编码的连续内存块组成的顺序型数据结构,⼀个压缩列表可以包含任意多个节点(entry),每个节点可以保存⼀个字节数组或者⼀个整数值。放到⼀个连续内存区 

previous_entry_length: 记录压缩列表前⼀个字节的⻓度。
encoding:节点的encoding保存的是节点的content的内容类型
content:content区域⽤于保存节点的内容,节点内容类型和⻓度由encoding决定。

(2) 双向链表 

双向链表(linkedlist):由⼀个list结构和多个listNode结构组成;

通过图中可以看出,双端链表同时保存了表头指针和表尾指针,并且每个节点都有指向前和指向后的指针;链表中保存了列表的⻓度;dup、free和match为节点值设置类型特定函数,所以链表可以⽤于保存各种不同类型的值。⽽链表中每个节点指向的是type为字符串的redisObject。

(3) 快速列表

        简单的说,我们仍旧可以将其看作⼀个双向列表,但是列表的每个节点都是⼀个ziplist,其实就是linkedlist和ziplist的结合。quicklist中的每个节点ziplist都能够存储多个数据元素。

Redis3.2开始,列表采⽤quicklist进⾏编码。

//32byte 的空间
typedef struct quicklist {// 指向quicklist的头部quicklistNode *head; // 指向quicklist的尾部quicklistNode *tail;// 列表中所有数据项的个数总和unsigned long count; // quicklist节点的个数,即ziplist的个数unsigned int len;// ziplist⼤⼩限定,由list-max-ziplist-size给定// 表示不⽤整个int存储fill,⽽是只⽤了其中的16位来存储int fill : 16; // 节点压缩深度设置,由list-compress-depth给定unsigned int compress : 16;
} quicklist;
typedef struct quicklistNode {struct quicklistNode *prev; // 指向上⼀个ziplist节点struct quicklistNode *next; // 指向下⼀个ziplist节点unsigned char *zl; // 数据指针,如果没有被压缩,就指向ziplist结构,反之指向    quicklistLZF结构unsigned int sz; // 表示指向ziplist结构的总⻓度(内存占⽤⻓度)unsigned int count : 16; // 表示ziplist中的数据项个数unsigned int encoding : 2; // 编码⽅式,1--ziplist,2--quicklistLZFunsigned int container : 2; // 预留字段,存放数据的⽅式,1--NONE,2--ziplistunsigned int recompress : 1; // 解压标记,当查看⼀个被压缩的数据时,需要暂时解压,标记此参数为1,之后再重新进⾏压缩unsigned int attempted_compress : 1; // 测试相关unsigned int extra : 10; // 扩展字段,暂时没⽤
} quicklistNode;

4.3 哈希

4.3.1 概况

哈希(作为⼀种数据结构),不仅是Redis对外提供的5种对象类型的⼀种(与字符串、列表、集合、有序结合并列),也是Redis作为Key-Value数据库所使⽤的数据结构。为了说明的⽅便,后⾯当使⽤“内层的哈希”时,代表的是Redis对外提供的5种对象类型的⼀种;使⽤“外层的哈希”代指Redis作为Key-Value数据库所使⽤的数据结构。

4.3.2 内部编码

内层的哈希使⽤的内部编码可以是压缩列表(ziplist)和哈希表(hashtable)两种;Redis的外层的哈希则只使⽤了hashtable

压缩列表前⾯已介绍。与哈希表相⽐,压缩列表⽤于元素个数少、元素⻓度⼩的场景;其优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(1)变为了O(n),但由于哈希中元素数量较少,因此操作的时间并没有明显劣势。

hashtable:⼀个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。

正常情况下(即hashtable没有进⾏rehash时)各部分关系如下图所示:

 (1) dict

⼀般来说,通过使⽤dictht和dictEntry结构,便可以实现普通哈希表的功能;但是Redis的实现中,在dictht结构的上层,还有⼀个dict结构。下⾯说明dict结构的定义及作⽤。

dict结构如下:

typedef struct dict{ dictType *type; // type⾥⾯主要记录了⼀系列的函数,可以说是规定了⼀系列的接⼝ void *privdata; // privdata保存了需要传递给那些类型特定函数的可选参数 //两张哈希表dictht ht[2];//便于渐进式rehash int trehashidx; //rehash 索引,并没有rehash时,值为 -1//⽬前正在运⾏的安全迭代器的数量int iterators;
} dict;

其中,type属性和privdata属性是为了适应不同类型的键值对,⽤于创建多态字典

ht属性和trehashidx属性则⽤于rehash,即当哈希表需要扩展或收缩时使⽤。ht是⼀个包含两个项的数组,每项都指向⼀个dictht结构,这也是Redis的哈希会有1个dict、2个dictht结构的原因。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使⽤。dict进⾏rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]。

因此,Redis中的哈希之所以在dictht和dictEntry结构之外还有⼀个dict结构,⼀⽅⾯是为了适应不同类型的键值对,另⼀⽅⾯是为了rehash。

(2) dictht

dictht结构如下:

typedef struct dictht{ //哈希表数组,每个元素都是⼀条链表dictEntry **table; //哈希表⼤⼩unsigned long size;// 哈希表⼤⼩掩码,⽤于计算索引值// 总是等于 size - 1unsigned long sizemask; // 该哈希表已有节点的数量unsigned long used; 
}dictht;

其中,各个属性的功能说明如下:

table属性是⼀个指针,指向bucket;

size属性记录了哈希表的⼤⼩,即bucket的⼤⼩;

used记录了已使⽤的dictEntry的数量;

sizemask属性的值总是为size-1,这个属性和哈希值⼀起决定⼀个键在table中存储的位置。

(3) bucket

bucket是⼀个数组,数组的每个元素都是指向dictEntry结构的指针。Redis中bucket数组的⼤⼩计算规则如下:⼤于dictEntry的、最⼩的2^n;

例如,如果有1000个dictEntry,那么bucket⼤⼩为1024;如果有1500个dictEntry,则bucket⼤⼩为2048。n%32 = n&(32-1)

(4) dictEntry

dictEntry结构⽤于保存键值对,结构定义如下:

// 键
typedef struct dictEntry{ void *key; union{ //值v的类型可以是以下三种类型 void *val; uint64_tu64; int64_ts64; }v; // 指向下个哈希表节点,形成链表struct dictEntry *next; 
}dictEntry;

其中,各个属性的功能如下:

key:键值对中的键

val:键值对中的值,使⽤union(即共⽤体)实现,存储的内容既可能是⼀个指向值的指针,也可能是64位整型,或⽆符号64位整型;

next:指向下⼀个dictEntry,⽤于解决哈希冲突问题

在64位系统中,⼀个dictEntry对象占24字节(key/val/next各占8字节)。

4.3.3 编码转换

如前所述,Redis中内层的哈希既可能使⽤哈希表,也可能使⽤压缩列表。

只有同时满⾜下⾯两个条件时,才会使⽤压缩列表:

哈希中元素数量⼩于512个;

哈希中所有键值对的键和值字符串⻓度都⼩于64字节

下图展示了Redis内层的哈希编码转换的特点:

4.4 集合

4.4.1 概况 

集合(set)与列表类似,都是⽤来保存多个字符串,但集合与列表有两点不同:集合中的元素是⽆序的,因此不能通过索引来操作元素;集合中的元素不能有重复。

⼀个集合中最多可以存储2^32-1个元素;除了⽀持常规的增删改查,Redis还⽀持多个集合取交集、并集、差集。

4.4.2 内部编码

集合的内部编码可以是整数集合(intset)或哈希表(hashtable)

哈希表前⾯已经讲过,这⾥略过不提;需要注意的是,集合在使⽤哈希表时,值全部被置为null。

整数集合的结构定义如下: 

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

其中,encoding代表contents中存储内容的类型,虽然contents(存储集合中的元素)是int8_t类型,但实际上其存储的值是int16_t、int32_t或int64_t,具体的类型便是由encoding决定的;

length表示元素个数。

整数集合适⽤于集合所有元素都是整数且集合元素数量较⼩的时候,与哈希表相⽐,整数集合的优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(1)变为了O(n),但由于集合数量较少,因此操作的时间并没有明显劣势。

4.4.3 编码转换

只有同时满⾜下⾯两个条件时,集合才会使⽤整数集合:

集合中元素数量⼩于512个;

集合中所有元素都是整数值。

如果有⼀个条件不满⾜,则使⽤哈希表;且编码只可能由整数集合转化为哈希表,反⽅向则不可能。下图展示了集合编码转换的特点:

4.5 有序集合

4.5.1 概况

有序集合与集合⼀样,元素都不能重复;但与集合不同的是,有序集合中的元素是有顺序的。与列表使⽤索引下标作为排序依据不同,有序集合为每个元素设置⼀个分数(score)作为排序依据。

4.5.2 内部编码

有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)。ziplist在列表和哈希中都有使⽤,前⾯已经讲过,这⾥略过不提。

跳跃表是⼀种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从⽽达到快速访问节点的⽬的。除了跳跃表,实现有序数据结构的另⼀种典型实现是平衡树;⼤多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现⽐平衡树简单很多,因此redis中选⽤跳跃表代替平衡树。跳跃表⽀持平均O(logN)、最坏O(N)的复杂点进⾏节点查找,并⽀持顺序操作。 

4.5.3 编码转换

Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成:前者⽤于保存跳跃表信息(如头结点、尾节点、⻓度等),后者⽤于表示跳跃表节点。

只有同时满⾜下⾯两个条件时,才会使⽤压缩列表:

1)有序集合中元素数量⼩于128个;

2)有序集合中所有成员⻓度都不⾜64字节。

如果有⼀个条件不满⾜,则使⽤跳跃表;且编码只可能由压缩列表转化为跳跃表,反⽅向则不可能。

下图展示了有序集合编码转换的特点: 

4.5.4 跳跃表

(1) 数据结构定义

有许多数据结构的定义其实是按照(结点+组织⽅式)来的,结点就是⼀个数据点,组织⽅式就是把结点组织起来形成数据结构,⽐如 双端链表 (ListNode+list)、字(dictEntry+dictht+dict)等,今天所说的SkipList其实也⼀样,我们⾸先看下它的 结点 定义:

typedef struct zskiplistNode { sds ele; //数据域double score; //分值struct zskiplistNode *backward; //后向指针,使得跳表第⼀层组织为双向链表struct zskiplistLevel { //每⼀个结点的层级struct zskiplistNode *forward; //某⼀层的前向结点unsigned int span; //某⼀层距离下⼀个结点的跨度} level[]; //level本身是⼀个柔性数组,最⼤值为32,由ZSKIPLIST_MAXLEVEL 定义
} zskiplistNode;
接下来是组织⽅式,即使⽤上⾯的 zskiplistNode 组织起⼀个 SkipList
typedef struct zskiplist {struct zskiplistNode *header; //头部struct zskiplistNode *tail; //尾部unsigned long length; //⻓度,即⼀共有多少个元素int level; //最⼤层级,即跳表⽬前的最⼤层级
} zskiplist;
核⼼的数据结构就是上⾯两个。

(2) 具体图表示

普通单向链表:

 跳跃表(跳表):

 (3) 查询

查找⼀个节点时,我们只需从⾼层到低层,⼀个个链表查找,每次找到该层链表中⼩于等于⽬标节点的 最⼤节点,直到找到为⽌。由于⾼层的链表迭代时会“ 跳过 低层的部分节点,所以跳跃表会⽐正常的链 表查找少查部分节点,这也是skiplist 名字的由来。
例如:
查找 46 55---21---55--37--55--46

(4) 插⼊

L1
概率算法
在此还是以上图为例:跳跃表的初试状态如下图,表中没有⼀个元素:

                                     

如果我们要插⼊元素2,⾸先是在底部插⼊元素2,如下图:

                     

 然后我们抛硬币,结果是正⾯,那么我们要将2插⼊到L2层,如下图:

                    

继续抛硬币,结果是反⾯,那么元素 2 的插⼊操作就停⽌了,插⼊后的表结构就是上图所示。接下来,我 们插⼊元素33 ,跟元素 2 的插⼊⼀样,现在 L1 层插⼊ 33 ,如下图:

           

然后抛硬币,结果是反⾯,那么元素 33 的插⼊操作就结束了,插⼊后的表结构就是上图所示。接下来, 我们插⼊元素55 ,⾸先在 L1 插⼊ 55 ,插⼊后如下图:

    然后抛硬币,结果是正⾯,那么L2层需要插⼊55,如下图:

 继续抛硬币,结果⼜是正⾯,那么L3层需要插⼊55,如下图:

 

 继续抛硬币,结果⼜是正⾯,那么要在L4插⼊55,结果如下图:

 继续抛硬币,结果是反⾯,那么55的插⼊结束,表结构就如上图所示。

以此类推,我们插⼊剩余的元素。当然因为规模⼩,结果很可能不是⼀个理想的跳跃表。但是如果元素 个数n 的规模很⼤,学过概率论的同学都知道,最终的表结构肯定⾮常接近于理想跳跃表(隔⼀个⼀跳)。
(5) 删除
直接删除元素,然后调整⼀下删除元素后的指针即可。跟普通的链表删除操作完全⼀样。
typedef struct zskiplistNode {//层struct zskiplistLevel{//前进指针 后边的节点struct zskiplistNode *forward;//跨度unsigned int span;}level[];//后退指针struct zskiplistNode *backward;//分值double score;//成员对象robj *obj;
} zskiplistNode
--链表
typedef struct zskiplist{//表头节点和表尾节点structz skiplistNode *header, *tail;//表中节点的数量unsigned long length;//表中层数最⼤的节点的层数int level; 
}zskiplist;
①、搜索:从最⾼层的链表节点开始,如果⽐当前节点要⼤和⽐当前层的下⼀个节点要⼩,那么则往下 找,也就是和当前层的下⼀层的节点的下⼀个节点进⾏⽐较,以此类推,⼀直找到最底层的最后⼀个节 点,如果找到则返回,反之则返回空。
②、插⼊:⾸先确定插⼊的层数,有⼀种⽅法是假设抛⼀枚硬币,如果是正⾯就累加,直到遇⻅反⾯为 ⽌,最后记录正⾯的次数作为插⼊的层数。当确定插⼊的层数k 后,则需要将新元素插⼊到从底层到 k 层。
③、删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头 尾两个节点,则删除这⼀层。

五、Redis 设计优化

5.1 估算Redis内存使⽤量

要估算 redis 中的数据占据的内存⼤⼩,需要对 redis 的内存模型有⽐较全⾯的了解,包括 hashtable、 sds redisobject 、各种对象类型的编码⽅式等。
下⾯以最简单的字符串类型来进⾏说明。
假设有 90000 个键值对,每个 key 的⻓度是 12 个字节 ,每个 value 的⻓度也是 12 个字节 (且 key 和 value都不是整数);

下⾯来估算这 90000 个键值对所占⽤的空间。在估算占据空间之前,⾸先可以判定字符串类型使⽤的编码⽅式:embstr
90000 个键值对占据的内存空间主要可以分为两部分: ⼀部分是 90000 dictEntry 占据的空间;⼀部分 是键值对所需要的bucket 空间。
每个 dictEntry 占据的空间包括:
(1) ⼀个 dictEntry 结构, 24 字节, jemalloc 会分配 32 字节的内存块 ( 64 位操作系统下,⼀个指针 8 字 节,⼀个dictEntry 由三个指针组成 )
(2) ⼀个 key 12 字节,所以 SDS(key) 需要 12+4=16 个字节( [SDS 的⻓度 =4+ 字符串⻓度), jemalloc 会分配16 字节的内存块
(3) ⼀个 redisObject 16 字节, jemalloc 会分配 16 字节的内存块                                       ( 4bit+4bit+24bit+4Byte+8Byte=16Byte )
(4) ⼀个 value 12 字节,所以 SDS(value) 需要 12+4=16 个字节( [SDS 的⻓度 =4+ 字符串⻓度), jemalloc会分配 16 字节的内存块
(5) 综上,⼀个 dictEntry 所占据的空间需要 32+16+16+16=80 个字节。
bucket 空间:
bucket 数组的⼤⼩为⼤于 90000 的最⼩的 2^n ,是 131072 ;每个 bucket 元素( bucket 中存储的都是指针元素 )为 8 字节( 因为 64 位系统中指针⼤⼩为 8 字节 )。
因此,可以估算出这 90000 个键值对占据的内存⼤⼩为: [90000*80 + 131072*8 = 8248576
作为对⽐ key value 的⻓度由 12 字节增加到 13 字节 ,则对应的 SDS 变为 17 个字节, jemalloc 会 分配32 个字节,因此每个 dictEntry 占⽤的字节数也由 80 字节变为 112 字节。此时估算这 90000 个键 值对占据内存⼤⼩为: 90000*112 + 131072*8 = 11128576

5.2 优化内存占用

了解 redis 的内存模型,对优化 redis 内存占⽤有很⼤帮助。下⾯介绍⼏种优化场景和⽅式

5.2.1 利用jemalloc特性进行优化

上⼀⼩节所讲述的 90000 个键值便是⼀个例⼦。由于 jemalloc 分配内存时数值是不连续的,因此
key/value 字符串变化⼀个字节,可能会引起占⽤内存很⼤的变动;在设计时可以利⽤这⼀点。
例如,如果 key 的⻓度如果是 13 个字节,则 SDS 17 字节, jemalloc 分配 32 字节;此时将 key ⻓度 缩减为12 个字节,则 SDS 16 字节, jemalloc 分配 16 字节;则每个 key 所占⽤的空间都可以缩⼩⼀ 半。

5.2.2 使用整型/长整型

如果是整型 / ⻓整型, Redis 会使⽤ int 类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以 使⽤⻓整型/ 整型代替字符串的场景下,尽量使⽤⻓整型 / 整型

5.2.3 共享对象

利⽤共享对象,可以减少对象的创建(同时减少了 redisObject 的创建),节省内存空间。⽬前 redis 中的 共享对象只包括10000 个整数( 0-9999 );可以通过调整 OBJ_SHARED_INTEGERS 参数提⾼共享对象的个数;

5.2.4 缩短键值对的存储长度

键值对的⻓度是和性能成反⽐的,⽐如我们来做⼀组写⼊数据的性能测试,执⾏结果如下:

从以上数据可以看出,在 key 不变的情况下, value 值越⼤操作效率越慢,因为 Redis 对于同⼀种数据 类型会使⽤不同的内部编码进⾏存储,⽐如字符串的内部编码就有三种:int (整数编码)、 raw (优化 内存分配的字符串编码)、embstr (动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现 效率和空间的平衡,然⽽数据量越⼤使⽤的内部编码就越复杂,⽽越是复杂的内部编码存储的性能就越低。
这还只是写⼊时的速度,当键值对内容较⼤时,还会带来另外⼏个问题:
内容越⼤需要的持久化时间就越⻓,需要挂起的时间越⻓, Redis 的性能就会越低;
内容越⼤在⽹络上传输的内容就越多,需要的时间就越⻓,整体的运⾏速度就越低;
内容越⼤占⽤的内存就越多,就会更频繁的触发内存淘汰机制,从⽽给 Redis 带来了更多的运⾏负 担。
因此在保证完整语义的同时,我们要尽量的缩短键值对的存储⻓度,必要时要对数据进⾏序列化和压缩再存储,以 Java 为例,序列化我们可以使⽤ protostuff kryo ,压缩我们可以使⽤ snappy

六、Reids 内存⽤量统计

查看 Redis 内存统计
127.0.0.1:6379> info memory
# Memory
#Redis分配的内存总量,包括虚拟内存(字节)
used_memory:853464
#占操作系统的内存,不包括虚拟内存(字节)
used_memory_rss:12247040
#内存碎⽚⽐例 如果⼩于1说明使⽤了虚拟内存
mem_fragmentation_ratio:15.07
#内存碎⽚字节数
mem_fragmentation_bytes
#Redis使⽤的内存分配器
mem_allocator:jemlloc-5.1.0

6.1 used_memory

Redis 内存分配器 分配的 数据内存 缓冲内存 的内存总量(单位是字节),包括使⽤的虚拟内存(即 swap) used_memory_human 只是显示更加⼈性化。

6.2 used_memory_rss

记录的是由 操作系统分配 Redis 进程内存 Redis 内存中⽆法再被 jemalloc 分配的 内存碎⽚
(单位是字节)。
used_memoryused_memory_rss的区别:
前者是从 Redis ⻆度得到的量,后者是从操作系统⻆度得到的量。⼆者之所以有所不同,⼀⽅⾯是 因为内存碎⽚和Redis 进程运⾏需要占⽤内存,使得前者可能⽐后者⼩,另⼀⽅⾯虚拟内存的存在,使得前者可能⽐后者⼤。
由于在实际应⽤中, Redis 的数据量会⽐较⼤,此时进程运⾏占⽤的内存与 Redis 数据量和内存碎⽚相⽐,都会⼩得多;因此used_memory_rss used_memory 的⽐例,便成了衡量 Redis 内存碎⽚率的参 数;这个参数就是mem_fragmentation_ratio

6.3 mem_fragmentation_ratio

内存碎⽚⽐率 ,该值是 used_memory_rss / used_memory 的⽐值。
mem_fragmentation_ratio ⼀般⼤于 1 ,且 该值越⼤,内存碎⽚⽐例越⼤。
mem_fragmentation_ratio<1 ,说明 Redis 使⽤了虚拟内存,由于虚拟内存的媒介是磁盘,⽐内存速度 要慢很多, 当这种情况出现时,应该及时排查,如果内存不⾜应该及时处理,如增加 Redis 节点、增加 Redis服务器的内存、优化应⽤等
⼀般来说, mem_fragmentation_ratio 1.03 左右是⽐较健康的状态(对于 jemalloc 来说);刚开始的 mem_fragmentation_ratio值很⼤,是因为还没有向 Redis 中存⼊数据, Redis 进程本身运⾏的内存使得 used_memory_rss ⽐ used_memory ⼤得多。

6.4 mem_allocator

Redis 使⽤的内存分配器,在编译时指定;可以是 libc jemalloc 或者 tcmalloc 默认是 jemalloc

七、Redis内存划分

Redis 作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前⾯的叙述可以知道,除了数 据以外,Redis 的其他部分也会占⽤内存。
Redis 的内存占⽤主要可以划分为以下⼏个部分:

7.1 数据内存

作为数据库,数据是最主要的部分;这部分占⽤的内存会统计在used_memory

Redis 使⽤键值对存储数据,其中的值(对象)包括 5 种类型,即字符串、哈希、列表、集合、有序集 合。这5 种类型是 Redis 对外提供的,实际上,在 Redis 内部,每种类型可能有 2 种或更多的内部编码实 现;此外,Redis 在存储对象时,并不是直接将数据扔进内存,⽽是会对对象进⾏各种包装:如 redisObject、 SDS 等;

7.2 进程内存

Redis 主进程本身运⾏肯定需要占⽤内存,如代码、常量池等等;这部分内存 ⼤约⼏兆 ,在⼤多数⽣产环 境中与Redis 数据占⽤的内存相⽐可以忽略。 这部分内存不是由 jemalloc 分配,因此不会统计在 used_memory中。

7.3 缓冲内存

缓冲内存包括客户端缓冲区、复制积压缓冲区、 AOF 缓冲区等;其中,客户端缓冲存储客户端连接的输 ⼊输出缓冲;复制积压缓冲⽤于部分复制功能;AOF 缓冲区⽤于在进⾏ AOF 重写时,保存最近的写⼊命 令。在了解相应功能之前,不需要知道这些缓冲的细节; 这部分内存由 jemalloc 分配,因此会统计在 used_memory中

7.4 内存碎⽚

内存碎⽚是 Redis 在分配、回收物理内存过程中产⽣的。 例如,如果对数据的更改频繁,⽽且数据之间的 ⼤⼩相差很⼤,可能导致redis 释放的空间在物理内存中并没有释放,但 redis ⼜⽆法有效利⽤,这就形成 了内存碎⽚。 内存碎⽚不会统计在 used_memory 中。
内存碎⽚的产⽣与对数据进⾏的操作、数据的特点等都有关;此外,与使⽤的内存分配器也有关系:如 果内存分配器设计合理,可以尽可能的减少内存碎⽚的产⽣。jemalloc 便在控制内存碎 ⽚⽅⾯做的很好。
如果 Redis 服务器中的内存碎⽚已经很⼤,可以通过安全重启的⽅式减⼩内存碎⽚:因为重启之后, Redis重新从备份⽂件中读取数据,在内存中进⾏重排,为每个数据重新选择合适的内存单元,减⼩内存碎⽚。
好了到这里本篇完,如果你有不懂的或者想要补充的,可以在评论区留言,我会及时回复。
如果这篇文章对你有帮助请不要忘记给博主个三连!!!感谢!!!

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

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

相关文章

【Go 基础篇】Go 语言字符串函数详解:处理字符串进阶

大家好&#xff01;继续我们关于Go语言中字符串函数的探索。字符串是编程中常用的数据类型&#xff0c;而Go语言为我们提供了一系列实用的字符串函数&#xff0c;方便我们进行各种操作&#xff0c;如查找、截取、替换等。在上一篇博客的基础上&#xff0c;我们将继续介绍更多字…

基于Java+SpringBoot+Vue前后端分离工厂车间管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

基于Java+SpringBoot+Vue前后端分离纺织品企业财务管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

前端学习记录~2023.8.10~JavaScript重难点实例精讲~第6章 Ajax

第 6 章 Ajax 前言6.1 Ajax的基本原理及执行过程6.1.1 XMLHttpRequest对象&#xff08;1&#xff09;XMLHttpRequest对象的函数&#xff08;2&#xff09;XMLHttpRequest对象的属性 6.1.2 XMLHttpRequest对象生命周期&#xff08;1&#xff09;创建XMLHttpRequest对象&#xff…

kubernetes--技术文档--可视化管理界面dashboard安装部署

阿丹&#xff1a; 使用官方提供的可视化界面来完成。 Kubernetes Dashboard是Kubernetes集群的Web UI&#xff0c;用户可以通过Dashboard进行管理集群内所有资源对象&#xff0c;例如查看资源对象的运行情况&#xff0c;部署新的资源对象&#xff0c;伸缩Deployment中的Pod数量…

Linux命令200例:telnet用于远程登录的网络协议(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

java八股文面试[JVM]——垃圾回收器

jvm结构总结 常见的垃圾回收器有哪些&#xff1f; CMS&#xff08;Concurrent Mark Sweep&#xff09; 整堆收集器&#xff1a; G1 由于整个过程中耗时最长的并发标记和并发清除过程中&#xff0c;收集器线程都可以与用户线程一起工作&#xff0c;所以总体上来说&#xff0c;…

基于JSP+Servlet+Mysql员工信息管理系统

基于JSPServletMysql员工信息管理系统 一、系统介绍二、功能展示三.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基于JSPServlet的员工/客户/人员信息管理系统 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言…

使用CSS的@media screen 规则为不同的屏幕尺寸设置不同的样式(响应式图片布局)

当你想要在不同的屏幕尺寸或设备上应用不同的CSS样式时&#xff0c;可以使用 media 规则&#xff0c;特别是 media screen 规则。这允许你根据不同的屏幕特性&#xff0c;如宽度、高度、方向等&#xff0c;为不同的屏幕尺寸设置不同的样式。 具体来说&#xff0c;media screen…

React绑定antd输入框,点击清空或者确定按钮实现清空输入框内容

其实实现原理和vue的双向绑定是一样的&#xff0c;就是监听输入框的onChange事件&#xff0c;绑定value值&#xff0c;当输入框内容发生变化后&#xff0c;就重新设置这个value值。 示例代码&#xff1a;我这里是统一在handleCancel这个函数里面处理清空逻辑了&#xff0c;你们…

【大数据】Doris:基于 MPP 架构的高性能实时分析型数据库

Doris&#xff1a;基于 MPP 架构的高性能实时分析型数据库 1.Doris 介绍 Apache Doris 是一个基于 MPP&#xff08;Massively Parallel Processing&#xff0c;大规模并行处理&#xff09;架构的高性能、实时的分析型数据库&#xff0c;以极速易用的特点被人们所熟知&#xff…

Elasticsearch 入门安装

1.Elasticsearch 是什么 The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elaticsearch&#xff0c;简称为…

[NLP]LLM--transformer模型的参数量

1. 前言 最近&#xff0c;OpenAI推出的ChatGPT展现出了卓越的性能&#xff0c;引发了大规模语言模型(Large Language Model, LLM)的研究热潮。大规模语言模型的“大”体现在两个方面&#xff1a;模型参数规模大&#xff0c;训练数据规模大。以GPT3为例&#xff0c;GPT3的参数量…

CodeLlama本地部署的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

00-音视频-概述

有很多场合会使用的音视频&#xff0c;比如安防、视频闸机、影音播放器、视频通话&#xff0c;短视频等等。 从摄像头采集到用户观看&#xff0c;这中间涉及到了很多技术。 用户一般观看的高清视频1080P30帧。若按24位RGB对视频进行存储&#xff0c;一个60分钟视频所占空间 …

STM32 Cubemx配置串口收发

文章目录 前言注意事项Cubemx配置printf重定向修改工程属性修改源码 测试函数 前言 最近学到了串口收发&#xff0c;简单记录一下注意事项。 注意事项 Cubemx配置 以使用USART1为例。 USART1需配置成异步工作模式Asynchronous。 并且需要使能NVIC。 printf重定向 我偏向…

JMeter 接口自动化测试:从入门到精通的完全指南

JMeter 是一个开源的负载测试工具&#xff0c;它可以模拟多种协议和应用程序的负载&#xff0c;包括 HTTP、FTP、SMTP、JMS、SOAP 和 JDBC 等。在进行接口自动化测试时&#xff0c;使用 JMeter 可以帮助我们快速地构建测试用例&#xff0c;模拟多种场景&#xff0c;发现接口的性…

论文阅读_条件控制_ControlNet

name_en: Adding Conditional Control to Text-to-Image Diffusion Models name_ch: 向文本到图像的扩散模型添加条件控制 paper_addr: http://arxiv.org/abs/2302.05543 date_read: 2023-08-17 date_publish: 2023-02-10 tags: [‘图形图像’,‘大模型’,‘多模态’] author: …

什么是计算机视觉,计算机视觉的主要任务及应用

目录 1. 什么是计算机视觉 2. 计算机视觉的主要任务及应用 2.1 图像分类 2.1.1 图像分类的主要流程 2.2 目标检测 2.2.1 目标检测的主要流程 2.3 图像分割 2.3.1 图像分割的主要流程 2.4 人脸识别 2.4.1 人脸识别的主要流程 对于我们人类来说&#xff0c;要想认出身边…

生成地图展示【Python思路】

# 1.导包 import json from pyecharts.charts import Map #导入关于编写地图的包 from pyechart.options import * #全局设置# 2.得到地图对象 map Map()# 3.打开事先准备好的JSON数据文件 f open("D:/Typora 记事本/notebook/Python/Exercise_data/疫情.txt",&…