如何用更少的内存保存更多的数据?
我们应该从 Redis 是如何保存数据的原理展开,分析键值对的存储结构和原理。
从而继续延展出每种数据类型底层的数据结构,针对不同场景使用更恰当的数据结构和编码实现更少的内存占用。
为了保存数据, Redis 需要先申请内存,数据过期或者内存淘汰需要回收内存,从而拓展出内存碎片优化。
最后,说下 key、value 使用规范和技巧、 Bitmap 等高阶数据类型,运用这些技巧巧妙解决有限内存去存储更多数据难题……
这一套组合拳下来直接封神。
主要优化神技如下:
- 键值对优化;
- 小数据集合的编码优化;
- 使用对象共享池;
- 使用 Bit 比特位或 byte 级别操作
- 使用 hash 类型优化;
- 内存碎片优化;
- 使用 32 位的 Redis。
在优化之前,我们先掌握 Redis 是如何存储数据的。
Redis 如何存储键值对
Redis 以 redisDb
为中心存储,redis 7.0 源码在 https://github.com/redis/redis/blob/7.0/src/server.h
:
- dict:最重要的属性之一,就是靠这个定义了保存了对象数据键值对,dcit 的底层结构是一个哈希表。
- expires:保存着所有 key 的过期信息.
- blocking_keys 和 ready_keys 主要为了实现 BLPOP 等阻塞命令
- watched_keys用于实现watch命令,记录正在被watch的一些key,与事务相关。
- id 为当前数据库的id,redis 支持单个服务多数据库,默认有16个;
- clusterSlotToKeyMapping:cluster 模式下,存储key 与哈希槽映射关系的数组。
Redis 使用「dict」结构来保存所有的键值对(key-value)数据,这是一个全局哈希表,所以对 key 的查询能以 O(1) 时间得到。
所谓哈希表,我们可以类比 Java 中的 HashMap
,其实就是一个数组,数组的每个元素叫做哈希桶。
dict 结构如下,源码在 https://github.com/redis/redis/blob/7.0/src/dict.h
:
- dictType:存储了hash函数,key和value的复制等函数;
- ht_table:长度为 2 的 数组,正常情况使用 ht_table[0] 存储数据,当执行 rehash 的时候,使用 ht_table[1] 配合完成 。
key 的哈希值最终会映射到 ht_table 的一个位置,如果发生哈希冲突,则拉出一个哈希链表。
大家重点关注 dictEntry
的 ht_table,ht_table 数组每个位置我们也叫做哈希桶,就是这玩意保存了所有键值对。
码哥,Redis 支持那么多的数据类型,哈希桶咋保存?
哈希桶的每个元素的结构由 dictEntry 定义:
typedef struct dictEntry {
// 指向 key 的指针
void *key;
union {
// 指向实际 value 的指针
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 哈希冲突拉出的链表
struct dictEntry *next;
} dictEntry;
- key 指向键值对的键的指针,key 都是 string 类型。
- value 是个 union(联合体)当它的值是 uint64_t、int64_t 或 double 类型时,就不再需要额外的存储,这有利于减少内存碎片。(为了节省内存操碎了心)当然,val 也可以是 void 指针,指向值的指针,以便能存储任何类型的数据。
- next 指向另一个 dictEntry 结构, 多个 dictEntry 可以通过 next 指针串连成链表, 从这里可以看出, ht_table 使用链地址法来处理键碰撞: 当多个不同的键拥有相同的哈希值时,哈希表用一个链表将这些键连接起来。 <