我爱编程

Redis

2018-02-11  本文已影响0人  一凡呀

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
底层实现

简单动态字符串

常用的操作
List

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


image.png
底层实现

链表/压缩列表

常用的操作
Hash

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


image.png
底层实现

字典/压缩列表

常用操作

Set(无序集合)

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


image.png
底层实现

整数集合/字典

常用操作

Sorted Set(有序集合)

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


image.png
底层实现

跳表/压缩链表

常用操作
系统管理命令

使用场景:

底层数据结构

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[];//数据存储空间 }


链表

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时才会使用跳表,否则将使用后面提到的压缩列表

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快照:
优点:

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文件。

优点:
缺点:
appendfsync:控制AOF文件写入磁盘的时机

主从复制

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


image.png
主从复制的过程

当设置好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

上一篇下一篇

猜你喜欢

热点阅读