【Redis 系列】redis 学习十六,redis 字典(map) 及其核心编码结构
redis 是使用 C 语言编写的,但是 C 语言是没有字典这个数据结构的,因此 C 语言自己使用结构体来自定义一个字典结构
typedef struct redisDb
src\server.h 中的 redis 数据库 数据结构
/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
redisDb 存放了 redis 数据库底层的数据结构:
- dict
字典类型
- expires
过期时间
- blocking_keys
客户端等待数据的键 (BLPOP)
- ready_keys
收到PUSH的键被阻塞
- watched_keys
监控 MULTI/EXEC CAS 的键,例如事务的时候就会使用到
- id
数据库的 id, 0 – 15
- avg_ttl
统计平均的 ttl
- expires_cursor
记录过期周期
- defrag_later
存放 key 的列表
typedef struct dict
src\dict.h 字典的数据结构
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;
dict 存放字典的数据结构
- type
字典的类型
- privdata
私有数据
- ht
hash 表, 一个旧表,一个新表,是有当 hash 表扩容的时候,新表才会被使用到,也就是 ht[1]
typedef struct dictType
typedef struct dictType {
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
int (*expandAllowed)(size_t moreMem, double usedRatio);
} dictType;
dictType 定义了多个函数指针,便于后续进行方法的实现和调用
例如 keyCompare 函数指针,他是一个指针,指向的是一个函数,这个函数有 3 个参数,和 1 个返回值:
3 个参数
- privdata
具体的数据
- key1
key1 这个键具体的值
- key2
key2 这个键具体的值
这个指针 keyCompare 指向的函数作用是比较两个 key 的大小
typedef struct dictht
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht 存放的是 hash 表使用到的数据结构
- table
实际的 key-value 键值对
- size
hashtable 的容量
- sizemask
等于 size -1
- used
hashtable 元素的个数
typedef struct dictEntry
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
dictEntry 为键值对的实际数据结构
- key
key 值,实际上是一个 sds 类型的
- v
value 值,是一个联合体
- next
dictEntry 指针,指向下一个数据,主要是解决 hash 冲突的
例如上一篇我们介绍到的 hash,如下图中,key 就是 1,v 就是 (k3,v3) ,next 指向的就是 (k2,v2),一般默认情况 next 指向 NULL
上述联合体 v ,里面第 1 个元素是, void *val;
实际上这个元素才是指向真正的值,这个元素是一个指针,实际的数据结构是这个样子的
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
- type
类型,占 4 个 bit ,是用来约束客户端 api 的,例如 string 类型,embstr,hash,zset 等等
- encoding
编码类型,占 4 个bit ,使用到的数字有 0 - 10,分别表示不同的数据类型
- lru
lru 占 24 个bit ,3 个字节 , 内存淘汰算法
- refcount
引用计数 , int 类型,占 4 个字节
- ptr
实际的数据指针 , 64 位操作系统中, ptr 占 8个字节
bitmap 的小案例
设置一个 bitmap 的 key,作用为标记 11 号的在线用户
127.0.0.1:6379> SETBIT login:9:11 25 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:11 26 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:11 27 1
(integer) 0
127.0.0.1:6379> BITCOUNT login:9:11
(integer) 3
127.0.0.1:6379> strlen login:9:11
(integer) 4
- BITCOUNT key [start end]
通过 BITCOUNT 可以看出 11 号在线人数 3 个人,login:9:11 占用字节数位 4 字节
127.0.0.1:6379> SETBIT login:9:12 26 1
(integer) 0
127.0.0.1:6379> SETBIT login:9:12 25 0
(integer) 0
127.0.0.1:6379> SETBIT login:9:12 27 1
(integer) 0
127.0.0.1:6379> STRLEN login:9:12
(integer) 4
通过 BITCOUNT 可以看出 12 号在线人数 2 个人,login:9:12 占用字节数位 4 字节
下面我们将取 login:9:11 和 login:9:12 的 与操作,来计算 11 号 和 12 号两天来都在线的人数
127.0.0.1:6379> BITOP and login:and login:9:11 login:9:12
(integer) 4
127.0.0.1:6379> BITCOUNT login:and
(integer) 2
- BITOP operation destkey key [key ...]
根据上述结果我们可以看出,11 号 和 12 号两天来都在线的人数为 2 人,验证 ok
我们再来看看11 号 和 12 号任意一天在线的人数
127.0.0.1:6379> BITOP or login:or login:9:11 login:9:12
(integer) 4
127.0.0.1:6379> BITCOUNT login:or
(integer) 3
根据上述结果我们可以看出,11 号 和 12 号任意一天在线的人数为 3 人,验证 ok
127.0.0.1:6379> type login:or
string
127.0.0.1:6379> OBJECT encoding login:or
"raw"
127.0.0.1:6379> OBJECT encoding login:9:12
"raw"
127.0.0.1:6379> OBJECT encoding login:and
"raw"
咱们来看看上述用到的 key ,在 redis 里面实际是什么数据类型吧,
- OBJECT encoding [arguments [arguments ...]]
可以看出上述都是 “raw” 类型, 也就是 redis 的 sds 类型
缓存行
咱们再来看一个小例子,redis 中设置一个字符串 key
127.0.0.1:6379> set name xiaoming
OK
127.0.0.1:6379> OBJECT encoding name
"embstr"
我们可以看出 name 的类型是 “embstr”,那么 “embstr” 底层是如何实现的呢?“embstr” 有能承载多少个字节的数据呢?
上述我们有说到 redis 里面存放键值对的地方在 dictEntry 结构体中,dictEntry 结构体中的val 指针指向的是一个 redisObject 结构体,是这样的
我们在一个 64 位的机器中,CPU 在内存中读取数据的是通过读取缓存行的方式来实现的
一个缓存行有 64 字节
一个 redisObject 结构体占 16 字节
那么就还剩 48 字节 可以使用,那么使用 redis 里面 哪一个 sds 数据结构来存放数据数据呢?
使用 hisdshdr8 类型,hisdshdr8 类型 sds 的前 3 个元素占用 3 个字节,那么剩下的 buf 存放数据就可以存放 45个字节(64 - 16 - 3)的数据了
如果你这么认为了,那么就有点粗心哦,因为 redis 为了兼容 C 语言的标准,会在字符串的后面加上 1 个 ‘\0’ ,他是占一个字节的因此最终“embstr” 实际能存放的字节数是:
44 字节
来回顾上一篇文章,可以看出
当数据占用空间在 0 - - 2^5-1 , 使用 hisdshdr5 数据类型
2^5 – 2^8-1 的占用空间的时候,使用 hisdshdr8 数据类型
小小的实践
我们在 redis 中设置一个 test 的值为一个 44字节 的内容,查看这个 key 的类型,是 embstr
127.0.0.1:6379> set test 99999999991111111111222222222233333333334444
OK
127.0.0.1:6379> OBJECT encoding test
"embstr"
127.0.0.1:6379> STRLEN test
(integer) 44
再来设置 test2 为 大于 44 字节的内容,再查看他的内容是 raw
127.0.0.1:6379> set test2 999999999911111111112222222222333333333344449
OK
127.0.0.1:6379> OBJECT encoding test2
"raw"
最后送上一张上述数据结构的关系图
参考资料:
reids 源码 reids-6.2.5 Redis 6.2.5 is the latest stable version.
欢-迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~
【Redis 系列】redis 学习十六,redis 字典(map) 及其核心编码结构的更多相关文章
- Redis进阶实践之十六 Redis大批量增加数据
一.介绍 有时,Redis实例需要在很短的时间内加载大量先前存在或用户生成的数据,以便尽可能快地创建数百万个键.这就是所谓的批量插入,本文档的目标是提供有关如何以尽可能快的速度向Redis提 ...
- Redis系列(二):Redis高可用集群
一.集群模式 Redis集群是一个由多个主从节点组成的高可用集群,它具有复制.高可用和分片等特性 二.集群部署 1.环境 3台主机分别是: 192.168.160.146 192.168.160.15 ...
- Redis系列(三):Redis集群的水平扩展与伸缩
一.Redis集群的水平扩展 Redis3.0版本以后,有了集群的功能,提供了比之前版本的哨兵模式更高的性能与可用性,但是集群的水平扩展却比较麻烦,接下来介绍下Redis高可用集群如何做水平扩展,在原 ...
- Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis
在 Redis 出现之前,我们的缓存框架各种各样,有了 Redis ,缓存方案基本上都统一了,关于 Redis,松哥之前有一个系列教程,尚不了解 Redis 的小伙伴可以参考这个教程: Redis 教 ...
- Redis系列之key操作命令与Redis中的事务详解(六)
序言 本篇主要目的有二: 1.展示所有数据类型中key的所有操作命令,以供大家学习,查阅,更深入的挖掘redis潜力. 2.掌握redis中的事务,让你的数据完整性一致性拥有更优的保障. redis命 ...
- redis系列之5----redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
- Redis进阶实践之十九 Redis如何使用lua脚本
一.引言 redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入 ...
- Redis系列(一):Redis简介
一.Redis概述 Redis是一个开源(遵循BSD协议)Key-Value数据结构的内存存储系统,用作数据库.缓存和消息代理.它支持5种数据结构:字符串string.哈希hash.列表list.集合 ...
- Redis系列(五):Redis的RESP协议详解
一.什么是RESP Redis是Redis序列化协议,Redis客户端RESP协议与Redis服务器通信.Redis协议在以下几点之间做出了折衷: 简单的实现 快速地被计算机解析 简单得可以能被人工解 ...
随机推荐
- linux修改静态ip
1.修改配置文件 vi /etc/sysconfig/network-scripts/ifcfg-ens32 bootproto:设置为静态 onboot:开机自启 ipaddr:ip地址 netma ...
- Data详细解析
- css兼容问题集锦
BEGIN; 1.文本框很大,导致里面的内容不居中.以及内容为数字时,不支持text-indent属性 解:line-height: K px; (值为文本框的height值). 2.文本框有背景图片 ...
- vulnhub DC:1渗透笔记
DC:1渗透笔记 靶机下载地址:https://www.vulnhub.com/entry/dc-1,292/ kali ip地址 信息收集 首先扫描一下靶机ip地址 nmap -sP 192.168 ...
- js字符串操作方法集合
1.字符方法: str.charAt(): 可以访问字符串中特定的字符,可以接受0至字符串长度-1的数字作为参数,返回该位置下的字符,如果参数超出该范围,返回空字符串,如果没有参数,返回位置为0的字符 ...
- 源码解析Synchronous Queue 这种特立独行的队列
摘要:Synchronous Queue 是一种特立独行的队列,其本身是没有容量的,比如调用者放一个数据到队列中,调用者是不能够立马返回的,调用者必须等待别人把我放进去的数据消费掉了,才能够返回. 本 ...
- /proc/meminfo 解释
- Docker从入门到放弃(1) Docker简介与安装
目录 一.Docker简介 1.Docker是什么: 2.为什么有docke的出现: 3.docker与传统容器的区别: 4.docker基本组成 5.docker工作原理: 二.Docker安装 ...
- HandlerMethodArgumentResolver 自定义使用
HandlerMethodArgumentResolver 自定义使用 1.HandlerMethodArgumentResolver 的应用场景 HandlerMethodArgumentRes ...
- drools的简单入门案例
一.背景 最近在学习规则引擎drools,此处简单记录一下drools的入门案例. 二.为什么要学习drools 假设我们存在如下场景: 在我们到商店购买衣服的时候,经常会发生这样的事情,购买1件不打 ...