Redis数据库特性分析
2015-03-31马豫星
马豫星
摘 要:Redis是一款开源的、网络化的、基于内存的、可进行数据持久化的Key-Value存储系统。详细介绍了redis数据库底层数据结构、数据库的持久化方式、数据库事务特性以及隐藏在设计之中的一些考量。阐明了Redis高效性的原因在于其精简高效的底层数据结构设计以及对具有高消耗的功能进行分散处理。
关键词:数据库;Redis;NoSQL;分散处理
中图分类号:TP316 文献标识码:A 文章编号:2095-1302(2015)03-00-02
0 引 言
随着互联网的发展以及Web 2.0的兴起,超大规模以及高并发的纯动态型网站日渐成为主流,由于SNS类网站在数据存取过程中有着实时性等刚性需求的原因,致使关系型数据库越来越不足以胜任,这使得目前NoSQL数据库慢慢成了人们所关注的焦点,并大有成为取代关系型数据库而成为未来主流数据存储模式的趋势。当前NoSQL数据库很多,大部分都是开源的,其中比较知名的有:MemcacheDB、Redis、Tokyo Cabinet、Flare、MongoDB、CouchDB、Cassandra、Voldemort等。本文主要介绍Redis,这是一款足以满足海量读写需求基于Key-Value数据存储方式的高性能NoSQL数据库。
1 Redis简介
Redis是一款开源的、网络化的、基于内存的、可进行数据持久化的Key-Value存储系统。它的数据模型建立在外层,类似于其它结构化存储系统,是通过Key映射Value的方式来建立字典以保存数据,有别于其它结构化存储系统的是,它支持多类型存储,包括String、List、Set、Sort set和Hash等,你可以在这些数据类型上做很多原子性操作。
在操作方面,Redis基于TCP协议的特性使得它可以通过管道的方式进行数据操作,Redis本身提供了一个可连接Server的客户端,通过客户端,可方便地进行数据存取操作。
2 Redis底层数据结构中的两种:字符串和字典
在Redis的内部, 数据结构类型值由高效的数据结构和算法进行支持, 并且在Redis 自身的构建当中,也大量用到了这些数据结构。
2.1 字符串
SDS(Simple Dynamic String,简单动态字符串)是Redis底层所使用的字符串表示,几乎所有的Redis模块中都用了SDS。用SDS取代C默认的char*类型。
因为char*类型的功能单一,抽象层次低,并且不能高效地支持一些Redis常用的操作,所以在Redis程序内部,绝大部分情况下都会使用SDS而不是char*来表示字符串。
在C语言中,字符串可以用一个\0结尾的char数组来表示。但是,它并不能高效地支持长度计算和追加这两种操作:
(1)计算字符串长度的复杂度为θ(N)。
(2)对字符串进行N次追加,必定需要对字符串进行N次内存重分配。
在Redis内部,字符串的追加和长度计算很常见,这两个简单的操作不应该成为性能的瓶颈。
另外,Redis除了处理C字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容,所以为了方便起见,Redis的字符串表示还应该是二进制安全的:程序不应对字符串里面保存的数据做任何假设,数据可以是以\0结尾的C字符串,也可以是单纯的字节数组,或者其他格式的数据。
考虑到这两个原因,Redis使用SDS类型替换了C语言的默认字符串表示:SDS既可高效地实现追加和长度计算,同时是二进制安全的。
值得一提的是,在Redis最初的设计中就加入了统计信息:
在设计SDS的时候,在内部使用了zmalloc与zfree来动态使用内存,并记录占有内存大小,方便计算Redis的性能。
2.2 字典
实现字典的方法有很多种:为了兼顾高效和简单性,Redis使用了哈希表。在实现哈希表时,有一个问题就是采用何种策略来解决碰撞问题。对于使用链地址法来解决碰撞问题的哈希表来说,哈希表的性能取决于哈希表大小与保存节点数量之间的比率:
(1)哈希表的大小与节点数量,比率在1:1时,哈希表的性能最好;
(2)如果节点数量比哈希表的大小要大很多的话,那么哈希表就会退化成多个链表,哈希表本身的性能优势便不复存在;
Redis保证当上述比率达到一定值时,会执行rehash操作,即对哈希表进行扩容或缩减。当扩容时,是以空间换取时间,当缩减时是以时间换空间。由此可以看出Redis对时间和空间的高效利用率。当然,rehash操作一般是渐进方式执行的。因为其中涉及到对整个哈希表的迁移,如果数据量很大,那么势必会影响系统的性能。
Redis使用了两种渐进式的rehash方式:
(1)每次执行一次添加、查找、删除操作,rehash都会被执行一次;
(2)当Redis的服务器常规任务执行时,rehash会被执行。在规定的时间内,尽可能地对数据库字典中那些需要rehash的字典进行rehash,从而加速数据库字典的rehash进程。
3 Redis的持久化方式:RDB与AOF
在运行情况下,Redis以数据结构的形式将数据维持在内存中,为了让这些数据在Redis重启之后仍然可用,Redis分别提供了RDB和AOF两种持久化模式。
RDB将数据库的快照以二进制的方式保存到磁盘中。在Redis运行时,RDB程序将当前内存中的数据库快照保存到磁盘文件中,在Redis重启动时,RDB程序可以通过载入RDB文件来还原数据库的状态。
AOF则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到AOF文件,以此达到记录数据库状态的目的。AOF更像是历史记录,记录所有运行过的命令。但是AOF文件就会随着时间持续增长,进而占据整个磁盘。为此,Redis设计了AOF重写机制,通过开启新线程,扫描数据库数据,将其转化为Redis命令,存入临时的AOF文件。当扫描完后,用临时文件代替AOF文件。这样一来,AOF文件中记录的命令就是最简洁的,因而不会占据很多空间。
4 Redis事务
4.1 一致性
Redis的一致性问题可以分为两部分来讨论:入队错误、执行错误。
在命令入队的过程中,如果客户端向服务器发送了错误的命令,Redis会拒绝执行事务,并返回失败信息。如果命令在事务执行的过程中发生错误,那么Redis只会将错误包含在事务的结果中,这不会引起事务中断或整个失败,不会影响已执行事务命令的结果,也不会影响后面要执行的事务命令,所以它对事务的一致性也没有影响。
4.2 隔离性
Redis是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis的事务是总是带有隔离性的。
4.3 原子性
在上述一致性的介绍中,可以看出在事务队列中,即使有命令执行错误,该事务也会执行完,符合原子性的要求。
4.4 持久性
因为事务不过是用队列包裹起了一组Redis命令,并没有提供任何额外的持久性功能,所以事务的持久性由Redis所使用的持久化模式决定:
在单纯的内存模式下,事务肯定是不持久的;
在RDB模式下,服务器可能在事务执行之后、RDB文件更新之前的这段时间失败,所以RDB模式下的Redis事务也是不持久的;
在AOF的“总是SYNC”模式下,事务的每条命令在执行成功之后,都会立即调用 fsync 或 fdatasync 将事务数据写入到AOF文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的;
其他AOF模式也和“总是 SYNC”模式类似,所以它们都是不持久的;
综上所述,Redis事务满足原子性、一致性、隔离性,不满足持久性。
5 结 语
本文详细介绍了Redis数据库数据结构、事务、持久化等特性,为读者深入理解Redis提供了帮助。
参考文献
[1] 李子骅.Redis入门指南[M].北京:人民邮电出版社,2013.
[2] 黄健宏.Redis设计与实现[M].北京:机械工业出版社,2014.
[3] 皮雄军.NoSQL数据库技术实战[M].北京:清华大学出版社,2015.
[4] Shashank Tiwari.深入NoSQL[M].北京:人民邮电出版社, 2012.
[5] 徐小威.非关系型数据库数据恢复技术研究[D].杭州,杭州电子科技大学,2014.