一张脑图助你搞定Redis

前言

  温故知新,最近自己在复习之前零散的知识点,想通过脑图的方式将每一块知识内容串在一起,比如redis,多线程,jvm,spring等等。
  同时希望我的总结也可以帮助到其他人,所以打算写这个系列的文章。就叫它“一张脑图系列”。


  这篇文章是我复习完redis之后的总结,共有四小节,每小节最后会有本节的脑图。

  1. Redis 基础
  2. Redis原理
  3. Redis分布式
  4. Redis应用实战

  由于文章是以相对简练的描述进行总结,适用人群:

  • 想要先概览redis,然后对细节部分逐个击破
  • 有redis使用经验,想要短时间复习redis大部分知识点

01.Redis 基础

缓存

  • 应用内缓存:Map、EH Cache
  • 应用外缓存(缓存组件):Memcache、Redis

存储结构

  • remote dictionary server(远程字典服务器)
  • 以字典结构存储数据,允许其他应用通过TCP读写字典中的内容

redis启动&停止

  • 可执行文件
    • Redis-server Redis服务器
    • Redis-benchmark Redis性能测试工具
    • redis-check-aof Aof文件修复工具
    • redis-check-dump Rdb文件检查工具
    • Redis-sentinel Sentinel服务器(2.8以后)
  • 直接启动(默认端口6379) → redis-server …/redis.conf
  • 停止 → redis-cli shutdown
    • redis收到shutdown命令后,先断开所有客户端连接
    • 然后根据配置执行持久化,最终完成退出
    • 强制杀线程,会导致数据丢失。∵redis可能正在将内存中的数据同步到硬盘

五种数据类型及应用场景

字符串类型
  • 使用场景(最大容量512M)
    • 存储用户的邮箱、json化的对象、图片
  • 数据结构
    • int → 存放整型数据
    • SDS(simple dynamic string) → 存放字节 / 字符&浮点型数据
    • 根据字符串长度调整Header,从而节省内存
列表类型(双向)
  • 使用场景
    • 最新消息排行、消息队列
  • 数据结构
    • v3.2:linkedlist | ziplist → quicklist(由ziplist组成的双向链表)
    • linkedlist → 插入复杂度低,但内存开销大
    • ziplist → 存储效率高,但需要频繁申请和释放内存。∵存储在一段连续内存
    • 当元素个数和单个长度小,采用ziplist
hash类型
  • 使用场景
    • 存储对象(每一个field-value相当于对象的属性和属性值)
  • 数据结构
    • 数据量小时,用ziplist
    • 否则使用hashtable
    • dictEntry → 内部链(key-value+next指针)
    • dictht → 存放buckets
    • dict → 扩容/缩容时,dictht的迁移
集合(set)
  • 使用场景
    • 交集、并集、差集 → 共同喜好、二度好友
    • 获取某段时间所有数据的去重值
  • 数据结构(唯一性、无序性)
    • 只包含整数型元素,intset存储
    • 否则,hashtable存储,value=null
有序集合(zset)
  • 使用场景
    • 带权重的消息队列
    • 按照时间获取 → 把时间作为score
  • 数据结构
    • skiplist+hashtable

一张脑图助你搞定Redis_第1张图片


02.Redis原理

Redis过期时间设置

  • 过期删除原理
    • 惰性删除 → 当键被访问时,发现失效就删除
    • 定期删除
      • 随机测试20个带有timeout信息的key
      • 删除已经过期的
      • 如果超过25%过期,则重复步骤1
  • 命令行
    • EXPIRE key seconds → 设置成功返回1;设置失败或键不存在返回0
    • PEXPIRE key mseconds(毫秒)
    • setex(String key,int seconds,String value) → 针对字符串
    • TTL key → 还有多久被删除;键不存在返回-2

Redis发布订阅

  • 命令行
    • PUBLISH channel message
    • SUBSCRIBE channel [***]
  • 普通channel、pattern channel(规则匹配)

Redis持久化

  • RDB方式(快照)
    • 单独fork(复制一个当前进程)一个子进程进行持久化
    • 优点:高效
    • 缺点:最后一次持久化的数据可能丢失
    • RDB时机
      • 根据配置进行自动快照 → redis.conf
        • save 900 1 (900秒内有1个以上键被改则快照)
      • 用户执行SAVE或BGSAVE
        • SAVE → 阻塞所有客户端请求,同步快照
        • BGSAVE → 异步进行快照同步
        • LASTSAVE → 最近一次快照时间
      • 执行FLUSHALL → 清除内存中所有数据
        • redis.conf快照规则不为空,就执行save
      • 主从复制时
  • AOF方式(Append Only File)
    • AOF实现 → 每执行一条更改命令,就将该命令写入硬盘AOF文件
    • 开启AOF → redis.conf:appendonly yes
    • AOF重写
      • 目的 → 避免AOF文件中命令的冗余
      • 原理
        • 主进程fork一个子进程进行aof重写。重写的过程类似快照,遍历内存中的数据,序列到aof文件- 中。
        • 重写过程中,redis仍对外提供服务,新写入的命令缓存到aof_rewrite_buf 文件中。子进程重写完,再把缓存的数据追加到新aof文件中。
        • 最后将新aof替换旧的aof文件。
    • 配置
      • auto-aof-rewrite-percentage 超过上次重写aof大小的百分比,自动重写
      • auto-aof-rewrite-min-size 最小重写aof文件大小
      • BGREWRITEOF → 手动重写

Redis内存回收策略(内存不足时,淘汰哪些对象?)

  • 默认noeviction:内存达到阈值,申请内存命令报错
  • allkeys-lru:移除最近最少使用
    • 场景:热点数据
  • allkeys-random:随机移除
    • 场景:key访问概率相等
  • volatile-radom:已设置过期,随机移除
  • volatile-lru:已设置过期,移除最近最少使用
  • volatile:已设置过期,移除将要淘汰的

Redis的单线程模型

  • CPU并不是Redis的瓶颈所在,Redis的瓶颈在于内存和网络带宽
  • 仍然可以处理多个客户端的请求 → 多路复用(异步阻塞IO)

Redis中Lua脚本

  • 优点
    • 减少网络开销 → 多命令一次传输
    • 原子性操作
    • 复用性 → lua脚本保存在redis上,可复用
  • 命令
    • redis.call()
    • EVAL
    • EVALSHA → 通过sha字符串在服务器上找到对应的Lua脚本


03.Redis分布式

主从复制

  • 概念
    • master:读写操作,数据变化,自动同步给slave
    • slave:只读
  • 配置
    • master不需要改动;slave的redis.conf增加 slaveof masterip 6379
    • 所有节点将bind ip注释掉,允许所有ip访问
  • 复制方式
    • 全量复制 → slave初始化阶段
      1. slave连接master,并发送同步请求
      2. master执行BGSAVE,并记录在此期间的写命令
      3. master发送快照到slave
      4. master发送缓存的写命令到slave
      • 配置:
        • min-slaves-to-write 3 → slave连接数达到3时,master可写
        • min-slaves-max-lag 10 → slave最长失去连接时间10s
    • 增量复制(v2.8+) → 防止复制时出现网络问题
      • master会创建backlog,slave保存offset
      • 自动从offset处开始复制
    • 无硬盘复制(v2.8+)
      • 解决问题
        • 禁用rdb时,主从复制产生的RDB文件会使恢复数据丢失
        • 硬盘性能影响复制性能
      • 配置 → repl-diskless-sync yes
      • master在内存中创建rdb,不落地磁盘,直接发送给slave

哨兵机制

  • 作用
    • 监控master和slave正常运行
    • master出现故障自动将slave升级为master
    • 哨兵之间相互监控,保证可用性
  • 原理
    • sentinel相互感知
      • sentinel同时订阅master channel:sentinel:hello
      • 新加入的sentinel向这个channel发布一条包含自身信息的消息
      • 与新加入的sentinel建立长连接
    • master故障发现
      • sentinel定期向master发送心跳包,一旦没有响应,→“主观不可用”
      • 发送给其他sentenel节点确认,>quorum,→“客观不可用”
      • sentinel leader(Raft算法选举出)选举新的master
    • master选举原则(按顺序)
      • 较低的slave_priority(slave的redis.conf)
      • 较大的replication offset
      • 较小的runid (redis启动时设置)
      • 上面都不能区分,会看哪个slave节点处理之前master的command多
  • 配置(sentinel.conf)
    • sentinel monitor [mastername] ip port quorum
    • sentinel down-after-milliseconds [mastername] 5000
    • sentinel failover-timeout [mastername] 15000
  • 启动方式
    • redis-sentinel sentinel.cof
    • redis-server /path/to/sentinel.conf --sentinel
  • 日志信息
    • +sdown master 主观不可用
    • +odown master 客观不可用
    • +try-failover 哨兵开始进行故障恢复
    • +failover-end 哨兵完成故障恢复
    • +slave 列出新slave,包含旧master

Redis-Cluster

  • 目的 → 对数据进行水平扩容(分片)
  • 数据分区原理
    • 按照虚拟槽(0~16383)
    • 计算公式 → CRC16(key)%16383
  • HashTags
    • 场景:业务中需要进行原子操作
    • 用法:{user1}:id,{user1}:name;只对key中{}部分做hash
  • 重定向客户端
    • 客户端不参与hash(key)
    • 当访问一个key不存在的节点,由该节点计算槽位,返回给客户端key对应槽位所在节点
    • 客户端再访问key所在节点
  • 分片迁移
    • 新增master节点 → 从各节点前取一部分slot到新节点
    • 删除master节点
    • 槽迁移过程(不稳定)
      • 迁移目标主节点 标记状态 IMPORTING
      • 被迁移主节点 标记状态 MIGRATING
      • 客户端只会先访问 IMPORTING 节点(直接访问 MIGRATING 时,会返回给客户端跳转IMPORTING)
      • IMPORTING 中没找到key,再回复客户端跳转到 MIGRATING


04.Redis应用实战

Redis客户端

  • Jedis → 比较全面的Redis命令支持
    • jedis-sentinel原理
      • 客户端连接到哨兵集群,从sentinel中询问master信息
      • 客户端向master发起连接
      • 在客户端建立监听;当master重新选举后,client重连新的master
    • jedis-cluster原理
      • 程序启动初始化集群环境;实现slot与jedisPool映射,jedisPool中包含master信息
      • 通过key计算slot值,再找到对应master,完成读取
  • Redission → 操作简单;封装了分布式锁、原子操作、布隆过滤器、队列等功能
  • lettuce → 基于Nerry,异步响应式

Redis实现分布式锁

  • 原理
    • 范围;所有进程都可以访问Redis
    • SETEX命令
  • Jedis实现分布式锁 → 需要自己实现
  • Redisson实现分布式锁 → 已封装
    • 通过threadId做唯一标识
    • LUA脚本保证原子性
      • 加锁
        • 判断lock键是否存在,不存在设置过期时间 → 获取到锁
        • 存在则重入次数+1,重新设置过期时间, → 获取到锁
        • 被其他线程锁定,返回锁有效期, → 需要等待
      • 释放锁
        • lock不存在 → 锁可用
        • 锁不是当前线程 → nil
        • 锁是当前线程 → 重入次数-1
        • 重入次数>0,重新设置过期时间
        • 重入次数<=0,锁可用

管道模式

  • 实现客户端非阻塞;服务端未及时响应时,客户端也可以继续发请求,保存在管道上,最终返回所有请求。

Redis应用架构(读多写少)

一张脑图助你搞定Redis_第2张图片

  • Redis缓存与数据库一致性

    • 先更新缓存,再更新数据库
      • 适合对数据实时性要求严格的场景
      • 缺点:容易造成数据丢失
    • 先更新数据库,再删除缓存
      • 大部分场景使用,保证了数据的安全性
      • 有很小概率的脏数据 → 读缓存时,缓存失效,并发还有写操作
        一张脑图助你搞定Redis_第3张图片
  • 缓存雪崩

    • 缓存同时失效,请求全部转发到DB,导致DB压力过大而崩溃
    • 解决方式
      • 通过加锁或队列保证单线程访问数据库 → 性能损耗
      • 将缓存失效时间分散
      • 保证缓存服务器高可用,多级缓存
  • 缓存穿透

    • 大量请求缓存不能命中,导致DB压力过大
    • 解决方式
      • 查询数据库为空,直接设默认值放入缓存
      • 根据缓存Key的设计规则,事先过滤 → 布隆过滤器
        一张脑图助你搞定Redis_第4张图片

总结

  本文只是redis知识体系的概览,像很多问题还没有深入。比如:

  • jedis & redission 源码分析
  • LRU算法的实现
  • redis分布式锁的具体实现
  • 布隆过滤器 …

  以后有机会另写博客补充。

你可能感兴趣的:(分布式,一张脑图系列)