本文主要对Redis的应用场景及其内部实现机制进行了总结,并对在使用Redis过程中可能会遇到的坑给出了针对性的规避措施。

Redis思维导图

1. 背景

关系数据库访问数据较慢

2. 目标

提高数据的访问速度

3. 优势

数据访问速度较快

4. 劣势

数据存放在内存中,容量存在限制

5. 底层原理

5.1. 内存数据库

5.2. 持久化方式

5.2.1. RDB

  • 介绍
    在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,在替换之前的文件,用二进制压缩存储

  • 优势
    适合进行备份
    适合进行灾备
    对Redis的性能压力比较小
    RDB在恢复大数据集的速度比AOF恢复速度要快

  • 劣势
    RDB有可能会丢失分钟级的数据
    如果数据集比较大,且CPU紧张的情况下,Redis停止服务的时间可能会比较长

5.2.2. AOF

  • 介绍
    以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

  • 优势
    AOF只会丢失秒级的数据
    AOF只进行追加
    AOF文件的可读性较好

  • 劣势
    对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积
    AOF的速度可能会比RDB慢
    AOF可能会拖慢Redis的访问速度

5.3. 部署架构

  • 主从复制
  • 哨兵
  • 集群
    Master之间分配slots,一共16384个slot
    slot不完整,集群失效
    每个Master都会有Slave
    扩缩容需要手动重新分片
    先到其中任何一台执行,没执行成功会将客户端转向正确的节点执行命令
    redis提供了命令供管理员自动迁移slots
    使用CRC16(KEY) mod 16384的值,决定将key放到哪个槽中
    去中心化设计,每个节点都是对等的,保存全量的元数据信息

6. 应用场景

查多写少
热点数据
不适合存储持久化数据

7. 底层原理

7.1. 底层数据结构

  • String
    embstr
    SDS
    len,free
    redisObject

  • HMap
    1、hashtable
    元素比较多时
    2、ziplist
    适用于元素较小时
    key和value都被推到队尾

  • List
    1、ziplist
    使用连续空间,节省空间,但只能保存小整数或者小字符串
    ziplist如何压缩空间
    小数字和大数字不一样,小数子只用一个字节存储
    不需要前后指针
    2、linkedlist
    双端链表

  • Set
    1、inset
    inset编码的集合对象使用整数集合作底层实现,集合对象包含所有元素都会被保存在整数集合里面
    2、hashtable

  • SortedSet
    1、skiplist
    使用ZRANGE和ZREVERANGE比较多
    内存占用不多
    实现比较简单
    跳表示意图
    2、ziplist

  • HyperLogLog
    统计元素数量

  • GEO
    地理位置

7.2. 一致性Hash

  • 背景
    取模的方式扩缩容的时候对缓存命中率的影响比较大

  • 原理
    将整个hash值空间组织成一个虚拟的圆环
    1、扩容
    只影响添加节点后面的节点(顺时针)
    2、缩容
    只影响删除节点后面的数据(顺时针)

  • 问题1
    描述
    当节点较少的时候,容易因为节点分布不均匀,产生缓存热点的问题
    1、解决方法
    使用虚拟节点,改善一致性Hash的问题
    2、具体实现
    https://www.cnblogs.com/snowwhite/p/9672185.html

7.3. 过期策略

  • 定期删除
    默认是每隔100ms就随机抽取一些一些设置了过期的key
    如果此时key已经过期,就删除,不会返回任何东西

  • 惰性删除
    放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键

  • 定时删除
    从设置key的过期时间开始,就启动一个定时器,到时间就删除该key
    对内存比较友好,但浪费CPU资源

7.4. 复制原理
slave第一次连接或者重连,会触发一次全量同步
主节点做一个bgsave,同时将后续修改操作记录到内存buffer,如果buffer超过限定的值,则复制失败。待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存,加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程
后续Master手机到的写命令都会通过之前建立的连接,增量发送给slave端
slave执行buffer中的命令
slave异步方式执行同步

7.5. 复制时机

  • 全量复制
    slave首次启动或重启
    slave不满足部分复制的条件

  • 部分复制
    主从的redis版本 >= 2.8
    slave只是掉线
    slave保存的run id与master当前run id一致
    slave掉线期间,master保存在内存的offset可用

7.6. 数据丢失

  • 异步复制导致的数据丢失
    Master突然宕机,Slave有些数据还没有同步过来

  • 脑裂导致的数据丢失
    发生主从切换,但是客户端还在往Master写入数据

7.7. 事务

  • ACID
    1、原子性
    2、持久性
    AOF在总是SYNC的模式下,因为主线程不会阻塞到写入完成,所以不能保证持久性
    RDB的情况下,事务执行完成后,服务器可能在RDB未更新之前失败,所以也不可能保证持久性
    3、隔离性
    4、一致性

  • 使用方法
    WATCH
    MULTI
    EXEC

7.8. 过期算法
ttl
lru
random

8. 对比

  • MongoDB
    非关系数据库
    非内存数据库

  • Memcached
    内存数据库
    支持的数据结构较少
    不能持久化

9. 客户端驱动

  • Jedis
    偏数据库驱动,API提供了比较全面的Redis命令的支持
    高性能,分布式特性和丰富的结构,让使用者能够将精力更集中地放在处理业务逻辑上

  • Redisson
    底层原理
    使用Netty作为通信组件
    将一些操作组合成Script实现特定的功能

  • 问题
    MapCache有内存溢出风险
    在编解码失败的时候可能会造成某些命令执行时间过长

10. 相关问题

  • 缓存命中率

  • 缓存穿透
    解决方法
    缓存预热

  • 缓存雪崩
    解决方法
    使用高可用架构
    服务降级,使用本地缓存

  • 缓存击穿
    解决方法
    让key不在一个时间段集中失效,加或减一个随机值

  • 为什么使用单线程
    CPU不是瓶颈点,IO是瓶颈点,单线程够用也简单

  • redis集群做迁移的时候怎么查找数据?
    单个key迁移的过程中会阻塞,所以如果没找到key会重新ask一遍

  • 常用命令

    1
    scan 176 MATCH *11* COUNT 1000

11. 常见优化

Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
尽量避免在压力很大的主库增加从库
主从复制不要用图状结构,用单向链表结构更为稳定,即:Master < Slave1 < Slave2 …

Contents
  1. 1. 1. 背景
  2. 2. 2. 目标
  3. 3. 3. 优势
  4. 4. 4. 劣势
  5. 5. 5. 底层原理
  6. 6. 6. 应用场景
  7. 7. 7. 底层原理
  8. 8. 8. 对比
  9. 9. 9. 客户端驱动
  10. 10. 10. 相关问题
  11. 11. 11. 常见优化