跳转至

Redis

NoSQL

Not only SQL,非关系型数据库

  • K-V键值对
    • Redis和memcached
  • 文档型数据库
    • mongoDB

CAP和BASE

SQL数据库中是ACID,NoSQL中是CAP原理

CAP原理

  • C
    • consistency 强一致性
  • A
    • Availability 可用性
  • P
    • Partition tolerance 分区容错性

CAP的3进2

一个分布式系统**必须实现分区容错性**,CAP只能同时满足其中两个

  • CA
    • 传统的MySQL和Orcal数据库
  • AP
    • 大多数网站的架构选择
  • CP
    • Redis和MongoDB

BASE

为了解决关系数据库强一致性导致可用性降低的问题提出的解决方案,通过让系统放松对某一时刻数据的一致性要求来换取系统整体的伸缩性和性能上的改观

  • BA
    • 基本可以,Basically Available
  • S
    • 软状态,Soft State
  • E
    • 最终一致,Eventually consistent

分布式和集群

  • 分布式
    • 不同的多台服务器上部署不同的服务器模块,他们之间通过RPC(远程过程调用)来通信和调用
  • 集群
    • 不同的多台服务器上部署着相同的服务器模块,通过分布式调度软件统一调度

Redis及优缺点

Redis是Remote Dictionary Server(远程字典服务器),是**高性能的K/V分布式内存数据库**

相对于memcached的不同

  • Redis支持数据持久化,可以将内存中的数据保存到磁盘中,重启时再次加载到内存中
  • Redis支持丰富的数据结构,list,set,hash,zset
  • Redis是单进程单线程,Memcached是单进程多线程

缺点

  • Redis主要消耗内存资源,数据库容量受到**物理内存的限制**,不能用作海量数据的高性能读写
  • 因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上

Redis线程模型

  • 单线程模型处理客户端请求
  • 封装epoll函数实现读写事件的响应

为什么单线程效率还这么高

  • 纯内存操作
  • 核心是基于非阻塞的 IO 多路复用机制
  • 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题

Redis数据类型和应用场景

Redis五大数据类型,string,hash,list,set,zset

  • string(字符串)
    • 一个字符串类型的值能存储最大容量是512M
    • set/get
      • 设置/获取
    • setex
      • setex k4 10 v4
      • 设置过期时间10s,然后给k4赋值为v4
    • setnx
      • setnx k1 v11
      • 若k1存在,则不覆盖,否则,建立k1,并赋值v11
    • mset/get
  • hash(哈希)
  • list(列表)
    • lpush/rpush
      • 从左边/右边压入
    • lrange
      • 指定范围,从左边获取
    • lpop/rpop
      • 从左边/右边弹出
  • set(集合)
  • zset(有序集合)
    • Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构

Redis持久化机制RDB和AOF

  • 默认是RDB持久
  • redis 的持久化机制,将数据写入内存的同时,异步的慢慢的将数据写入磁盘文件里,进行持久化,RDB是数据,AOF是写命令。
  • 同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

RDB

RDB(Redis Database)持久化机制,是对redis中的**数据**执行**周期性**的持久化,生成dump.rdb文件。

  • snapshot快照
    • 在指定时间间隔将内存的数据集快照写入磁盘,恢复时将快照直接读入内存
    • 默认rdb参数,可以通过save 秒数 次数来修改rdb参数
      • 1分钟改1万次
      • 5分钟改10次
      • 15分钟改1次
  • 当执行save,bgsave,flushall,shutdown命令时,也会生成快照文件dump.rdb,其中flushall会生成空的dump文件。
  • save和bgsave
    • save立即保存,不会等待RDB参数,另外save只保存,其他不管,因此会阻塞
    • bgsave下Redis会在后台异步进行快照操作,同时相应客户端请求
  • 恢复

    • redis启动时,会自动加载dump.rdb文件,将快照加载到内存中
  • 优点

    • RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备
      • 我们知道RDB每次生成的新dump文件都会覆盖旧文件,如果想保存每个时刻写入的快照文件,可以另外修改代码,按时间生成的dump文件,并传到备份机器。
    • RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
    • 大规模数据比AOF快,直接将数据恢复到内存中,想对于AOF回放要快
  • 缺点
    • 一定时间间隔做一次持久化,若redis意外宕机,则会丢失最后一次快照的修改
    • 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF

AOF(Append Only File) 机制对**每条写命令**作为日志,以 append-only(追加) 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集,生成appendonly.aof文件

  • 追加方式
    • everysec
      • 异步持久化,默认是每秒进行记录,最多丢失一秒的数据
    • always
      • 同步持久化,每次修改都会同步,redis性能会大大降低
  • 恢复
    • 正常恢复
      • 启动appendonly yes
      • 将数据aof复制到对应目录
      • 重新启动redis
    • 异常恢复
      • redis-check-aof –fix appendonly.aof
      • dump也可以这样修复
  • rewrite

    • AOF会越来越大,文件会越来越大,redis默认每个文件64M,当超过上一个文件大小一倍时,启动重写
    • 在rewrite log的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。
    • 在创建新日志文件的时候,老的日志文件还是照常写入。
    • 每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建
    • 当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
  • 优点

    • AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
    • AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合**做灾难性的误删除的紧急恢复**。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
  • 缺点
    • 类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。
    • 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大

Redis事务

Redis事务操作

Redis过期策略和缓存淘汰策略

过期策略

redis 过期策略是:定期删除+惰性删除

  • 定期删除
    • redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
  • 惰性删除
    • 在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

淘汰策略

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key**(这个是最常用的)**。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
  • volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间中**,移除最近最少使用的 key**(这个一般不太合适)**。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

  • 首先缓存预热,将我们认为是热点的数据放在redis中,然后根据请求更新数据,redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

Redis主从复制+Redis哨兵

Redis实现高并发和高可用,可以使用Redis主从架构+哨兵或Redis集群两种方式

Redis集群

分布式算法一致性哈希

普通哈希

  • 原始的做法是对缓存项的键进行哈希,将hash后的结果对**缓存服务器的数量**进行取模操作,通过取模后的结果,决定缓存项将会缓存在哪一台服务器上。
  • 缺点
    • 当服务器数量发生变化的时候,所有缓存在一定时间内是失效的,重新哈希,跟服务器结点的数量有关
    • 当应用无法从缓存中获取数据时,则会向后端服务器请求数据,造成了缓存的雪崩,整个系统很有可能被压垮

一致性哈希

  • 环形hash空间的概念

    • 通常hash算法都是将value映射在一个32位的key值当中,那么把数据首尾相接就会形成一个圆形,取值范围为0 ~ 2^32-1,这个圆环就是环形hash空间。
  • 一致性哈希步骤

    • 首先求出服务器(节点)的哈希值,并将其配置到0~2^32-1的圆(continuum)上。
    • 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
    • 然后从数据映射到的位置开始**顺时针查找**,将数据保存到找到的第一个服务器上。如果超过232-1仍然找不到服务器,就会保存到第一台服务器上。(0和232重合,超过后就顺序找到第一台了)
  • 当发生服务器结点变化

    • 一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
    • 一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性

虚拟结点

一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。

  • rehash实现虚拟结点进行平衡
    • 对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点
    • 数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上,这样就解决了服务节点少时数据倾斜的问题。
    • 在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。

Redis哈希槽

哈希槽

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

Redis集群最多结点数量

16384

新增结点,哈希槽如何重新分配

每个结点从自己的哈希槽中分出若干,分配给新结点,最终使所有节点负责的哈希槽数量基本一致

Redis和MySQL缓存和数据库的双写一致性

缓存血崩和缓存穿透及解决方案

缓存血崩

缓存宕机,此时所有请求落到数据库上,由于高并发的请求,导致数据库宕机,发生缓存血崩。

  • 解决方案

    • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
    • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
    • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
  • 请求流程

    • 用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
    • 限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
  • 好处

    • 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
    • 只要数据库不死,就是说,对用户来说,部分请求都是可以被处理的。

缓存穿透

发来的请求或恶意攻击,此时的数据在缓存和数据库中都没有,因此全部在数据库中查询,导致缓存穿透;若每次都有大量穿透,直接查数据库,则数据库宕机。

  • 解决方案
    • 每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set key UNKNOWN。
    • 然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据

缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库。

  • 解决方案
    • 将热点key设置为永不过期

Redis常见问题及解决方案

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。