Redis design ideas and usage specifications

Redis design ideas and usage specifications

B433b981e15d8f2373df4363352434f9?s=128

halfrost

June 18, 2019
Tweet

Transcript

  1. Redis 设计思想与使用规范

  2. 内容大纲 一. 数据结构 二. 单机数据库实现 三. 多机数据库实现 四. 使用规范及常见问题 五.

    Redis的其他功能
  3. Redis初识 高性能Key-Value存储系统 多种数据结构 丰富的功能 高可用分布式支持 多语言客户端支持 支持持久化

  4. 数据结构 Redis使用并不是C语言中的字符串,而是使用自己构建的简单动态字符串(SDS) 简单动态字符串 C字符串 SDS 获取字符长度时间复杂度O(N) 获取字符长度时间复杂度O(1) API不安全,可能造成缓冲区溢出 API安全,不会造成缓冲区溢出 修改字符串长度N次必然需要执行N次内存重分配

    修改字符串长度N次至多需要执行N次内存重分配 只能保存文本数据 可以保存文本和二进制数据
  5. 数据结构 链表提供了高效的节点重排能力,以及顺序性的节点访问方式。 链表 Redis的链表被广泛使用,比如列表键,发布订阅,慢查询,监听器 通过为链表设置不同类型特定函数,Redis链表可以用于保存各种不同类型的值

  6. 数据结构 字典又叫关联数组,映射,是一种用来保存键值对(key-value pair)的抽象数据结构 字典 Redis的数据库就是的底层实现,对数据库的增删改查操作也是建立在对字典的操作 字典也是哈希键的底层实现之一,当哈希键包含的键值对比较多,或者键值对中元素是比较长的字符串时, Redis会使用字典作为哈希键的底层实现。 字典的实现是由哈希表定义的

  7. 数据结构 字典

  8. 数据结构 哈希算法:当一个新的键值对添加到字典里面时,程序会根据键计算出哈希值和索引值,然后根据索引值,将键值对 的哈希表节点放到哈希表数组的指定索引上去。 键冲突解决:当两个或以上key分配哈希数组的同一个索引上面,Redis的哈希表使用链地址法(separate chaining) 来解决冲突,每个哈希节点都有一个next指针,被分配到同一个索引的多个节点构成单向链表,就解决了键哈希冲突 的问题。 rehash:随着操作变化,哈希表内的键值对会不断增多或减少,需要将哈希表的负载因子(load factor)维持在一 个合理的范围内,需要对哈希表的大小进行相应扩展或收缩。load_factor=ht[0].used

    / ht[1].size 自动rehash触发条件: 1. 服务器没有执行bgsave或者bgrewriteaof命令,负载因子大于等于1 2. 服务器正在执行bgsave或者bgrewriteaof命令,负载因子大于等于5 字典
  9. 数据结构 Redis对字典的哈希表执行rehash步骤: 1. 为ht[1]哈希表分配空间,大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(ht[0].userd) a. 如果执行扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used * 2 的2n b.

    如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2n 2. 将保存在ht[0]上的键值对,rehash到ht[1]上面,即重新计算键值哈希即索引,放到ht[1]哈希表指定位置 3. 当ht[0]包含的所有键值对都迁移到ht[1]之后(ht[0]变为空表)释放ht[0], ht[1]变成ht[0], ht[1]新创建一个空 哈希表,为下一次rehash做准备 字典
  10. 数据结构 那么问题来了,如果哈希表里保存了1个亿的键值对数量,rehash庞大的计算量可能会导致服务器一段时间不可用。 字典 渐进式rehash 1. 为ht[1]分配空间,让字典同时拥有ht[0]和ht[1]两个hash。 2. 字典中维护一个索引计数器rehashidx,并将它设为0,表示rehash正式开始。 3. rehash期间,每次对字典执行增、删、查、改操作时,执行相关指令同时,还有将ht[0]上rehashidx索引上所有键

    值对rehash到ht[1]上,之后将rehashidx增加1。 4. 随着字典的不断操作,最终某个时间点,ht[0]上的所有键值对被rehash到ht[1]上;这是键rehashidx设为-1,表示 rehash操作完成。 渐进式rehash期间,客户端发送 的增、删、改、查指令在服务端 执行过程是怎样的?
  11. 数据结构 跳跃表(skiplist) 跳跃表是一种有序数据结构,其通过每个几点维护多个指向其他节点的指针,达到快速访问节点的目的。Redis使用跳 表的地方不多,一个是有序集合、一个是集群节点中用作内部数据结构。 整数集合(intset) 集合键的底层实现之一,当一个集合全是整数,且集合元素不多时,Redis就会采用整数集合作为集合键的底层实现。 可以了解下整数集合的升级过程。 压缩列表(ziplist) 压缩列表是列表键和哈希键的底层实现之一。当列表或者哈希中是比较小的整数或者比较短的字符串时,Redis会使用 压缩列表作为其底层实现。是一种为了节约内存而开发的顺序数据结构。

  12. 单机数据库实现 服务器中的数据库是一个redisDB数组,默认16个DB,每一个客户端都有自己的目标数据库,默认0号数据库。 数据库

  13. 单机数据库实现 常见的过期删除策略: 1.定时删除:在设置过期时间同时,创建一个定时器(timer),让时间来临时立即执行删除操作。 2.惰性删除:放任键过期不管,只有在使用到的时候,检查键是否过期,如果过期删除改键。 3.定期删除:每个一段时间定期检查数据库,删除里面的过期键。 三种过期策略各有什么优缺点?Redis采用什么过期删除策略? 数据库

  14. 单机数据库实现 Redis是一个内存数据库,如果服务器故障导致进程退出,那么数据就会丢失。为了解决这个问题Redis提供了持久化 功能。 常见持久方案: 1. 快照,比如 mysql Dump; Redis RDB

    2. 写日志,比如 mysql binlog;Hbase Hlog;Redis AOF 持久化
  15. 单机数据库实现 Redis提供RDB持久化功能,可以将Redis在内存中的数据库状态保存到磁盘里,RDB文件是一个经过压缩的二进制文 件,通过该文件可以还原生成RDB文件时的数据库状态。 生成RDB文件有两个命令,一个是SAVE,一个是BGSAVE。 SAVE命令会阻塞Redis服务器进程,知道RDB文件创建完毕为止,期间不能处理任何命令请求; BGSAVE命令会派生出一个子进程,由子进程负责创建RDB文件,服务器(父进程)进程继续处理命令请求;但是 SAVE和BGSAVE命令会被拒绝。默认RDB策略: save 900 1

    # 15分钟后发生1次key变化,dump内存快照 save 300 10 # 5分钟后发生10次key变化,dump内存快照 save 60 10000 # 1分钟后发生10000次key变化,dump内存快照 载入RDB文件没有专门的命令,而是在服务启动,检测到RDB文件会自动加载的。 持久化—RDB
  16. 单机数据库实现 与RDB通过保存数据库中的键值对来记录数据库状态不同,AOF(Append Only File)是通过保存Redis服务器所执行 的命令来记录数据库状态的。AOF持久化功能的实现可分为命令追加、文件写入、文件同步三个步骤。 AOF持久化的效率性与安全性 服务器配置的appendfsync的值直接决定了AOF的效率性和安全性。 - always 每个事件循环都会将aof_buf缓冲区的内容写入AOF文件,并且同步。效率最低,但最安全,最多丢是一个事

    件循环中产生的命令数据。 - everysec 每个事件循环都会同步aof_buf缓存区内容,每隔一秒钟子线程对AOF文件进行一次同步。效率足够快,故 障时会丢失一秒钟的数据。 - no 每个事件循环都会将aof_buf缓冲区的内容写入AOF文件,至于何时同步由操作系统控制。写入速度最快,但是故 障时会丢失上次同步AOF文件之后的所有写数据。 持久化—AOF
  17. 单机数据库实现 AOF重写 随着服务器运行,AOF文件内容会越来越多,文件体积会越来越大,如果不加以控制,可能会对Redis服务器,甚至整 个宿主机造成影响,且文件越大,数据恢复时间就越长。为了解决AOF文件膨胀问题,Redis提供了AOF重写(rewrite) 功能。 AOF文件重写并不需要读取现有AOF文件,而是读取服务器当前状态,写入新的AOF文件。为了不对客户端请求造成 阻塞,aof_rewrite 是交给了子进程处理的。 不过,子进程在进行AOF重写期间,服务器进程还需继续处理命令,这势必会造成重写后的AOF与数据库状态不一致。 解决方案:

    持久化—AOF
  18. 单机数据库实现 持久化-数据还原 AOF更加安全,可以将数据更加及时的同步到文件中,但是AOF需要较多的磁盘IO开支,AOF文件尺寸较大,文件内 容恢复数度相对较慢。 RDB,安全性较差,它是“正常时期”数据备份以及master-slave数据同步的最佳手段,文件尺寸较小,恢复数度较快。 RDB 和 AOF用哪个好? 官方推荐都启用 对数据不敏感,单RDB

    架构设计良好的环境下,通常master做AOF保证数据完整性,slave做RDB,保证快速read即可;如果密集write场景, 可以反过来。 网络/物理环境极差,建议都用AOF,减少人工引导恢复的成本
  19. 多机数据库实现 在redis中通过slaveof 命令,让一台服务器去复制(replicate)另一台服务器,被复制的服务器成为主服务器 (master),对主服务器进行复制的服务器,成为从服务器(slave)。 复制功能分为同步(sync)和命令传播(command propagate)。 旧版(2.8之前)复制功能 复制 对于初次复制来说,旧版复制功能能够很好完成工作,但是对 于断线重连复制,旧版复制功能让主从重新回到一致状态,效

    率就非常低了。
  20. 多机数据库实现 在为了解决旧版复制功能在处理断线重复制情况下的低效问题,新版复制功能使用了PSYNC命令代替了SYNC命令来执 行复制时的同步操作。PSYNC有全量重同步(full resynchronization)和部分重同步(partial resynchronization) 两种模式: - 全量重同步和SYNC步骤基本一致,都是主服务器创建并发送RDB文件,以及向从服务器发送缓冲区写命令 - 部分重同步用于处理断线重复制问题,当从服务器断开后又重新连接主服务器,如果条件允许。主服务器可以将断开

    后的写命令发送给从服务器,从服务器只需更新这部分数据。 复制
  21. 多机数据库实现 当主服务器进行命令传播时,不经将写命令发送发所有从服务器,还要写入到“复制积压缓冲区”(默认1MB)。从服务 器可能在缓冲区写满前、后恢复。不同情况采取重同步也不同。 复制

  22. 多机数据库实现 Sentinel(哨兵)是Redis高可用的解决方案:由一个或多个Sentinel实例组成Sentinel系统,可以监控任意多个主服 务器,以及这些主服务器属下的所有从服务器,并在监视到主服务器下线状态时,将其某个从服务器升级为主服务器, 来处理请求。 Sentinel

  23. 多机数据库实现 Redis Cluster是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移 功能。 握手 一个Redis Cluster由多个node组成,node0向另一个node1发送Cluster Meet命令进行握手,握手成功后node1就会 添加到node0所在集群;但是这个集群还是处于下线状态,因为没有分配槽; 槽指派

    Redis通过分片方式保存数据库中的键值对,集群整个数据库被分为16384个槽(slot)。在clusterState中记录为包含 16384个项的数组。数组的每一项指向了clusterNode 集群中执行命令 Redis首先会计算key属于哪一个槽,CRC16(key) & 16384; 再看槽是否属于当前节点,如果不是会返回MOVED错误, 指引客户端转到正在负责槽的节点。 集群
  24. 多机数据库实现 重新分片 Redis集群可以将任意数量已经指派给某个节点的槽改派给其他节点,并且相关槽所属的键值对从源节点迁移到目标节 点。重新分片期间集群不需要下线。 复制与故障转移 Redis集群中节点分为master和slave,master负责处理槽,slave负责某个master。并且在master下线时代替下线的 master继续处理请求。 集群

  25. 多机数据库实现 故障检测 Redis集群每个节点都会定期向集群中其他节点发送PING消息,来检测对方是否在线,如果没在规定时间内收到 PONG回应,就将其标记为疑似下线(PFAIL);集群中各个节点通过互相发送消息的方式交换集群各个节点状态信息, 例如某个节点在线,疑似下线,已下线(FAIL);如果集群中半数以上主节点将某个节点标记为PFAIL,那么这个节点 就会被标记为FAIL,并向集群广播。 故障转移 当从节点发现自己正在复制分master已经进入下线状态,从节点将开始进行故障转移: 1. 下线主节点对于所有从节点里面选出一个节点

    2. 被选中的节点执行slaveof no one,成为新主 3. 新主会撤销已下线主节点所有槽指派,并将这些槽全部指派给自己 4. 新主集群广播PONG消息,让集群其他主节点立即知道已经这个节点由从变主,并接管所有已下线节点负责的槽 5. 新主开始接收和自己负责处理的槽有关的命令请求,故障转移完成 集群
  26. 使用规范与常见问题 Key设计 1. 可读性和可管理性 - 业务名为前缀,防止key冲突,冒号分割,如ele:wp:uid:1 2.简洁性 - 保证语义前提下,控制key长度 3.不包含特殊字符

    Value设计 1. 避免bigkey - String控制在10KB内 - hash、list、set、zset元素不超过5000,反例:一个包含百万原生的list、hash,一个巨大的json字符串 2.bigkey的危害 - 网络阻塞;慢查询阻塞其他命令;集群节点数据不均衡;应用需要频繁序列化&反序列化 使用规范
  27. 使用规范与常见问题 bigkey删除 - 直接del删除bigkey非常慢,对redis发生阻塞 - 小心bigkey隐性删除,如过期 - 可以使用lazy delete(unlink命令),后台删除,不会阻塞前台命令线程 bigkey

    预防 - 优化数据结构,例如二级拆分,按天/小时存入 - 命令优化,避免hgetall,使用hmget、hscan - 报警和定期优化 合理的数据结构 1. 避免数据松散,hash可能更适合,ziplist能够节省内存 2. 需求 100万数据(picId -> userId)有哪些存储方案? 使用规范
  28. 使用规范与常见问题 Key生命周期管理 - 周期数据需要设置过期时间 - 过期时间不宜集中,容易出现缓存穿透和雪崩问题 命令 - O(N)以上命令尽量不用,如果必须,关注n的规模 -

    keys、flushall等禁止使用 使用规范
  29. 使用规范与常见问题 缓存穿透 - 不存在的数据,不能命中缓存,查询DB - 业务代码问题、恶意攻击 - 关注指标缓存命中率,缓存空(设置过期),布隆过滤器 缓存雪崩 -

    cache承载大量请求,如果宕机,流量直接打到后端,造成级联故障。或者缓存集中过期,发生大量缓存穿透。 - 保证高可用;二级缓存;后端限流;避免集中过期;提前演练 无底洞问题 - 添加机器是,客户端性能不升反降 - 命令本身优化,减少通信次数,其他IO优化 热key问题&热key重建问题 常见问题
  30. 其他功能 Redis还有很多其他功能比如: - 类MQ的发布订阅 - 支持Lua - 支持pipeline - GEO

    - …
  31. Thank You