zoukankan      html  css  js  c++  java
  • 【学习笔记】《Redis深度历险》

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

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

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

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

    读书笔记分几个模块:

    1. 基本数据类型
    2. Redis逻辑架构
    3. 集群模式
    4. 一些有意思的问题讨论(如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都是使用hash实现。zset使用跳表原因,一是空间占用方面考虑,二是要支持range查询。哈希表效率高,但像golang LoadFactor 6.5空间效率极低。像rbtree也能实现O(lgn)的复杂度并支持range查询,但其平均每个节点需要额外2个指针,空间效率也低于跳表。

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

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

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

    二、Redis逻辑架构

     一个问题:为什么Redis命令处理是单线程模型,但却qps这么高?

    1. 非阻塞I/O

    非阻塞I/O有个问题,就是线程要读数据,结果读了一部分就返回了,那么进程如何知道何时继续读呢?

    2. 多路复用API就用来解决这个问题,包括select/pool/epool等。

    第二个问题,快照涉及到磁盘I/O,是非常慢的,Redis怎么处理的?

    redis快照其实是fork一个子进程,在子进程中做dump到磁盘操作。因为操作系统的COW(copy on write),父进程的写入也不会影响子进程的快照过程。

    三、集群模式

    1. sentinel解决单点问题,可以实现主备的自动切换。

    2. codis vs. redis cluster

    redis cluster是P2P的架构,类似于cassandra对数据分区的处理。codis依赖一个zk或etcd的中心,记redis实例与slot的对应关系。

    codis有层codis proxy,后面是一堆的redis实例。

    由于这种模型,一个事务中多个key时,就不支持事务了,因为多个command可能分到多个redis实例上面。这就要我们对key设置hashtag,同理pfmerge,pfcount等功能在codis也要设置hashtag,否则数据将不正确。

    四、讨论一些有意思的问题

    1)hash的扩容

    redis hash扩容过程与Golang基本一致,也使用的是渐进式扩容的方式;

    2)key过期实现

    惰性删除,即访问时判断过期则删除 + 定时删除,1秒25次扫描。使用了贪心算法,如果一次扫描,过期key占比大,则进行第二次。但为防止循环,限制了次数。

    3)lru vs. lfu

  • 相关阅读:
    正则表达式
    python最新版mplfinance中文乱码解决方法
    Python学习笔记:pandas.read_csv分块读取大文件(chunksize、iterator=True)
    pandas,读取或存储DataFrames的数据到mysql中
    BaoStock:使用python的baostock接口,查询季频盈利能力
    win10解决Mysql net start mysql启动,提示发生系统错误 5 拒绝访问
    mysql5.7 本地计算机上的mysql 服务启动后停止 的问题
    获取A股所有的股票代码
    phpcms <= v9.15 任意文件读取漏洞的分析和利用
    ubuntu系统安装搜狗法并设置不能切换的问题解决
  • 原文地址:https://www.cnblogs.com/gm-201705/p/12897939.html
Copyright © 2011-2022 走看看