本篇文章基于dis4.0.14
字符串(String)
1、String常用命令
set、mset、get、mget、incr(针对数字递增1)、incrby(递增N)setnx
2、String应用场景
(1)计数器
(2)集群环境下的session共享
(3)分布式锁
3、String内部实现
structRedisObject{int4type;//4bit对象类型REDIS_STRING0,REDIS_LIST1,REDIS_SET2,REDIS_ZSET3,REDIS_HASH4int4encoding;//4bit对象使用的编码int24lru;//24bitLRU信息int32fcount;//4bytes引用计数,当为0时,对象销毁,内存回收void*ptr;//8bytes,指向对象内容的具体存储位置}robj;
一个RedisObject对象头需要占据16字节的存储空间。
Redis的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。字符串最大长度为M。
Redis的字符串叫SDS,SimpleDynamicString。
struct__attribute__((__packed__))sdshdr8{uint8_tlen;/*used*/uint8_talloc;/*excludingtheheaderandnullterminator*/unsignedcharflags;/*3lsboftype,5unusedbits*/charbuf[];};
len表示buf中已占用空间的长度,alloc中表示buf中可剩余空间的长度,buf是数据空间。Redis的字符串有三种存储形式,如果一个字符串对象保存的是整数值,字符串对象的编码设置为INT。在长度比较短时,使用emb形式存储(embedded),当长度超过44字节,使用raw形式存储。
内存分配器jemalloc/tcmalloc等分配内存大小的单位都是2、4、8、16、32、64等等,为了能容纳一个完整的embstr对象,jemalloc最少会分配32字节的空间,如果字符串再稍微长一点,那就是64字节的空间。如果总体超出了64字节,Redis认为它是一个大字符串,不再使用emdstr形式存储,而该用raw形式。64-19-3-1=44。
存储的是正数时:
local:0setdemo"12""OK"local:0debugobjectdemo"Valueat:0xfcount:encoding:intserializedlength:2lru:lru_seconds_idle:12"
存储字符串时,长度不大于44字节
local:0setdemo"abcdefghijklmnopqrstuvwxyz""OK"local:0debugobjectdemo"Valueat:0xfe8fcount:1encoding:embstrserializedlength:45lru:lru_seconds_idle:3"
存储字符创大于44字节时
local:0setdemo"abcdefghijklmnopqrstuvwxyz9""OK"local:0debugobjectdemo"Valueat:0xb88fcount:1encoding:rawserializedlength:46lru:lru_seconds_idle:2"
4、bitmap
SETBITkeyoffsetvalue#对key所储存的字符串值,设置或清除指定偏移量上的位(bit)
a的ASCII码是97。转换为二进制是:,这里offset0=‘0’,offset1=1,offset2=1,offset6=0,offset是从左往右计数的,也就是从高位往低位。通过setbit命令把a改为b,b的ascii码98=,,也就是把a的offset6设置为1,offset7设置为0即可。
bitmap本身offset的限制就是0到2^32,内存限制为MB,刚刚好是2^32个bit,也就是,也就是说,offset最大能去到-1去。有四十二亿。
哈希(Hash)
1、常用命令
hsetkeyfieldvaluehgetkeyfieldhgetallkeyhmsetkeyfieldvalue…hincrbykeyfieldincment
2、应用场景
(1)购物车hmsetuserId:prod:1prod:用户id为key,商品id为field,商品数量为value(2)即时通信的未读消息。以用户id为key,好友或者群id为field,未读消息为value
3、内部实现
哈希对象的编码可以是ziplist(压缩列表)或者hashtable(哈希表)
ziplist(压缩列表)
压缩列表不是基础数据结构,而是Redis自己设计的一种数据存储结构。有点儿类似数组,通过一片连续的内存空间,来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。
ziplist数据结构
structziplistT{int32zlbytes;//整个压缩列表占用字节数int32zltail;//最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点int16zllen;//ziplist的节点(entry)个数T[]entries;//元素内容列表,挨个挨个紧凑存储int8zlend;//标志压缩列表的结束,值恒为0xFF}
entry块随着容纳的元素类型不同,也会有不一样的结构。
structentry{int?pvlen;//前一个entry的字节长度int?encoding;//元素类型编码optionalbyte[]content;//元素内容,可以是数字或字符串}
pvlen字段表示前一个entry的字节长度,当压缩列表倒着遍历时,需要通过这个字段来快速定位到下一个元素的位置。它是一个变长的整数,当字符串长度小于(0xFE)时,使用一个字节表示;如果达到或超出(0xFE)那就使用5个字节来表示。
Redis使用压缩列表来实现字典类型。需要同时满足两个条件:
当哈希类型元素个数小于hash-max-ziplist-entries配置(默认个)所有值都小于hash-max-ziplist-value配置(默认64字节)
hashtable(哈希表)
Redis的字典相当于Java语言里面的HashMap,它是无序字典。内部实现结构上同Java的HashMap也是一致的,同样的数组+链表二维结构。第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
(1)开放寻址法;(2)链表法
typedefstructdict{dictType*type;void*privdata;dicththt[2];longhashidx;/*hashingnotinprogssifhashidx==-1*/unsignedlongiterators;/*numberofiteratorscurntlyrunning*/}dict;
ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会对ht[0]哈希表进行hash时使用,渐进式hash,hash算法使用的是SipHash。
列表(List)
Redis的列表相当于Java语言里面的LinkedList
1、常用命令
LPUSHkeyvalue[value...]#列表头部插入数据LRANGEkeystartstop#返回指定范围内的元素LINSERTkeyBEFORE
AFTERpivotvalue#在基准值pivot之前/后插入valueLPOPkey#移除并且返回key对应的list的第一个元素BLPOPkey[key...]timeout#阻塞式LTRIMkeystartstop#让列表只保留指定区间内的元素
2、应用场景
list可以充当栈和队列的角色。
lpush+lpop=Stack(栈)lpush+rpop=Queue(队列)lpush+ltrim=CappedCollection(有限集合)lpush+brpop=MessageQueue(消息队列)
(1)消息队列
(2)文章、商品列表
a、文章使用哈希结构存储hmsetacticle:1titlexxtimestampcontentxxxx...hmsetacticle:ktitleyytimestamp1476536contentyyyy...b、用户文章列表user:{id}:articles作为用户文章列表的键lpushuser:1:acticlesarticle:1article:kc、分页查询articles=lrangeuser:1:articles09forarticleinarticles{hgetall{article}}
3、内部实现
在dis3.2版本之前list类型内部编码有2种
ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
linkedlist
typedefstructlistNode{structlistNode*pv;//前置节点structlistNode*next;//后置节点void*value;//节点的值}listNode;typedefstructlist{//表头节点listNode*head;//表尾节点listNode*tail;//链表所包含的节点数量unsignedlonglen;}list;
quicklist
而在dis3.2版本开始对列表数据结构进行了改造,使用quicklist代替了ziplist和linkedlist。quicklist是ziplist和linkedlist的混合体,它将linkedList按段切分,每一段使用zipList来紧凑存储,多个zipList之间使用双向指针串接起来。
typedefstructquicklist{quicklistNode*head;//指向头节点的指针quicklistNode*tail;//指向尾节点的指针unsignedlongcount;//所有ziplists中entries的个数总和unsignedlonglen;//quicklist节点个数intfill:QL_FILL_BITS;//16bit,ziplist大小设置,存放list-max-ziplist-size参数的值unsignedint