【学习笔记】《Redis深度历险》

花了三个晚上读完《Redis深度历险 – 核心原理与应用实践》

Redis在工作中大量使用到,但读完书仍有些收获,同时整理出了几道适合作为面试题的问题。并分几个模块进行总结。

书的优点是讲了一些4.x,5.0新版本的特性,实际线上应用的还停留在3.x。因此像stream等新特性暂未尝过鲜。

缺点是为了凑篇幅吧,相同的算法(如分布式锁)使用python和java分别实现了一遍。同时数据结构的描述深度也不够,如HLL算法实现原理没有详细讲解,仅一笔带过。

读书笔记分几个模块:

  1. 基本数据类型的底层实现
  2. 集群模式
  3. 应用实践
  4. Redis逻辑架构
  5. 一些有意思的问题讨论(如key过期,LFU算法)

一、基础数据结构

整个redis数据,可看作一个大的K/V,V又分多种类型:

string

list

hash

set

zset

bloom filter

bitmap

HyperLogLog

Geo

而这些基本数据类型,底层使用的数据结构并不复杂:

SDS(simple dynamic string),用于实现string存储:

struct SDS<T> {
  T capacity; //数组容量
  T len; // 数组长度
  byte flags;
  byte[] content; // 数据内容
}

capacity和len为什么使用泛型呢?因为在content长度很小的时候,T可以是short等,从而缩小内存占用。从这一个小点上,可以看到Redis对内存优化的极致。

capacity是因为redis的string是可修改的,支持append操作。冗余空间可减少内存分配和拷贝的开销。

冗余了一个len表示string长度,因为strlen(str)是一个O(n)的算法,len可以O(1)返回字符串长度。

List也非使用普通双向链表实现。其实现为,元素较少时,使用ziplist。元素较多时,使用多个ziplist串联起的quicklist

ziplist结构如下:

zlbytes|zltail_offset|zllength|entry|entry|entry|…|entry|slend|

可以看到,使用数组实现,省去了指标的开销。zltail_offset指向最后一个元素,用于快速定位到队尾,然后倒着遍历。

entry存放的内容不同,长度不一致。那么,倒着遍历如何定位到上一个entry呢?

struct entry {
  int<var> prevlen;
  int<var> encoding;
  optional byte[] content;
}

其中的prevlen代表前一个entry的大小,所以可以用它来找到前一个元素,倒着遍历;

quicklist是一个双向链表,其每一个node是一个ziplist。

注意,元素较少时,hash/set/zset等结构的实现,都是使用ziplist。当然若set中的元素都是int,则使用的是一个intset的结构,其专为int设计,因此更节省空间,redis处处有此类设计。

当set其中只要有一个元素不为int时,则intset退化成ziplist.

元素多时,hash和set都是使用跳表实现。使用跳表原因,也是空间占用方面考虑。哈希表效率高,但像golang loadFactor 6.5p空间效率极低。像rbtree也能实现O(lgn)的复杂度,但其平均每个节点需要额外2个指针,空间效率也低于跳表。

同时由于zset还需维护value与score的对应关系,因此实际是个skiplist+hash的组合实现。

bloom filter,HLL,bitmap没什么好讲,普通的实现。主要看下Geo。

Geo用于计算两个经纬度坐标之间的距离,它使用GeoHash算法将一个二维的经纬度,转换为一维的整数。在Redis进行Geo查询时,我们要时刻想到它的内部结构实际上是一个zset。其score为GeoHash算法算出的52位整数。通过score排序就可以得到坐标附近的元素,通过score还原为原始坐标即可找到附近的人。

二、一些应用实践

猜你喜欢

转载自www.cnblogs.com/gm-201705/p/12897939.html