前言
本文是自己阅读 Redis 相关文章和数据以及对 Redis 实际操作后的一些总结,旨在对学习过的 Redis 进行总结和复习。
由于本人水平有限,在讲述中可能会出现纰漏和错误,希望大家可以指出,一起学习,一起进步。
Redis 内置数据类型
Redis 内置的数据类型可以分为基本数据类型和高级数据类型。其中基本数据类型便是经常使用的5类:string
、list
、set
、zset
、hash
。
字符串的底层实现是字节,所以可以直接操作字符串的位。比如如果要记录某个人一年的打卡记录,那么不需要创建365个字符串,只需要创建一个365位的位图就可以了。
set 的底层实现是 hash,该 hash 中的每个 value 都是空值。zset 的底层实现是跳跃表 + 哈希,后面的
Redis 数据结构实现
会讲到这一部分。
对于队列的操作,如果没有元素读取,那么可以选择直接返回空,或者阻塞读。在阻塞读的时间内,Redis 可能因为长时间没有操作而关闭该连接,此时阻塞读会抛出异常。
阻塞写和阻塞读的操作是一样的。
对于 list、set、zset、hash,Redis 都是使用
如果没有则创建,如果没有元素则删除
的策略进行管理的。
渐进式 hash 会在 rehash 时,创建一个新的 hash 表。在查询时会先去新旧哈希表进行查找,如果在旧表找到了数据就会在返回数据前先将数据迁移到旧表。如果长时间没有查询操作,Redis 也会周期性将旧哈希表的数据迁移到新哈希表。
高级数据类型是对 Redis 在一些特殊场景下的使用,主要的结构有:HyperLogLog
、GeoHash
,Reboolm
, 现在对这两种结构进行简单的说明:
-
HyperLogLog 用于提供不精确的去重计数方案。它主要使用的场景为统计某大型网站或页面的 PV/UV。当网站的访问量达到数千万乃至亿级时,简单地使用
string
存储每个用户的访问信息对服务器的性能会造成很大的压力,HyperLogLog 就是用于处理这种情况的。关于它的算法理解,可以参考这篇博客:探索HyperLogLog算法(含Java实现) -
GeoHash 用于存储和处理地理信息。可以将某一地点的经纬度存储在该数据结构中,对应的操作有:
计算两个元素间的距离
、获取某一地点指定范围的其它地点
。GeoHash 的底层是用 zset 集合存储的,所以在数据迁移过程中,可能会因为 key 过大导致集群迁移出现卡断,因此对于 Geo 数据最好使用单独的 Redis 实例进行部署和拆分。 -
Rebloom 用于不精确地判断某一值是否存在于集合中。原理是通过 hash 值将对应位的 0 置为 1。那么如果要判断某个值是否存在,只需要看它 hash 值对应的位是否都为1就可以了,但是这样是不精确的,因为可能是其它的值占了该位,这也是为什么说它不精确。但是在某些不注重精确的场景下,布隆过滤器能起到很好的作用。
Reids 中可以设置布隆过滤器的相关参数:
- error_rate:误判率,该值越低,需要的空间就越大。
- initial_size:表示预计的元素数量,当实际数量超过这个值时,误判率会上升。
Redis 数据结构实现
因为 Redis 是运行在内存的数据库,所以对于数据类型的实现都要尽量地减少内存占用。Redis 主要的数据实现由以下结构,下面会对这些结构进行讲述:
- sds
- ziplist
- quicklist
- intset
- skiplist
在了解各结构前,我们要知道 Redis 每一个对象都是由一共 redisObject 表示,redisObject 的数据结构如下:
typedef struct redisObject {
unsigned type:4; // 对象的数据类型,对应 Redis 对外暴露的 5 种基本数据结构
unsigned encoding:4; // 对象的内部表示方式
unsigned lru:LRU_BITS; // 记录对象的 LRU 信息
int refcount; // 引用计数,Redis 自实现的垃圾回收算法
void *ptr; // 数据指针
} robj;
SDS
sds 全称为 Simple Dynamic String,即动态字符串,它的内部结构定义如下:
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 有效长度 */
uint16_t alloc; /* 总容量 */
unsigned char flags; /* 1byte,最低3位标识 header 类型 */
char buf[];
};
sds 一共有 5 种 header,分别是: