REmoteDIctionaryServer(Redis)是一个开源(BSD协议)的,使用ANSI C语言编写,基于内存in-memory数据存储结构,可以当作NoSQL数据库,缓存和消息代理来使用。支持多种数据结构,包括string, hashes, lists, set, sorted sets, bitmaps, hyperloglogs等。此外,Redis还支持内置replication, LRU(Least Recently Used)算法管理缓存,Pub/Sub,部分原子性操作,简单事务支持,基于磁盘的数据持久化以及3.0实现了分布式Redis支持HA,久违的Redis Cluster。从2013年5月开始,Redis的开发由Pivotal公司主持。 饮水思源,Redis之父Salvatore Sanfilippo, 一名来自意大利西西里岛(Sicily)出生于1977年的程序员,网名Antirez, 常居住于Catania。Antirez的IT职业生涯开始于系统管理员,IT安全,并于Web 2.0的年代创立一家web网络公司,主要开发社交应用。之后,在一次实时统计分析产品开发中,为了节省成本以及高性能扩展性,Antirez意识到需要一种支持多种复杂数据结构的in-memory数据库,并且支持快速操作。Redis就此诞生并开源。之后,VM慧眼相中了Redis, 并雇佣Antirez去全职开发,而之后又衍生出Pivotal公司,Antirez则继续主持Redis开发。
Redis通常被大家称为数据结构服务器,一种轻量级K/V数据存储,如我们上文介绍,支持丰富的数据类型。Redis以其速度而闻名,这使得其称为某一特定领域适用的最优选择。 那请问大家是在什么场景下选用Redis呢?有小伙伴说大多数情况是大量数据需要缓存吧。 那又请问这种情况为什么不用Memcached呢?又或者干脆选择Java自带容器ArrayList, HashMap, ConcurrentHashMap, HashSet呢?我们带着这两个问题往下看。 我们先看各大社交网站Twitter, Weibo(微博是国内最大的Redis用户)。这些网站的每个用户都有好友,粉丝,关注等,并且可以相互查看共同好友等。Redis可以用来帮助高效管理这些社交关系,如求共同好友: 如上,Redis很方便的使用有序集合的交集功能实现。其中user:100000:follow为用户1的粉丝列表,user:200000:follow为用户2的粉丝列表,out:100000:200000为交集共同好友列表。 又如另一个常用场景,电商各种计数,如商品维度计数(关注度,喜欢数,评论数,浏览数等)。可以采用Redis的Hash类型,并借助其原子性操作来维护计数。 用户维度计数(动态,关注数,粉丝数,发帖数等) 当然除了这些互联网用途,Redis最常用的功能还是被当作缓存。提到缓存,大家当然会想到memcached。 Redis v.s. Memcached
好了,千言不抵一图:
可以看出,HashMap(上图显示为一根线)整体操作插入/查询/移除都 25 - 90倍快于Memcached/Redis; 而Memcached/Redis又 5 -12 倍快于MySQL InnoDB,符合预期。究其原因,Java容器单机内存直接读取,无网络开销,无须序列化等。 而反之之所以要用Redis的原因与好处则包括:
所以Redis适合全部数据都在内存的场景包括需要临时持久化,尤其作为缓存来使用,并支持对缓存数据进行简单处理计算;如涉及Redis与RDBMS双向同步的话,则需要引入一些复杂度。
Sentinel架构
Sentinel主要提供了集群管理,包括监控,通知,自动故障恢复。如上图,当其中一个master无法正常工作时,Sentinel将把一个Slave提升为Master, 从而自动恢复故障。而Sentinel本身也做到了分布式,可以部署多个Sentinel实例来监控Redis实例(建议基数,至少3个Sentinel实例来监控一组Redis Master/Slaves),多个Sentinel进程间通过Gossip协议来确定Master是否宕机,通过Agreement协议来决定是否执行故障自动迁移以及重新选主,整体设计类似ZooKeeper(应该是作者参考了当年的ZK吧?)。 Cluster架构 Redis Cluster开始设计于2011年(早于Sentinel),正式诞生于2015年愚人节,Redis 3.0,其中之苦辣酸甜只有Antirez自己知道。 如上图,从3.0开始, Redis从一个单纯的NoSQL内存数据库变成了一个真正分布式NoSQL数据库。 概括来说,所谓分布式即支持数据分片,并且自动管理故障恢复(failover)与备份(replication)。
如上图Redis Cluster采用了无中心结构,每个节点都保存,共享数据和集群状态,每个节点与其它所有节点通信,使用Gossip协议来传播及发现新节点,通过分区来提供一定程度可用性,当某个node的Master宕机时,Cluste会自动选举一个Slave形成一个新的Master,这里应该是借鉴,重用了Sentinel的功能。 另外,Redis Cluster并没有使用通常的一致性哈希, 而引入哈希槽的概念,Cluster中固定有16384个slot, 每个key通过CRC16校验后对16384取模来决定其对应slot的位置,而每个node负责一部分的slot管理,当node变化时,动态调整slot的分布,而数据则无须挪动。对于客户端来说,client可以向任意一个实例请求,Cluster会自动定位需要访问的slot。
然而,完全去中心化的架构同时也失去了一些灵活与总控能力,如可通过引入中央控制的自动发现节点的变化及时Rebalance,分区粒度的备份,故障时分区自动调整,Gossip消息的通信开销,路由表维护等。 值得一提的是,Redis的企业版/商业版Redis Labs Enterprise Cluster(RLEC)则似乎解决了我们上述问题,如引入了中央控制Cluster Manager来管理,监控,分片迁移等工作, 引入高性能Proxy隐藏幕后的路由实现等。 当然,前提是商业化付费版本了,我们也期待未来的开源Redis逐步可以引入这些概念。 上图列出了Redis内部底层的一些重要数据结构,包括List, Set, Hash, String等。 来看几个比较核心的的数据结构。 Redis 3.x后的redisObject如下图所示: redisObject定义中使用了位字段(bit filed)。简单来说,redisObject定义了类型,编码方式,LRU时间,引用计数,*ptr指向实际保存值指针。
举个具体例子,redisObject{type: REDIS_LIST, encoding:REDIS_ENCODING_LINKEDLIST}, 这个对象是Redis列表,其值保存在一个链表中,ptr指针指向这个列表。
毫无疑问的直接malloc开辟内存空间,设置type,encoding,引用计数默认1,设置默认LRU。 可以看出refcount,猜测其内部是基于引用计数来管理释放内存空间的。事实要以代码为准。 果不其然,Redis提供了incrRefCount与decrRefCount来管理对象跟踪对象的引用, 当减少引用时检测计数器为是否需要释放内存对象。
从redisDB来看,几个重要属性:
具体代码如下: 代码注释较为清晰,其中long long是C99标准新加的,64位长整型。 RedisServer代码在Redis 3.0支持Cluster后变得较复杂,我们只列出部分代码。 总体来说包含如下几个大部分:
上图字典的底层实现为哈希表,每个字典包含2个哈希表,ht[0], ht[1], 1号哈希表是在rehash过程中才使用的。而哈希表则由dictEntry构成。 代码层面,每个字典包含了3个内部数据结构:
|