什么是redis
- 开源的key-value内存数据库。
- C语言实现。
- 单线程操作(6.0之后支持多线程)。
- 丰富的数据类型。
- 支持事务操作。
- 可作为缓存、数据库、消息中间件使用。
与memcache对比
对比项 | redis | memcache |
---|---|---|
集群 | 支持 | 支持 |
持久性 | 支持 | 不支持 |
分布式存储 | 一致性hash | 一致性hash |
数据一致性 | 单线程顺序提交 | 多线程,CAS保证并发一致性 |
事务支持 | 支持 | 不支持 |
数据类型 | 5种 | 1种,简单的key-value,value不再拆分 |
性能 | 100k一下小数据快(单核原因) | 100k以上大数据快(多核原因) |
5种数据类型
- string(字符串):String是简单的 key-value 键值对,value 不仅可以是 String,也可以是数字。
- hash(哈希):存储string类型的field-value表,适用于存储对象信息;例如存储一个用户,key是user对象hash,将用户的年龄、姓名等field各表示成为key-value。
- list(列表):简单的字符串列表,其内部实现是一个双向链表,支持双向操作,如从头插入,从尾部插入(或查找和遍历)。
- set(集合):String类型的无序集合,元素不允许重复;底层通过哈希表实现,所以操作复杂度是O1。
- zset(sorted set:有序集合): set的升级版,提供了排序功能。每个元素都会关联一个double类型的分数,由此进行排序。
redis为啥快
- 全部操作基于内存而非文件系统,不存在CPU和IO的瓶颈
- 文件存储基于hash表,大部分操作复杂度O1
- 数据结构简单,操作也简单
- 单线程,避免不必要的线程上下文切换,不用考虑锁等需要等待的操作
- 非阻塞IO,多路复用IO模型(提升吞吐量)
持久化
redis支持RDB和AOF两种持久化方式
-
RDB:将数据存储在硬盘(RDB)
-
AOF:将数据变化存储在硬盘,类似mysql binlog;与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。
-
RDB保存: RDB触发条件进行保存 执行SAVE命令(不推荐,会阻塞所以操作进程) 执行BGsave命令(推荐,开启子任务保存数据) 配置文件中配置条件进行定时保存
-
AOF保存: AOF不需要进行触发,redis自动将执行操作写入AOF文件,其提供了三种写入方式; 在了解AOF文件之前,我们需要了解缓冲区的概念,计算机为了避免频繁读取IO,会将数据写入缓存区,等待批量写入硬盘,但是由此产生故障丢失缓冲区内容的风险;同时操作系统提供了相关同步函数,支持强行写入硬盘。 always:命令写入AOF缓存区后,立即强制同步到硬盘。 no:命令写入AOF缓存区后,立即返回,由操作系统进行调度写入硬盘。 everysec(默认):命令写入AOF缓存区后,线程返回。同时有个子线程每秒强制同步一次。这种方式是前两者的折中。
-
AOF文件重写: 由于AOF文件越来越大,需要定期对AOF文件进行整理,比如整理无效命令(多次set同一个key、先set再del)、多条命令合并(多次sadd)等。
主从
redis主从是将一台服务器数据,单向同步到N台服务器,前者是主(master),后者是从(slave);复制只需要在从节点进行配置,主节点无需配置。
特点
- 数据冗余
- 故障恢复
- 负载均衡:读写分离
- 主从是哨兵模式和集群的基础
实现原理
- 从节点配置了主节点的host和port后,想主节点发送ping命令以及权限认证等,并建立起连接。
- 主节点通过心跳机制维持从节点的连接,并向从节点发送写命令;需要注意的是,主节点并不会等待从节点返回成功信息。
- REPLCONF ACK:为了解决从节点不返回成功消息从而可能导致发生的问题,从节点会定期发送自己的偏移量给主节点进行对比,主节点判断是否需要发送同步数据给从节点。
客户端使用:
可以通过实例化一个master连接N个slaver连接,但是考虑到slaver负载的情况,可以做一层HA,slaver连接到HA进行slaver转发。
哨兵
主从陌生并不能实现故障转移,哨兵基于Redis主从复制,主要作用是解决主节点故障恢复的自动化问题。
哨兵本质上是特殊的Redis节点,哨兵集群模式下可以配置至少有几个哨兵节点才能判断master故障。
作用
- 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
- 故障转移(Automatic failover):当主节点不能正常工作时,哨兵会一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
- 提供配置:客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址
- 通知:故障转移的结果发送给客户端,比如java客户端连接了哨兵,这故障转移后会将master节点发送给客户端,以便于重新初始化客户端
实现原理
- 向主节点发送命令获取其主从节点的配置。
- 通过心跳检测,判断主节点是否下线。
- 通过哨兵间命令进行主节点下线判断,1判断其他哨兵是否观察到主节点下线,2.判断观察到下线的哨兵是否大于配置数量
- 主节点确定下线后,通过Raft算法,向从节点发送选举请求,先到先得的推选主节点。
- 通过slaveof no one命令推选主节点,并通过slaveof命令让其他节点成为从节点。
客户端使用
- 通过连接到哨兵节点,获取master、slaver节点地址,进行实例化连接。
- 当master出现故障,通过哨兵的通知功能,通知客户端,重新实例化master以及slaver连接。
集群
redis的主从并不能解决数据存储受到单一机器的问题,通过集群主要实现两个功能:
- 数据分片存储
- 主从复制以及故障转移(类似哨兵)
数据分区方案:
在讲述redis集群之前,我们先看一下数据分区方案:
- 哈希取余
计算key的hash值,然后对节点数量进行取余,选择数据映射哪个机器上。
缺点:新增或删除节点时,系统中所有的数据都要重新映射,导致大量数据迁移 - 一致性哈希 将0--2^32-1(int类型长度)的hash值按照顺序大小组成一张逻辑上的圆环。
将机器按照ip或者hostname的hash映射到这个圆环上。
将key hash后,映射到圆环上,顺时针寻找到的第一台服务器即是数据存储的服务器。
其优点是当其中一台机器下线后,只会涉及到逆时针第一天机器上的数据迁移。
缺点:一致性hash并不能保证流量的均匀分布。 - 虚拟节点的一致性哈希分区
此方案在一致性hash基础上,引入虚拟节点这一抽象概念,虚拟节点称为槽(slot)。
数据在逻辑上落在槽中,每个节点都包含多个槽,槽解耦了数据和实际节点之间的关系,同时槽是数据迁移的基本单位。
一般槽和节点的对应关系是,槽数/节点数取模。
其优点是当节点下线之后,槽重新分配节点,数据仍然比较均匀。
集群方案
- redis使用了虚拟节点一致性哈希分区。
- redis中槽的数量为16384(不可更改)。
- Redis对key计算哈希值,使用的算法是CRC16。
- 根据哈希值,取模16384,计算数据属于哪个槽。
- 根据槽与节点的映射关系,计算数据属于哪个节点。
缺点
- key批量操作支持有限。如mset、mget(因为key可能不在同一节点)
- 事务有限(因为key可能不在同一节点)
- hash类型的数据不能映射到不同节点
- 集群有一个节点不可用,或者槽未分配情况下,整个集群将不可用
实现原理
- 在哨兵系统中,节点分为数据节点和哨兵节点,在集群中每个节点即是数据节点也是哨兵节点。
- 集群内部通过Gossip协议彼此通信,达到感知对方的过程。
- 当集群内节点出现故障后,集群从出现故障的主节点中选取从节点推举成主节点,同时其他从节点承担回复被线下的主节点恢复义务
- 新节点的master被推选出来后,通过clusterDelSlot撤销前一个主节点的槽,并通过clusterAddSlot把这些槽委派给自己
- 新节点向集群广播消息,通知从节点变成主节点。
客户端使用
- redis proxy ,例如推特的:twemproxy(会损失性能)。
- Redis集群对客户端通信协议做了比较大的修改,为了追求性能最大化,并没有采用代理的方式,而是采用客户端直连节点的方式。 因此对于从单机切换到集群的应用,需要修改客户端代码。
- 在集群模式下, Redis 接收任何见相关命令时都是首先计算出键对应的槽, 再根据槽找出对应的节点, 如果节点是自身, 则直接处理命令; 否则恢复 MOVED重定向错误, 通知客户端请求正确的节点。
- 节点对于不属于他的键命令只回复重定向, 并不负责转发。
- 集群模式下读写分离成本比较高, 可以直接扩展主节点数量来提高集群性能, 一般不建议集群模式下做读写分离。