Redis

0.334字数 8630阅读 285

Redis是啥

Redis是一个开源的key-value存储系统,由于拥有丰富的数据结构,又被其作者戏称为数据结构服务器。它属于NoSQL(Not Only SQL)数据库中的键值(Key-Value)存储数据库,即它属于与MySQL和Oracle等关系型数据库不同的非关系型数据库。它与memcached类似,但是优于memcached。

Redis和Memocache的区别

1.Memocache仅支持字符串类型,而redis支持丰富的数据类型
2.Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
3.Redis Cluster 实现了分布式的支持,Memocache不支持

Redis的应用场景

1.用于持久化存储:由于Redis拥有丰富的数据结构,所以可以存储多种类型的数据。同时,它在存储与获取某些数据的效率方面也优于关系型数据库。
2.用于数据缓存:Redis最适合所有数据in-momory的场景。

Redis的优点

1.性能好,读写速率快
2.拥有丰富的数据结构,可以存储多种类型的数据(相对于memocached来说)
3.操作都是原子性的操作,不用担心并发问题,还支持多个操作合并的原子操作
4.拥有其他丰富的特性,比如给key设置expire过期时间

Redis对大小写不敏感

Redis为什么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis为什么是单线程

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)
另,采用单线程发挥不出多核cpu的性能,不过可以通过在单机开多个redis来完善

数据类型

String

字符串是Redis的一种最基本的数据类型。Redis字符串是二进制安全的,这意味着一个Redis字符串能包含任意类型的数据。一个字符串类型的变量最多能存储512M字节的内容。

image.png
底层实现

简单动态字符串

常用的操作
  • get(key):返回数据库中名称为key的string的value
  • getset(key, value):给名称为key的string赋予上一次的value
  • mget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的value
  • setnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为value
  • setex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间time
  • mset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value i
  • msetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value i
  • incr(key):名称为key的string增1操作
  • incrby(key, integer):名称为key的string增加integer
  • decr(key):名称为key的string减1操作
  • decrby(key, integer):名称为key的string减少integer
  • append(key, value):名称为key的string的值附加value
  • substr(key, start, end):返回名称为key的string的value的子串
List

Redis中的List是简单的字符串列表,可以按照插入的顺序排序。我们可以添加一个元素到列表的左边(头部)或者是右边(尾部)。对应的命令为LPUSH和RPUSH。


image.png
底层实现

链表/压缩列表

常用的操作
  • rpush(key, value):在名称为key的list尾添加一个值为value的元素
  • lpush(key, value):在名称为key的list头添加一个值为value的 元素
  • llen(key):返回名称为key的list的长度
  • lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)
  • ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素
  • lindex(key, index):返回名称为key的list中index位置的元素
  • lset(key, index, value):给名称为key的list中index位置的元素赋值为value
  • lrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素
  • lpop(key):返回并删除名称为key的list中的首元素 rpop(key):返回并删除名称为key的list中的尾元素
  • blpop(key1, key2,… key N, timeout):lpop命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对keyi+1开始的list执行pop操作
  • brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。
  • rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
Hash

Hash是字符串字段和字符串值之间的映射,因此他们是展现对象的完美数据类型。一个带有一些字段的hash仅仅需要一块很小的空间存储,因此我们可以存储数以百万计的对象在一个小小的redis实例当中。


image.png
底层实现

字典/压缩列表

常用操作
  • hset(key, field, value):向名称为key的hash中添加元素field<—>value
  • hget(key, field):返回名称为key的hash中field对应的value
  • hmget(key, field1, …,field N):返回名称为key的hash中field i对应的value
  • hmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value i
  • hincrby(key, field, integer):将名称为key的hash中field的value增加integer
  • hexists(key, field):名称为key的hash中是否存在键为field的域
  • hdel(key, field):删除名称为key的hash中键为field的域
  • hlen(key):返回名称为key的hash中元素个数
  • hkeys(key):返回名称为key的hash中所有键
  • hvals(key):返回名称为key的hash中所有键对应的value
  • hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value

Set(无序集合)

Redis集合(Set)是一个无序的字符串集合。我们可以在O(1)的时间复杂度(无论集合中有多少元素时间复杂度都是常量)完成添加、删除或者是查看元素是否存在。Redis集合拥有令人满意的不允许包含相同成员的属性。多次添加相同的元素,最终在集合里面只会有一个元素。实际上说这些就是意味着在添加元素的时候无须检测元素是否存在。一个关于Redis集合非常有趣的事情就是它支持一些服务端的命令从现有的集合出发去进行集合运算,因此我们可以在非常短的时间内合并(unions),求交集(intersections),找出不同的元素(difference of sets)。


image.png
底层实现

整数集合/字典

常用操作
  • sadd(key, member):向名称为key的set中添加元素member
  • srem(key, member) :删除名称为key的set中的元素member
  • spop(key) :随机返回并删除名称为key的set中一个元素
  • smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合
  • scard(key) :返回名称为key的set的基数
  • sismember(key, member) :测试member是否是名称为key的set的元素
  • sinter(key1, key2,…key N) :求交集
  • sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合
  • sunion(key1, key2,…key N) :求并集
  • sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合
  • sdiff(key1, key2,…key N) :求差集
  • sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合
  • smembers(key) :返回名称为key的set的所有元素
  • srandmember(key) :随机返回名称为key的set的一个元素

Sorted Set(有序集合)

Redis有序集合和普通集合非常类似,是一个没有重复元素的字符串集合。不同之处在于有序集合的所有成员都关联了一个评分,这个评分被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的。使用有序集合我们可以用非常快的速度(O(logN))添加、删除以及更新元素。因为元素是有序的,所以我们也可以很快地根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此我们能够使用有序集合作为一个没有重复成员的智能列表。在有序集合中,我们可以很快捷地访问一切我们所需要的东西:有序的元素、快速的存在性测试、快速访问集合的中间元素。简而言之,使用有序集合我们可以完成许多对性能有极端要求的任务,而这些任务是使用其他类型的数据库很难完成的。


image.png
底层实现

跳表/压缩链表

常用操作
  • zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。
  • zrem(key, member) :删除名称为key的zset中的元素member
  • zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment
  • zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素
  • zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素
  • zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素 zcard(key):返回名称为key的zset的基数 zscore(key, element):返回名称为key的zset中元素element的score zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素 zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素
系统管理命令
  • exists key:判断一个key是否存在。存在返回1,否则返回0
  • del key:删除一个key。成功返回,失败返回0
  • type key:返回key的数据类型:string、list、set、zset、hash。key不存在返回none
  • keys key-pattern:将所有能够匹配key-pattern的key都列出来
  • randomkey:随机返回一个key,如果此时数据库是空的,则返回一个为空的字符串
  • clear:清除界面
  • rename oldname newname:将key由原来的oldname改为newname,不管此时newname存不存在
  • renamenx oldname newname:将key由原来的oldname改为newname.如果此时newname存在则更改失败
  • dbsize:返回当前数据库中key的总数
    expire key :限定key的生存时间,命令的一般形式如expire name 30,意思是值为name的
    key只能存活30秒
  • ttl key:返回key剩余存活时间
  • flushdb:清除当前数据库中所有的key
  • flushall:清除数据库中所有的key
  • config get:读取Redis此时的配置参数
  • config set:设置Redis此时的配置参数
  • auth:密码认证
  • info:可以查询Redis几乎所有的信息

使用场景:

底层数据结构

1.简单动态字符串
2.链表
3.字典(Map)
4.跳表
5.整数集合
6.压缩列表
7.对象

简单动态字符串(Simple Dynamic String)

虽然Redis是使用C语言编写的,但是Redis中的字符串类型并不是直接搬用C语言的字符串。


/字符串对象底层结构/ struct sds{ int len;//buf已占用的空间长度 int free;//buf中剩余空间长度 char buf[];//数据存储空间 }


  • 获取字符串长度时间更快(SDS为O(1)/C语言字符串为O(n))
  • 避免了缓冲区溢出问题:当我们在对SDS进行修改之前Redis会预先检查所操作的SDS空间够不够。如果不够,则会拓展对应SDS空间之后再进行拼接等操作
  • 减少修改字符串时带来的内存分配问题
  • 二进制安全
  • 兼容部分C语言中有关字符串的函数
链表

Redis中的list底层使用的是双向链表

字典(Map)

在字典中,一个key和一个value关联,并且字典中的每个key都是独一无二的。

Redis字典使用的哈希表底层结构:

typedef struct dictht { //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 unsigned long sizemask; //该哈希表已有节点的数量 unsigned long used; }

哈希表节点:

typeof struct dictEntry{ //键 void *key; //值 union{ void *val; uint64_tu64; int64_ts64; } struct dictEntry *next; }

1.根据hash算法算出key的hash值然后分配存储空间(由于哈希表中没有记录链表尾节点的位置,所以是在链表的head插入新的节点);
2.链地址法解决hash地址冲突
3.随着哈希表中节点数量的增加,适当时候会进行rehash将哈希表的负载因子保持在一个合理的范围

跳表(skiplist)

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在对数期望时间下完成,并且比起平衡树来说,跳跃表的实现要简单直观得多。Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另外一个是在集群节点中用作内部数据结构。
只有当有序集合中的元素个数大于128时才会使用跳表,否则将使用后面提到的压缩列表

  • 跳跃表是有序集合的底层实现之一
  • 主要有zskiplist 和zskiplistNode两个结构组成
  • 每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的对象必须是唯一的
  • 节点按照分值的大小从大到小排序,如果分值相同,则按成员对象大小排序
    zskiplist(链表)数据结构:
typedef struct zskiplist { //表头节点和表尾节点 structz skiplistNode *header,*tail; //表中节点数量 unsigned long length; //表中层数最大的节点的层数 int level; }zskiplist;

zskiplistNode(节点)数据结构:

typedef struct zskiplistNode{    //层 struct zskiplistLevel{      //前进指针 struct zskiplistNode *forward;     //跨度 unsigned int span; } level[];   //后退指针 struct zskiplistNode *backward;   //分值 double score;   //成员对象 robj *obj; }
整数集合(Intset)

整数集合是集合建的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。我们可以这样理解整数集合,他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。
整数集合升级不仅可以提高灵活性,还能节约内存。整数集合是集合键的底层实现之一。整数集合的底层实现为数组,这个数组以有序,无重复的范式保存集合元素,在有需要时,程序会根据新添加的元素类型改变这个数组的类型。同时,升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存。但是整数集合只支持升级操作,不支持降级操作

压缩列表

压缩列表是列表键和哈希键的底层实现之一。当一个列表键只含少量列表项(一般是少于128)时并且每个列表项要么就是小整数,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
关于压缩列表的几点总结:

  • 压缩列表是一种为了节约内存而开发的顺序型数据结构
  • 压缩列表被用作列表键和哈希键的底层实现之一
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者是整数值
  • 添加新节点到压缩列表,可能会引发连锁更新操作

Redis持久化

Redis直接将数据存储在内存当中,但是并不是所有的数据都一直存储在内存中的(这是和memcached相比最大的一个区别)。Redis会缓存所有的key的信息,但是如果Redis发现内存的使用量超过了某一个阈值,就会触发swap操作。Redis会计算出哪些key对应的value需要swap到磁盘,然后再将这些key对应的value持久化到磁盘中同时清除内存中存储的对应的value。这种特性使得Redis可以保持超过其机器本身内存大小的数据。但是机器本身的内存必须可以有足够的空间存储所有的key,因为key是不会进行swap操作的。由于Redis将内存中的数据swap到磁盘中时,提供服务的主线程和进行swap操作的子线程会共享这部分内存。如果更新需要swap的数据,Redis将阻塞这个操作,直至子线程完成swap操作之后才可以进行修改
当从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就要从swap文件加载相应的数据,然后再返回给请求数据的一方。此时存在一个I/O线程池的问题。在默认情况下,Redis会出现阻塞,它要完成所有的swap文件的加载之后才会响应。这样的策略在客户端数量较少,进行批量操作的时候比较合适。但是如果将Redis应用在一个大型的网站中,这显然是无法满足高并发的需求的。所有Redis允许我们设置I/O线程池的大小,对需要从swap文件中加载对应数据的请求进行并发操作,减少阻塞时间。

Redis提供的两种持久化方式

RDB快照:
  • Redis支持将当前的快照存储为一个数据文件的持久化机制,即当前提到的RDB快照。Redis借助了fork命令的copy on write机制(私有内存非共享内存)。在生成快照时,将当前进程fork出一个子进程。接着在子进程中迭代循环所有的数据同时将数据存储到一个临时文件当中。当数据全部处理完之后,就通过原子性rename系统调用将临时文件重命名为RDB文件。值得注意的是,这样的文件生成机制可以保证RDB文件不会坏掉,即Redis的RDB文件总是可用的。但是,RDB有明显的不足-----一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍受的,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不高。但是对于另外一些对数据安全性要求极高的应用,无法容忍数据丢失的应用,RDB就无能为力了,所以Redis引入了另一个重要的持久化机制:AOF日志。
    我们可以通过Redis的save指令来配置快照生成的时机
    save 900 1 #900秒内有一条key数据被修改就生成RDB文件
优点:

RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。
RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。
RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

缺点:

如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。 虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。
每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。

AOF日志:

AOF日志是追加写入的日志文件。和一般数据库的binlog不同的是,AOF文件是可读性较强的纯文本。其中保存的内容即Redis一条条的标准指令。但是并不是所有的Redis指令都会记录在AOF文件中,只有会导致数据发生修改的指令才会追加到AOF文件中。随着记录的指令越来越多,文件会变得越来越大。此时Redis提供了一个叫做AOF rewrite的功能,可以重新生成一份新的并且更小的AOF文件。新的文件中针对同一条key只记录了最新的一次数据修改的指令。AOF的文件生成机制和RDB快照类似。再写入新文件的过程中,所有操作日志还是会写到旧的文件当中,同时会记录在内存缓冲区中。当rewrite操作完成后,会将所有缓冲区中的日志一次性写入临时文件并调用原子性的rename命令将新的AOF文件覆盖旧的AOF文件。

优点:
  • 使用 AOF 持久化会让 Redis 变得非常耐久(much more durable):你可以设置不同的策略,比如无fsync ,每秒钟一次 fsync,或者每次执行写入命令时fsync 。 AOF 的默认策略为每秒钟 fsync一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
  • AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
缺点:
  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒fsync的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
  • AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。) 测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。
appendfsync:控制AOF文件写入磁盘的时机
  • appendfsync no:当设置appendfsync为no时,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,这完全依赖于操作系统。对于大多数Linux系统来说,一般是每30秒进行一次fsync,将缓冲区中的数据同步到磁盘上;
  • appendfsync everysec:当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。所以结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。
  • 当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。

主从复制

为了保证单点故障下的数据可用性,Redis引入了Master节点和Slave节点。


image.png
  • master节点可拥有多个slave节点
  • 除了多个slave连到相同的master之外,slave也可以连接其他slave形成图状结构
  • 主从复制不会阻塞master。也就是说,当一个或多个slave与master进行初次同步数据时,master可以继续处理client发来的请求。相反slave在初次同步数据时则会阻塞,不能处理client的请求
  • 主从复制可以用来提高系统的可伸缩性,我们可以用多个slave专门用于client的读请求,比如sort操作可以使用slave来处理。也可以用来做简单的数据冗余
  • 可以在master禁用数据持久化,只需要注释掉master配置文件中的所有save配置,然后只在slave上配置数据持久化
主从复制的过程

当设置好slave服务器后,slave会建立和master的连接,接着发送sync命令。无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来。后台进程完成写文件后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存并恢复数据库快照到磁盘上。接着master就会把缓存的命令转发给slave,而且后续master收到的写命令都会通过开始建立的连接发送给slave。从master到slave的同步数据的命令和从client发送的命令使用相同的协议格式。当master和slave的连接断开时slave可以自动重新建立连接。如果master同时收到多个slave发来的同步连接命令,只会启动一个进程来写数据库镜像,然后发送给所有slave。

  1. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
  2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
  3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。

redis集群相关:
https://www.zhihu.com/question/21419897

改自:https://www.jianshu.com/p/717eaee97444

推荐阅读更多精彩内容