|
概述
Redis 是一个开源的高性能键值数据库,它支持多种数据类型,可以满足不同的业务需求。本文将介绍 Redis 的10种数据类型,分别是
- string(字符串)
- hash(哈希)
- list(列表)
- set(集合)
- zset(有序集合)
- stream(流)
- geospatial(地理)
- bitmap(位图)
- bitfield(位域)
- hyperloglog(基数统计)
String
概述
string 是 Redis 最基本的数据类型,它可以存储任意类型的数据,比如文本、数字、图片或者序列化的对象。一个 string 类型的键最大可以存储 512 MB 的数据。
string 类型的底层实现是 SDS(simple dynamic string),它是一个动态字符串结构,由长度、空闲空间和字节数组三部分组成。SDS有3种编码类型:
- embstr:占用64Bytes的空间,存储44Bytes的数据
- raw:存储大于44Bytes的数据
- int:存储整数类型
embstr和raw存储字符串数据,int存储整型数据
应用场景
string 类型的应用场景非常广泛,比如:
- 缓存数据,提高访问速度和降低数据库压力。
- 计数器,利用 incr 和 decr 命令实现原子性的加减操作。
- 分布式锁,利用 setnx 命令实现互斥访问。
- 限流,利用 expire 命令实现时间窗口内的访问控制。
底层原理
embstr结构
embstr 结构存储小于等于44个字节的字符串,embstr 每次开辟64个byte的空间
- 前19个byte用于存储embstr 结构
- 中间的44个byte存储数据
- 最后为\0符号
raw结构
embstr和raw的转换
在存储字符串的时候,redis会根据数据的长度判断使用哪种结构
- 如果长度小于等于44个字节,就会选择embstr 结构
- 如果长度大于44个byte,就会选择raw结构
- 127.0.0.1:6379> object encoding str
- "embstr"
- # str赋值44个字节的字符串
- 127.0.0.1:6379> set str 1234567890123456789012345678901234567890abcd
- OK
- 127.0.0.1:6379> object encoding str
- "embstr"
- # str2赋值45个字节的字符串
- 127.0.0.1:6379> set str2 1234567890123456789012345678901234567890abcde
- OK
- 127.0.0.1:6379> object encoding str2
- "raw"
- 127.0.0.1:6379> set num 123
- OK
- 127.0.0.1:6379> object encoding num
- "int"
复制代码 Hash
概述
hash 是一个键值对集合,它可以存储多个字段和值,类似于编程语言中的 map 对象。一个 hash 类型的键最多可以存储 2^32 - 1 个字段。
Hash类型的底层实现有三种:
- ziplist:压缩列表,当 **hash **达到一定的阈值时,会自动转换为hashtable结构
- listpack:紧凑列表,在Redis7.0之后,listpack正式取代ziplist。同样的,当 hash达到一定的阈值时,会自动转换为hashtable结构
- hashtable:哈希表,类似map
应用场景
hash 类型的应用场景主要是存储对象,比如:
- 用户信息,利用 hset 和 hget 命令实现对象属性的增删改查。
- 购物车,利用 hincrby 命令实现商品数量的增减。
- 配置信息,利用 hmset 和 hmget 命令实现批量设置和获取配置项。
底层原理
Redis在存储hash结构的数据,为了达到内存和性能的平衡,也针对少量存储和大量存储分别设计了两种结构,分别为:
- ziplist(redis7.0之前使用)和listpack(redis7.0之后使用)
- hashTable
从ziplist/listpack编码转换为hashTable编码是通过判断元素数量或单个元素Key或Value的长度决定的:
- hash-max-ziplist-entries:表示当 **hash **中的元素数量小于或等于该值时,使用 **ziplist **编码,否则使用 **hashtable 编码。ziplist 是一种压缩列表,它可以节省内存空间,但是访问速度较慢。hashtable **是一种哈希表,它可以提高访问速度,但是占用内存空间较多。默认值为 512。
- hash-max-ziplist-value:表示当 **hash **中的每个元素的 key 和 value 的长度都小于或等于该值时,使用 **ziplist **编码,否则使用 **hashtable **编码。默认值为 64。
ziplist与listpack
ziplist/listpack都是hash结构用来存储少量数据的结构。从Redis7.0后,hash默认使用**ziplist **结构。因为 ziplist 有一个致命的缺陷,就是连锁更新,当一个节点的长度发生变化时,可能会导致后续所有节点的长度字段都要重新编码,这会造成极差的性能
ziplist结构
ziplist是一种紧凑的链表结构,它将所有的字段和值顺序地存储在一块连续的内存中。
Redis中ziplist源码- typedef struct {
- /* 当使用字符串时,slen表示为字符串长度 */
- unsigned char *sval;
- unsigned int slen;
- /* 当使用整形时,sval为NULL,lval为ziplistEntry的value */
- long long lval;
- } ziplistEntry;
复制代码 listpack结构
zipList的连锁更新问题
ziplist的每个entry都包含previous_entry_length来记录上一个节点的大小,长度是1个或5个byte:
- 如果前一节点的长度小于254个byte,则采用1个byte来保存这个长度值
- 如果前一节点的长度大于等于254个byte,则采用5个byte来保存这个长度值,第一个byte为0xfe,后四个byte才是真实长度数据
假设,现有有N个连续、长度为250~253个byte的entry,因此entry的previous_entry_length属性占用1个btye
当第一节长度大于等于254个bytes,导致第二节previous_entry_length变为5个bytes,第二节的长度由250变为254。而第二节长度的增加必然会影响第三节的previous_entry_length。ziplist这种特殊套娃的情况下产生的连续多次空间扩展操作成为连锁更新。新增、删除都可能导致连锁更新的产生。
listpack是如何解决的
- 由于ziplist需要倒着遍历,所以需要用previous_entry_length记录前一个entry的长度。而listpack可以通过total_bytes和end计算出来。所以previous_entry_length不需要了。
- listpack 的设计彻底消灭了 ziplist 存在的级联更新行为,元素与元素之间完全独立,不会因为一个元素的长度变长就导致后续的元素内容会受到影响。
- 与ziplist做对比的话,牺牲了内存使用率,避免了连锁更新的情况。从代码复杂度上看,listpack相对ziplist简单很多,再把增删改统一做处理,从listpack的代码实现上看,极简且高效。
hashTable
hashTable是一种散列表结构,它将字段和值分别存储在两个数组中,并通过哈希函数计算字段在数组中的索引
Redis中hashTable源码- struct dict {
- dictType *type;
- dictEntry **ht_table[2];
- unsigned long ht_used[2];
- long rehashidx; /* 当进行rehash时,rehashidx为-1 */
- int16_t pauserehash; /* 如果rehash暂停,pauserehash则大于0,(小于0表示代码错误)*/
- signed char ht_size_exp[2]; /* 哈希桶的个数(size = 1<<exp) */
- };
- typedef struct dict {
- dictEntry **table;
- dictType *type;
- unsigned long size;
- unsigned long sizemask;
- unsigned long used;
- void *privdata;
- } dict;
- typedef struct dictEntry {
- void *key;
- void *val;
- struct dictEntry *next;
- } dictEntry;
复制代码 ziplist的废弃
显然是ziplist在field个数太大、key太长、value太长三者有其一的时候会有以下问题:
- ziplist每次插入都有开辟空间,连续的
- 查询的时候,需要从头开始计算,查询速度变慢
hashTable变得越来越长怎么办
扩容,hashTabel是怎么扩容的?使用的是渐进式扩容rehash。rehash会重新计算哈希值,且将哈希桶的容量扩大。
rehash 步骤
扩展哈希和收缩哈希都是通过执行rehash来完成,这其中就涉及到了空间的分配和释放,主要经过以下五步:
- 为字典dict的ht[1]哈希表分配空间,其大小取决于当前哈希表已保存节点数(即:ht[0].used):
- 如果是扩展操作则ht[1]的大小为2的n次方中第一个大于等于ht[0].used * 2属性的值(比如used=3,此时ht[0].used * 2=6,故2的3次方为8就是第一个大于used * 2的值(2 的 2 次方 6))。
- 如果是收缩操作则ht[1]大小为 2 的 n 次方中第一个大于等于ht[0].used的值
- 将字典中的属性rehashidx的值设置为0,表示正在执行rehash操作
- 将ht[0]中所有的键值对依次重新计算哈希值,并放到ht[1]数组对应位置,每完成一个键值对的rehash之后rehashidx的值需要自增1
- 当ht[0]中所有的键值对都迁移到ht[1]之后,释放ht[0],并将ht[1]修改为ht[0],然后再创建一个新的ht[1]数组,为下一次rehash做准备
- 将字典中的属性rehashidx设置为-1,表示此次rehash操作结束,等待下一次rehash
渐进式 rehash
Redis中的这种重新哈希的操作因为不是一次性全部rehash,而是分多次来慢慢的将ht[0]中的键值对rehash到ht[1],故而这种操作也称之为渐进式rehash。渐进式rehash可以避免集中式rehash带来的庞大计算量,是一种分而治之的思想。
在渐进式rehash过程中,因为还可能会有新的键值对存进来,此时Redis的做法是新添加的键值对统一放入ht[1]中,这样就确保了ht[0]键值对的数量只会减少。
当正在执行rehash操作时,如果服务器收到来自客户端的命令请求操作,则会先查询ht[0],查找不到结果再到ht[1]中查询
List
概述
list 是一个有序的字符串列表,它按照插入顺序排序,并且支持在两端插入或删除元素。一个 list 类型的键最多可以存储 2^32 - 1 个元素。
redis3.2以后,list 类型的底层实现只有一种结构,就是quicklist。版本不同时,底层实现是不同的,下面会讲解。
应用场景
list 类型的应用场景主要是实现队列和栈,比如:
- 消息队列,利用 lpush 和 rpop 命令实现生产者消费者模式。
- 最新消息,利用 lpush 和 ltrim 命令实现固定长度的时间线。
- 历史记录,利用 lpush 和 lrange 命令实现浏览记录或者搜索记录。
底层原理
在讲解list结构之前,需要先说明一下list结构编码的更替,如下
- 在Redis3.2之前,list使用的是linkedlist和ziplist
- 在Redis3.2~Redis7.0之间,list使用的是quickList,是linkedlist和ziplist的结合
- 在Redis7.0之后,list使用的也是quickList,只不过将ziplist转为listpack,它是listpack、linkedlist结合版
linkedlist与ziplist
在Redis3.2之前,linkedlist和ziplist两种编码可以选择切换,它们之间的转换关系如图
同样地,ziplist转为linkedlist的条件可在redis.conf配置- 127.0.0.1:6379> hset h1 id 123456789012345678901234567890123456789012345678901234567890abcd
- (integer) 1
- 127.0.0.1:6379> object encoding h1
- "ziplist"
- 127.0.0.1:6379> hset h2 id 123456789012345678901234567890123456789012345678901234567890abcde
- (integer) 1
- 127.0.0.1:6379> object encoding h2
- "hashtable"
复制代码 quickList(ziplist、linkedlist结合版)
quicklist存储了一个双向列表,每个列表的节点是一个ziplist,所以实际上quicklist并不是一个新的数据结构,它就是linkedlist和ziplist的结合,然后被命名为快速列表。
ziplist内部entry个数可在redis.conf配置
[code]list-max-ziplist-size -2# -5: 每个ziplist最多为 64 kb |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|