0. 基础

0.1. 常识

  1. Redis中单个key最长512M
  2. Redis中key的上线是2^32个,大概2.5个左右

    0.2. 缓存模式

  • Cache aside,最常用的模式。其具体逻辑如下:
    • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
    • 命中:应用程序从cache中取数据,取到后返回。
    • 更新:先把数据存到数据库中,成功后,再让缓存失效。
  • Read through
    • 在查询操作中更新缓存。
    • 当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的
  • Write through
    • 在更新数据时更新缓存。
    • 当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
  • Write behind caching/Write Back

    • Write-Back 算是 Write-Through 的改良版,Write-Through 每写一次缓存,缓存就会写一次数据库,而 Write-Back 则是写了多次缓存后才会写一次数据库,可以大大减轻服务器的压力
    • 当然, Write-Back 也不是没有缺点,如果缓存出现了问题,那么缓存中这部分没有持久化的数据就会丢失
    • MySQL的组提交,操作系统对脏页的处理,操作系统的OS Buffer,LSM树将随机IO转为顺序IO

      1. 数据类型

      1.1. string

      1.1.1. 字符

      1.1.2. 数值

      1.1.3. bitmap

      1.1.4. 底层结构

  • sds

  • embstr
  • raw
  • int

    1.2. hash

    1.3. list

    1.4. set

    1.5. sorted_set

    1.6. geo

    1.7. stream

    2. 用途

    2.1. 缓存

    数据相对而言,可以丢数据,追求极速。RDB快照一般就够用了。

    2.1.9. 常见问题

  • 击穿

    • 高并发场景下,某个key突然失效(过期或被淘汰),导致直接访问数据库。数据库有,缓存没有
    • 解决:利用setnx设置分布式锁,让某一个线程去访问数据库获取数据并设置到缓存
      新问题:如果这个线程挂了怎么办?
      解决:利用redis的超时时间,设定锁超时
      新问题:超时时间的长短怎么计算
      解决:利用多线程,先设置一个较短的超时时间,用一个线程去请求数据库,另一个线程监控这个结果,如果没有在规定时间获取到数据,就延长锁的超时时间
  • 穿透
    • 数据库没有,缓存也没有
    • 解决:bloom(布隆)过滤器,缺点:只能增加,不能删除
      布谷鸟过滤器
      bloom+
  • 雪崩
    • 大量key同时失效(过期或被淘汰),导致大量请求直接访问数据库
    • 解决:如果无所谓过期时间节点的:分散key过期时间
      如果时间节点精确要求的:类似击穿的解决方案,或者业务层进行随机延时来岔开流量到达数据层
  • 数据一致性,双写问题

    2.2. 数据库

    不可以丢数据。所以得启用AOF日志,来保证数据的可靠。

    3. 持久化机制

    3.1. 单机持久化

    存储层为了保证数据的持久性都会有两个机制来保证数据

  • 快照/副本

    • Redis中的快照文件是RDB文件,RDB文件是有时点性的。在这个期间使用fork系统调用快速创建一个子进程,然后依靠CopyOnWrite机制和Linux提供的进程隔离性,使得Redis可以在落地快照文件期间既可以继续对外提供服务,又可以保证RDB文件的时点准确性。
      • 时点性:假设从8点开始要落地一个日志快到到文件,假设到8.10分文件落地完毕,那么此时记录的日志记录是8点那个时间的数据。
      • 进程隔离:各个进程间不可以查看和修改其他进程的数据信息。但是使用export指令可以使得指定的变量被其他进程看到,且子进程对这个变量的修改不会影响到父进程,同样父进程的修改也不会影响到子进程。
      • CopyOnWrite 写时复制机制:只在写时拷贝受影响的数据。根据经验,大部分情况下父子进程不会修改所有的数据,所以这样节省空间
    • 使用方法
      • 命令 save/bgsave
        1. save 阻塞,用于服务即将关机/停机维护的时候
        2. bgsave 异步非阻塞,调用fork系统调用,服务不停机继续运行的时候
      • 配置文件中给出bgsave的规则,在配置文件中使用的是save的指令,但是触发的是bgsave的操作
      • 命令说明:save [时间(s)] [操作次数]
        • save 900 1 900秒内触发1次
        • save 300 10 300秒内触发10次
        • save 60 10000 60秒内触发10000次
        • 关闭 save “” 或者直接不写这个配置项
      • 存储位置 dbfilename 指定RDB文件名称,dir 指定存储目录
    • 弊端
      • 不支持拉链,只有一个RDB文件。无法查看历史数据
      • 丢失数据相对多一些,因为不是实时的记录数据的变化,时点与时点之间的数据可能就会丢失
    • 优势
      • 数据恢复快
  • AOF(append only file)日志

    • 丢失数据少
    • RDB和AOF都可以开启,但是如果开启了AOF,只会用AOF文件来进行数据恢复
    • 为了避免AOF文件过大的情况,可以使用AOF的重写指令,来合并一些命令,减少AOF文件的大小,方便数据恢复
      • 4.0之前AOF文件只是一个命令的集合,单纯的AOF文件
      • 4.0版本之后,在每次进行执行AOF重写操作的时候,会将当前的快照RDB放入AOF的前半部分后面会增加写操作的增量操作到文件中。既利用了RDB的快,又利用了AOF的全
    • 写操作会向AOF写入日志,所以会触发IO操作,相关的配置项,也可以使用bgrewriteaof命令手动触发
      • 打开AOF appendonly no -> yes
      • appendonlyfilename “”
      • 相关的同步写操作到AOF时机的命令
        1. appendfsync always 每个写操作写入内核缓冲区,并立即调用操作系统的命令,写入AOF文件
        2. appendfsync everysec 缓冲区满时,操作系统机制自动写,Redis每秒也固定调一次
        3. appendfsync no 从不主动,依赖操作系统的机制:当内核缓冲区满的时候刷到磁盘
      • aof-use-rdb-preamble yes 打开rdb与aof的混合日志模式
      • auto-aif-rewrite-percentage 100
      • auto-aof-rewrite-min-size 64mb 日志到达64mb进行rewriteaof操作

        3.2 Redis的内存淘汰策略

        3.2.1. 定期清除

  • Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。

  • Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
    • 从过期字典中随机 20 个 key;
    • 删除这 20 个 key 中已经过期的 key;
    • 如果过期的 key 比率超过 1/4,那就重复步骤 1;
  • Redis默认是每隔 100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。

    • 为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载

      3.2.2.惰性清除

  • 所谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。

  • 定期删除可能会导致很多过期key到了时间并没有被删除掉。所以就有了惰性删除。
  • 假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,即当你主动去查过期的key时,如果发现key过期了,就立即进行删除,不返回任何东西

总结
定期删除是集中处理,惰性删除是零散处理。

  1. 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key.
  2. 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key.
  3. 当前已用内存超过maxmemory限定时,触发主动清理策略

    3.3.3. 内存淘汰策略

    当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
    以下的策略是可用的:
  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:

  • 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
  • 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
  • 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。

allkeys-lruvolatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。

4.1. 自带的主从复制

采用异步的方式进行复制,特点是低延迟,高性能。所以是弱一致性。这是因为Redis的特点:极致的速度。默认从节点是只读模式,可以在配置文件中修改

  • 常用命令
    • 5.0以前 slaveof host port
    • 5.0以后 replicaof host port
      • replicaof noone 表示自己当主
  • 特殊配置
    • replica-server-stale-data yes 表示在接收主节点的数据期间是否继续对外提供老数据的访问
    • replica-read-only yes/no 从节点是否只读
    • repi-diskless-sync no 是否通过磁盘落地一个rdb文件在通过网络发送,还是直接通过网络发送rdb文件
    • repl-backlog-size 1mb 增量复制,从节点挂掉之后再同步的时候,主节点可以给从节点保留的增量数据更新,避免从节点重新全量复制
    • min-replicas-to-wirte=3
    • min-replicas-max-lag=10
  • 现象

    • 主节点在接收到从节点的追随信息后,落地一个rdb文件,然后发送给slave机器
    • 从节点接收到主节点的rdb文件后,flushall掉本地的旧数据,然后执行rdb文件,同步数据

      • 从节点在主节点正常运行期间挂掉,然后修复上线之后会自动同步增量数据,而不重新依赖rdb
      • 但是从节点的启动命令中如果指定了使用aof方式,那么每次重新上线主节点都会重新落地rdb文件,传递给从节点。全量同步数据

        4.2. 集群模式

        4.2.1. 单机、单节点的问题

    • 单点故障

    • 容量有限
    • 支撑的访问压力有限

解决:AKF扩展立方体

4.2.1.1. AKF扩展立方体

  • 方式
    • X轴:全量复制,主从分离
    • Y轴:业务拆分,减小单机的数据量
    • Z轴:单节点数据量依旧很大,按一定规则拆分单个节点
  • 引入了新的问题

    • 一变多就会导致数据一致性问题,节点变多就说明已然需要保证分区容错性
      • 各个节点间通过同步阻塞模式同步数据,也就是强一致性可以解决数据不一致的问题,但是会破坏可用性
        • 为了提高可用性,通过异步方式进行节点之间的数据同步,会丢数据。弱一致性
        • 为了不让数据丢失,通过其他中间件介入,保证数据最终在某个时间点上各个节点数据一。最终一致性
    • 主从/主备模式,需要对主做高可用。那么对主进行监控的程序自身也是一个集群
    • 为了保证监控集群的可用性和准确性,最终需要监控集群中的节点是过半决策:

      • 如果少于一半就会导致决策不准确或决策混乱(脑裂、网络分区现象)
        • 并不是所有的脑裂现象都是坏事,因为有分区容忍性,eureka的注册服务
      • 超过太多不仅会导致浪费,还会降低可用性
        • 以5台机器的集群为例,一个是3个即可决策生效,那么可以容忍两台机器挂掉。一个是4台机器才可决定一个决策有效,那么只会容忍一台机器挂掉,而且4台机器出问题的概率相对于3台的概率要大。所以超过太多不仅会造成浪费还会造成集群的可用性降低
        • 那么一个集群的机器数量,以多少为宜呢?
          • 以上面的结论为例:4台机器的集群,过半是3台机器,那么只允许一台机器挂掉。如果是3台的集群,过半是2,依然允许一台机器挂掉。所以一般奇数台比较合适。偶数台相对奇数台更容易出现故障,发生风险的概率更高

            4.2.1.2.解决单点问题

    • Sentinel方式

      4.2.1.3.解决容量问题

    • 客户端解决 Sharding分片阶段

      • 按业务拆分多个节点

image.png

  1. - 如果数据依然很大,到了无法拆分的时候,怎么办?
  2. - 按一定规则拆分
  3. - 随机 random

image.png

  1. - hash取模 modual

image.png

  1. - 一致性Hash

image.png

4.2.1.4.解决连接问题

  • 代理
    • 代理层解决sharding分片
      • modual 取模
      • random 随机
      • kemata 一致性hash
    • 将客户端的处理逻辑迁移到代理层,客户端变得轻盈,而且变成了无状态服务

      4.2.1.5.常见的代理

  • twemproxy
  • codis
  • predixy
  • Redis Cluster 预分区

    4.2.3. sharding分片的问题

    数据分散在各个节点中,进行聚合操作很麻烦

    4.2.3.1. Redis Cluster

    4.3. Sentinel

  • 监控

  • 提醒
  • 自动的故障转移
  • 启动命令
    • redis-server ./26379.conf —sentinel
  • sentinel是如何通过只连主节点而知道有多少从节点呢?通过订阅redis中的一个消息队列实现的