Redis是一个开源的,基于内存的,也可进行持久化的,基于C语言编写的存储数据库。Redis能达到11w的QPS。Redis这么快的原因主要有以下几点:
完全基于内存,数据全部存储在内存中,读取时没有磁盘IO,所以速度非常快。
Redis采用单线程的模型,没有上下文切换的开销,也没有竞态条件,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
Redis项目中使用的数据结构都是专门设计的,例如SDS(简单动态字符串)是对C语言中的字符串频繁修改时,会频繁地进行内存分配,十分消耗性能,而SDS会使用空间预分配和惰性空间释放来避免这些问题的出现。
采用多路复用IO模型,可以同时监测多个流的IO事件能力,在空闲时,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态唤醒,轮询那些真正发出了事件的流,并只依次顺序的处理就绪的流。可以让单个线程高效的处理多个连接请求(尽量减少网络 I/O 的时间消耗)。
Redis使用场景
- 取最新N个数据的操作:可以将数据存在redis的list中。
- 排行榜应用,取TOP N操作:这个可以将数据存入sorted set中,把排序值设为score,根据score进行排序。
- 需要精准设定过期时间的应用:
- 计数器应用:redis的命令都是原子性的,你可以轻松的利用incr,decr命令来构建计数器系统。
- unique操作,获取所有数据的排重值:可以使用redis的set
- pub/sub构建实时消息系统
- 构建队列系统
- 缓存
Redis和Memcached的区别
数据类型支持不同
Memcached仅支持简单的key-value结构的数据记录不同,Redis支持的数据类型要丰富得多。最为常用的数据类型主要由五种:String、Hash、List、Set和Sorted Set。
Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB内存管理机制不同
在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘。Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作.
Redis和Memcached虽然都是基于C语言开发的,但是为了提高内存的管理效率,高效的内存管理方案都不会直接使用malloc/free调用。Redis和Memcached均使用了自身设计的内存管理机制,但是实现方法存在很大的差异。
Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。
Redis每一个数据块都是根据数据类型和大小进行分配的,这一块数据的元数据(比如数据块大小)会存入内存块的头部。Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。数据持久化支持
Redis虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB快照和AOF日志。而Memcached是不支持数据持久化操作的。集群管理的不同
Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。当客户端向Memcached集群发送数据之前,首先会通过内置的分布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,同样要计算出查询数据所在的节点,然后直接向该节点发送查询请求以获取数据。
Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。为了保证单点故障下的数据可用性,Redis Cluster引入了Master节点和Slave节点。在Redis Cluster中,每个Master节点都会有对应的两个用于冗余的Slave节点。这样在整个集群中,任意两个节点的宕机都不会导致数据的不可用。当Master节点退出后,集群会自动选择一个Slave节点成为新的Master节点。
Redis数据结构
Redis共有5种数据结构:string,list,set,zset,hash。 Redis内部使用一个redisObject对象来表示所有的key和value。redisObject的源码如下所示:
1 | typedef struct redisObject { |
type字段对应redis的5种数据结构。
encoding字段记录对象使用的编码(数据结构),可以通过命令 object encoding key 来查看redis对象中的encoding。redis主要有以下11种encoding。
- string
string是Redis最常用的数据结构,应用场景也比较广泛,可以用于缓存,计数器,用户session共享等。 - list
List 是有序列表,和java的list很像,我们可以通过 List 存储一些列表型的数据结构,类似首页推荐列表、文章的评论列表之类的东西。通过 lrange 命令,读取某个闭区间内的元素,可以基于 List 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高。 - set
Set 是无序集合,类似java的set,也可以去重。set可以存储一些需要去重的数据,虽然java的HashSet可以做到,但是我们的项目一般都是分布式的,需要全局去重,这时使用Redis的set再好不过了。
Set可以用来存储好友列表,这样可以通过set的取交集,差集等操作获取共同好友等。 Sorted set
Sorted set 是排序的 Set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。有序集合的使用场景与集合类似,但是set集合不是自动有序的,而Sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择Sorted set数据结构作为选择方案。
排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。hash
这个是类似 Map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 Hash 里的某个字段。一般可以使用hash存储用户的信息。
Redis持久化
Redis 提供了 RDB 和 AOF 两种持久化方式,RDB 是把内存中的数据集以快照形式写入磁盘,实际操作是通过 fork 子进程执行,采用二进制压缩存储;AOF 是以文本日志的形式记录 Redis 处理的每一个写入或删除操作。RDB持久化的特点是:文件小,恢复快,不影响性能,实时性低。AOF持久化的特点是:文件大,恢复慢,性能影响大,实时性高。
对于两种方式的选择,如果可以接受丢失十几分钟及更长时间的数据,可以选择RDB持久化,对性能影响小,如果只能接受秒级的数据丢失,只能选择AOF持久化。
AOF如果配置是everysec那么会每秒执行fsync操作,调用write写入磁盘一次,但是如果硬盘负载过高,fsync操作可能会超过1s,Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗更快,所以Redis的处理逻辑是会对比上次fsync成功的时间,如果超过2s,则主线程阻塞直到fsync同步完成,所以最多可能丢失2s的数据,而不是1s。
为了防止AOF文件越来越大,可以通过执行BGREWRITEAOF命令,会fork子进程出来,读取当前数据库的键值对信息,生成所需的写命令,写入新的AOF文件。在生成期间,父进程继续正常处理请求,执行修改命令后,不仅会将命令写入aof_buf缓冲区,还会写入重写aof_buf缓冲区。当新的AOF文件生成完毕后,子进程父进程发送信号,父进程将重写aof_buf缓冲区的修改命令写入新的AOF文件,写入完毕后,对新的AOF文件进行改名,原子的地替换旧的AOF文件。
Redis过期策略
- 惰性清除:在访问key时,如果发现key已经过期,那么会将key删除。
- 过期清理:Redis有定时任务会对过期的key进行清理(这个不会扫描所有过期的key)。
- 内存淘汰:当执行写入命令时,如果发现内存不够,那么就会按照配置的淘汰策略清理内存,淘汰策略主要由以下几种:
- noeviction:不删除,达到内存限制时,直接不执行命令返回错误信息。
- allkeys-lru:在所有key中,使用LRU算法,优先删除最近没有使用的key。
- allkeys-random:在所有key中,随机删除一部分key。
- volatile-lru:在设置了过期时间的key中,使用LRU算法,优先删除最近没有使用的key。
- volatile-random:在设置了过期时间的key中,随机删除一部分key。
- volatile-ttl:在设置了过期时间的key中,优先删除过期时间短的key。
- allkeys-lfu:在所有key中,使用LFU算法,优先删除最少使用的key。
- volatile-lfu:在设置了过期时间的key中,使用LFU算法,优先删除最少使用的key。
Redis部署方式
Redis主要有四种部署方式:单点(Standalone),主从(Master-Slave),哨兵(Sentinel),集群(Cluster)。
单点
最简单的部署方式,采用单个redis节点进行部署。不能保证高可用,也不能实现读写分离,一般生产环境不会采用这种方式。主从
采用多个redis节点进行部署,其中一个为master节点,其他为slave节点,主从节点一般部署在不同机器上。主从模式同样不能保证高可用,主节点挂了之后需手动将从节点切换成主节点,但是可以实现读写分离,一定程度上提高吞吐量。哨兵
Redis Sentinel是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。
哨兵模式工作原理:- 认定主节点主观下线:哨兵节点会定时向主节点发送ping命令,如果没有回复,代表主节点主观下线。
- 认定主节点客观下线:哨兵节点认定主节点主观下线后,会向其他哨兵节点发送sentinel is-master-down-by-addr命令,获取其他哨兵节点对该主节点的状态,当认定主节点下线的哨兵数量超过半数时,就认定主节点客观下线。
- 进行领导者哨兵选举:认定主节点客观下线后,各个哨兵之间相互通信,选举出一个领导者哨兵,由它来对主节点进行故障转移操作。
- 领导者哨兵进行故障转移:领导者哨兵节点首先会从从节点中选出一个节点作为新的主节点,向这个从节点发送slaveof no one命令,让其成为主节点,通过slaveof 命令让其他从节点成为它的从节点,将已下线的主节点更新为新的主节点的从节点。
集群
Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。
Redis Cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
Redis Cluster没有使用一致性hash,而是采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。
Redis Cluster并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。