前言
Redis 是一种基于 键值对 的 NoSQL 数据库。与很多键值对数据库不同,Redis 提供了丰富的 值数据存储结构,包括 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、bitmap(位图)等等。
1. Redis的特性
- 速度快,最快可达到
10w QPS(基于 内存,C语言,单线程 架构); - 基于 键值对 (
key/value) 的数据结构服务器。全称Remote Dictionary Server。包括string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、bitmap(位图)。同时在 字符串 的基础上演变出 位图(BitMaps)和HyperLogLog两种数据结构。3.2版本中加入GEO(地理信息位置)。 - 丰富的功能。例如:键过期(缓存),发布订阅(消息队列),
Lua脚本(自己实现Redis命令),事务,流水线(Pipeline,用于减少网络开销)。 - 简单稳定。无外部库依赖,单线程模型。
- 客户端语言多。
- 持久化(支持两种 持久化 方式
RDB和AOF)。 - 主从复制(分布式的基础)。
- 高可用(
Redis Sentinel),分布式(Redis Cluster)和 水平扩容。
2. Redis的应用场景
2.1 缓存
合理的使用 缓存 能够明显加快访问的速度,同时降低数据源的压力。这也是 Redis 最常用的功能。Redis 提供了 键值过期时间(EXPIRE key seconds)设置,并且也提供了灵活控制 最大内存 和 内存溢出 后的 淘汰策略。
2.2 排行榜
每个网站都有自己的排行榜,例如按照 热度排名 的排行榜,发布时间 的排行榜,答题排行榜 等等。Redis 提供了 列表(list)和 有序集合(zset)数据结构,合理的使用这些数据结构,可以很方便的构建各种排行榜系统。
2.3 计数器
计数器 在网站应用中非常重要。例如:点赞数加 1,浏览数 加 1。还有常用的 限流操作,限制每个用户每秒 访问系统的次数 等等。Redis 支持 计数功能(INCR key),而且计数的 性能 也非常好,计数的同时也可以设置 超时时间,这样就可以 实现限流。
2.4 社交网络
赞/踩,粉丝,共同好友/喜好,推送,下拉刷新等是社交网站必备的功能。由于社交网站 访问量通常比较大,而且 传统的数据库 不太适合保存这类数据,Redis 提供的 数据结构 可以相对比较容易实现这些功能。
2.5 消息队列
Redis 提供的 发布订阅(PUB/SUB)和 阻塞队列 的功能,虽然和专业的消息队列比,还 不够强大,但对于一般的消息队列功能基本满足。
3. 数据结构和内部编码
redis支撑五种数据类型,它们分别是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),但这些只是 Redis 对外的 数据结构。如图所示:
对于每种 数据结构,实际上都有自己底层的 内部编码 实现,而且是 多种实现。这样 Redis 会在合适的 场景 选择合适的 内部编码,如图所示:
可以看到,每种 数据结构 都有 两种以上 的 内部编码实现。例如 list 数据结构 包含了 linkedlist 和 ziplist 两种 内部编码。同时有些 内部编码,例如 ziplist,可以作为 多种外部数据结构 的内部实现,可以通过 object encoding 命令查询 内部编码:
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"quicklist"
复制代码
可以看到键 hello 对应值的 内部编码 是 embstr,键 mylist 对应值的 内部编码 是 ziplist。
Redis 这样设计有两个好处:
- 其一:可以改进 内部编码,而对外的 数据结构 和 命令 没有影响。例如
Redis3.2提供的quicklist,结合了ziplist和linkedlist两者的优势,为 列表类型 提供了一种 更加高效 的 内部编码实现。 - 其二:不同 内部编码 可以在 不同场景 下发挥各自的 优势。例如
ziplist比较 节省内存,但是在列表 元素比较多 的情况下,性能 会有所 下降,这时候Redis会根据 配置,将列表类型的 内部实现 转换为linkedlist。
4. 单线程架构
Redis 使用了 单线程架构 和 I/O 多路复用模型 来实现 高性能 的 内存数据库服务。那为什么 单线程 还能这么快,下面分析原因:
4.1 纯内存访问
Redis 将所有数据放在 内存 中,内存的 响应时长 大约为 100 纳秒,这是 Redis 达到 每秒万级别 访问的重要基础。
4.2 非阻塞I/O
Redis 使用 epoll 作为 I/O 多路复用技术 的实现,再加上 Redis 自身的 事件处理模型 将 epoll 中的 连接、读写、关闭 都转换为 事件,从而不用不在 网络 I/O 上浪费过多的时间,如图所示:
4.3 单线程避免线程切换和竞态产生的消耗
采用 单线程 就能达到如此 高的性能,那么不失为一种不错的选择,因为 单线程 能带来几个好处:
- 单线程 可以简化 数据结构和算法 的实现,开发人员不需要了解复杂的 并发数据结构。
- 单线程 避免了 线程切换 和 竞态 产生的消耗,对于服务端开发来说,锁和线程切换 通常是性能杀手。
单线程 的问题:对于 每个命令 的 执行时间 是有要求的。如果某个命令 执行过长,会造成其他命令的 阻塞,对于
Redis这种 高性能 的服务来说是致命的,所以Redis是面向 快速执行 场景的数据库。
5. Redis的多种部署模式
单机模式、主从模式、哨兵模式、集群模式
5.1 单机模式
这个最简单,一看就懂。
就是安装一个redis,启动起来,业务调用即可。具体安装步骤和启动步骤就不赘述了,网上随便搜一下就有了。
单机在很多场景也是有使用的,例如在一个并非必须保证高可用的情况下。
咳咳咳,其实我们的服务使用的就是redis单机模式,所以来了就让我改为哨兵模式。
说说单机的优缺点吧。
优点:
- 部署简单,0成本。
- 成本低,没有备用节点,不需要其他的开支。
- 高性能,单机不需要同步数据,数据天然一致性。
缺点:
- 可靠性保证不是很好,单节点有宕机的风险。
- 单机高性能受限于CPU的处理能力,redis是单线程的。
单机模式选择需要根据自己的业务场景去选择,如果需要很高的性能、可靠性,单机就不太合适了。
5.2 主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。
前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
主从模式配置很简单,只需要在从节点配置主节点的ip和端口号即可。
slaveof <masterip> <masterport>
# 例如
# slaveof 192.168.1.214 6379
复制代码
启动主从节点的所有服务,查看日志即可以看到主从节点之间的服务连接。
从上面很容易就想到一个问题,既然主从复制,意味着master和slave的数据都是一样的,有数据冗余问题。
在程序设计上,为了高可用性和高性能,是允许有冗余存在的。这点希望大家在设计系统的时候要考虑进去,不用为公司节省这一点资源。
对于追求极致用户体验的产品,是绝对不允许有宕机存在的。
主从模式在很多系统设计时都会考虑,一个master挂在多个slave节点,当master服务宕机,会选举产生一个新的master节点,从而保证服务的高可用性。
主从模式的优点:
- 一旦 主节点宕机,从节点 作为 主节点 的 备份 可以随时顶上来。
- 扩展 主节点 的 读能力,分担主节点读压力。
- 高可用基石:除了上述作用以外,主从复制还是哨兵模式和集群模式能够实施的基础,因此说主从复制是Redis高可用的基石。
也有相应的缺点,比如我刚提到的数据冗余问题:
- 一旦 主节点宕机,从节点 晋升成 主节点,同时需要修改 应用方 的 主节点地址,还需要命令所有 从节点 去 复制 新的主节点,整个过程需要 人工干预。
- 主节点 的 写能力 受到 单机的限制。
- 主节点 的 存储能力 受到 单机的限制。
5.3 哨兵模式
刚刚提到了,主从模式,当主节点宕机之后,从节点是可以作为主节点顶上来,继续提供服务的。
但是有一个问题,主节点的IP已经变动了,此时应用服务还是拿着原主节点的地址去访问,这...
于是,在Redis 2.8版本开始引入,就有了哨兵这个概念。
在复制的基础上,哨兵实现了自动化的故障恢复。
如图,哨兵节点由两部分组成,哨兵节点和数据节点:
- 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
- 数据节点:主节点和从节点都是数据节点。
访问redis集群的数据都是通过哨兵集群的,哨兵监控整个redis集群。
一旦发现redis集群出现了问题,比如刚刚说的主节点挂了,从节点会顶上来。但是主节点地址变了,这时候应用服务无感知,也不用更改访问地址,因为哨兵才是和应用服务做交互的。
Sentinel 很好的解决了故障转移,在高可用方面又上升了一个台阶,当然Sentinel还有其他功能。
比如 主节点存活检测、主从运行情况检测、主从切换。
Redis的Sentinel最小配置是 一主一从。
说下哨兵模式监控的原理
每个Sentinel以 每秒钟 一次的频率,向它所有的 主服务器、从服务器 以及其他Sentinel实例 发送一个PING 命令。
如果一个 实例(instance)距离最后一次有效回复 PING命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例会被 Sentinel标记为 主观下线。
如果一个 主服务器 被标记为 主观下线,那么正在 监视 这个 主服务器 的所有 Sentinel 节点,要以 每秒一次 的频率确认 该主服务器是否的确进入了 主观下线 状态。
如果一个 主服务器 被标记为 主观下线,并且有 足够数量 的 Sentinel(至少要达到配置文件指定的数量)在指定的 时间范围 内同意这一判断,那么这个该主服务器被标记为 客观下线。
在一般情况下, 每个 Sentinel 会以每 10秒一次的频率,向它已知的所有 主服务器 和 从服务器 发送 INFO 命令。
当一个 主服务器 被 Sentinel标记为 客观下线 时,Sentinel 向 下线主服务器 的所有 从服务器 发送 INFO 命令的频率,会从10秒一次改为 每秒一次。
Sentinel和其他 Sentinel 协商 主节点 的状态,如果 主节点处于 SDOWN`状态,则投票自动选出新的主节点。将剩余的 从节点 指向 新的主节点 进行 数据复制。
当没有足够数量的 Sentinel 同意 主服务器 下线时, 主服务器 的 客观下线状态 就会被移除。当 主服务器 重新向 Sentinel的PING命令返回 有效回复 时,主服务器 的 主观下线状态 就会被移除。
哨兵模式的优缺点
优点:
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
- 主从可以自动切换,系统更健壮,可用性更高。
- Sentinel 会不断的检查 主服务器 和 从服务器 是否正常运行。当被监控的某个 Redis 服务器出现问题,Sentinel 通过API脚本向管理员或者其他的应用程序发送通知。
缺点:
- Redis较难支持在线扩容,对于集群,容量达到上限时在线扩容会变得很复杂。
5.4 集群模式
主从不能解决故障自动恢复问题,哨兵已经可以解决故障自动恢复了,那到底为啥还要集群模式呢?
主从和哨兵都还有另外一些问题没有解决,单个节点的存储能力是有上限,访问能力是有上限的。
Redis Cluster 集群模式具有 高可用、可扩展性、分布式、容错 等特性。
Cluster 集群模式的原理
通过数据分片的方式来进行数据共享问题,同时提供数据复制和故障转移功能。
之前的两种模式数据都是在一个节点上的,单个节点存储是存在上限的。集群模式就是把数据进行分片存储,当一个分片数据达到上限的时候,就分成多个分片。
数据分片怎么分?
集群的键空间被分割为16384个slots(即hash槽),通过hash的方式将数据分到不同的分片上的。
HASH_SLOT = CRC16(key) & 16384
复制代码
CRC16是一种循环校验算法,这里不是我们研究的重点,有兴趣可以看看。
这里用了位运算得到取模结果,位运算的速度高于取模运算。
有一个很重要的问题,为什么是分割为16384个槽?这个问题可能会被面试官随口一问
数据分片之后怎么查,怎么写?
读请求分配给slave节点,写请求分配给master,数据同步从master到slave节点。
读写分离提高并发能力,增加高性能。
如何做到水平扩展?
master节点可以做扩充,数据迁移redis内部自动完成。
当你新增一个master节点,需要做数据迁移,redis服务不需要下线。
举个栗子:上面的有三个master节点,意味着redis的槽被分为三个段,假设三段分别是07000,700112000、12001~16383。
现在因为业务需要新增了一个master节点,四个节点共同占有16384个槽。
槽需要重新分配,数据也需要重新迁移,但是服务不需要下线。
redis集群的重新分片由redis内部的管理软件redis-trib负责执行。redis提供了进行重新分片的所有命令,redis-trib通过向节点发送命令来进行重新分片。
如何做故障转移?
假如途中红色的节点故障了,此时master3下面的从节点会通过 选举 产生一个主节点。替换原来的故障节点。
此过程和哨兵模式的故障转移是一样的。