转载:Redis和Memcached的区别(数据类型、内存管理、数据持久化、集群管理)
转载:Redis的五种对象类型及其底层实现
转载:redis 的两种持久化方式及原理
转载:Redis学习笔记9--Redis持久化
转载:Redis Cluster集群的搭建与实践
转载:
Redis内部使用一个redisObject对象来表示所有的key和value。redisObject最主要的信息如图所示:
Redis对象底层数据结构
底层数据结构共有八种,如下表所示:
编码常量 | 编码所对应的底层数据结构 |
---|---|
REDIS_ENCODING_INT |
long 类型的整数 |
REDIS_ENCODING_EMBSTR |
embstr 编码的简单动态字符串 |
REDIS_ENCODING_RAW |
简单动态字符串 |
REDIS_ENCODING_HT |
字典 |
REDIS_ENCODING_LINKEDLIST |
双端链表 |
REDIS_ENCODING_ZIPLIST |
压缩列表 |
REDIS_ENCODING_INTSET |
整数集合 |
REDIS_ENCODING_SKIPLIST |
跳跃表和字典 |
1)String:int(数字)、raw(字符串)或者embstr(只读字符串,39字节以上转为raw)。
2)Hash:ziplist(压缩链表正向排序)或者hashtable(由两个字典构成:存储表和中转表)
3)List:ziplist或者linkedlist(双向链表)。
4)Set:intset(整数集合,支持16、32和64位三种格式)或者hashtable
5)Sorted Set:ziplist或者skiplist(跳表用于快速查找)与dict的结合
Redis的高效性和灵活性正是得益于对于同一个对象类型采取不同的底层结构,并在必要的时候对二者进行转换;以及各种底层结构对内存的合理利用。
Redis的数据持久化分为RDB、AOF、VM(废弃)和diskstore四种模式
1. 快照(snapshots)
默认redis是会以快照的形式将数据持久化到磁盘的(一个二进制文件,dump.rdb,这个文件名字可以指定),在配置文件中的格式是:save N M表示在N秒之内,redis至少发生M次修改则redis抓快照到磁盘。当然我们也可以手动执行save或者bgsave(异步)做快照。
优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。
工作原理:
每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。下面介绍
aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,如果redis发生故障,最多会丢失1s的数据;且如果日志写入不完整支持redis-check-aof来进行日志修复;AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)。
缺点:AOF文件比RDB文件大,且恢复速度慢。
当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要 通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)
appendonly yes //启用aof持久化方式
# appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no //完全依赖os,性能最好,持久化没保证
在Redis-2.4后虚拟内存功能已经被deprecated了,原因如下:
(1)重启太慢
(2)保存数据太慢
(3)上面两条导致 replication 太慢
(4)代码过于复杂
redis的虚拟内存与os的虚拟内存不是一码事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。redis没有使用os提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制,原因主要有两个:1. os 的虚拟内存是已4k页面为最小单位进行交换的。而redis的大多数对象都远小于4k,自己实现的虚拟内存可以进行精细的操作;2. redis可以将被交换到磁盘的对象进行压缩,相比os能够操作更多空间。
diskstore方式是作者放弃了虚拟内存方式后选择的一种新的实现方式,也就是传统的B-tree的方式。具体细节是:
1) 读操作,使用read through以及LRU方式。内存中不存在的数据从磁盘拉取并放入内存,内存中放不下的数据采用LRU淘汰。
2) 写操作,采用另外spawn一个线程单独处理,写线程通常是异步的,当然也可以把cache-flush-delay配置设成0,Redis尽量保证即时写入。但是在很多场合延迟写会有更好的性能,比如一些计数器用Redis存储,在短时间如果某个计数反复被修改,Redis只需要将最终的结果写入磁盘。这种做法作者叫per key persistence。由于写入会按key合并,因此和snapshot还是有差异,disk store并不能保证时间一致性。
Redis集群搭建的方式有多种,例如使用zookeeper等,但从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,具有线性可伸缩的功能,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。其redis-cluster架构图如下:
按照CAP理论来说,单机版的Redis属于保证CP(Consistency & Partition-Tolerancy)而牺牲A(Availability),也就说Redis能够保证所有用户看到相同的数据(一致性,因为Redis不自动冗余数据)和网络通信出问题时,暂时隔离开的子系统能继续运行(分区容忍性,因为Master之间没有直接关系,不需要通信),但是不保证某些结点故障时,所有请求都能被响应(可用性,某个Master结点挂了的话,那么它上面分片的数据就无法访问了)。
有了Cluster功能后,Redis从一个单纯的NoSQL内存数据库变成了分布式NoSQL数据库,CAP模型也从CP变成了AP。也就是说,通过自动分片和冗余数据,Redis具有了真正的分布式能力,某个结点挂了的话,因为数据在其他结点上有备份,所以其他结点顶上来就可以继续提供服务,保证了Availability。然而,也正因为这一点,Redis无法保证曾经的强一致性了。这也是CAP理论要求的,三者只能取其二。
其结构特点:
1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2、节点的fail是通过集群中超过半数的节点检测失效时才生效。
3、客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。
5、Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
四 Redis的内存优化
1. 关闭VM功能
首先最重要的一点是不要开启Redis的VM选项,即虚拟内存功能,这个本来是作为Redis存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高,并且我们后续会分析此种持久化策略并不成熟,所以要关闭VM功能,请检查你的redis.conf文件中 vm-enabled 为 no。
2. 设置maxmemory
其次最好设置下redis.conf中的maxmemory选项,该选项是告诉Redis当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的Redis不会因为使用了过多的物理内存而导致swap,最终严重影响性能甚至崩溃。
3. 设置内置数据类型的编码参数
Redis为不同数据类型分别提供了一组参数来控制内存使用,我们在前面详细分析过Redis Hash是value内部为一个HashMap,如果该Map的成员数比较少,则会采用类似一维线性的紧凑格式来存储该Map, 即省去了大量指针的内存开销,这个参数控制对应在redis.conf配置文件中下面2项:
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-entries
list-max-ziplist-entries 512 //list数据类型多少节点以下会采用去指针的紧凑存储格式。
list-max-ziplist-value 64 //list数据类型节点值大小小于多少字节会采用紧凑存储格式。
set-max-intset-entries 512 //set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。