程序员Java学习笔记我爱编程

Redis基础知识总结

2017-02-26  本文已影响11177人  EakonZhao

本文为笔者对在学习Redis过程中所收集资料的一个总结,目的是为了以后方便回顾相关的知识,大部分为非原创内容。特此声明!

Redis是什么?

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

Redis的应用场景

  1. 用于做持久化存储:由于Redis拥有丰富的数据结构,所以可以存储多种类型的数据。同时,它在存储与获取某些数据的效率方面也优于关系型数据库。例如微博在存储关注列表粉丝列表时,就可以使用Redis中的hash sets数据类型用于存储;在记录用户发言数以及粉丝数时,就可以使用Redis中的string(counter)进行存储,避免了关系型数据库中的select count(*) from....,减小了系统开销。但是在实际生产中,一般不会单独使用Redis作为数据库。
  1. 用于数据缓存:Redis最适合所有数据in-momory的场景。

Redis的优点

  1. 性能很高:Redis支持超过100K每秒的读写速率
  1. 丰富的数据类型(相对于memcached来讲):Strings,Lists,Hashes,Sets,Ordered Sets
  2. 所有操作都是原子性的,不用担心发生并发问题。并且Redis还支持对几个操作合并之后的原子性操作
  3. 拥有其他丰富的特性,例如给Key设置expire过期时间等

Redis对大小写不敏感

数据类型

Strings

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

对String常用的操作命令

  • set(key, value):给数据库中名称为key的string赋予值value
Lists

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

  • rpush(key, value):在名称为key的list尾添加一个值为value的元素
Hashes

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

  • hset(key, field, value):向名称为key的hash中添加元素field<—>value
Sets(无序集合)

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

  • sadd(key, member):向名称为key的set中添加元素member
Soted Sets(有序集合)

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

  • zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。

系统管理

  • exists key:判断一个key是否存在。存在返回1,否则返回0

底层数据结构

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

简单动态字符串(Simple Dynamic String)
虽然Redis是使用C语言编写的,但是Redis中的字符串类型并不是直接搬用C语言的字符串。

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

  • 获取字符串长度时间更快(SDS为O(1)/C语言字符串为O(n))

链表
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插入新的节点);
  1. 链地址法解决hash地址冲突
  2. 随着哈希表中节点数量的增加,适当时候会进行rehash将哈希表的负载因子保持在一个合理的范围

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

只有当有序集合中的元素个数大于128时才会使用跳表,否则将使用后面提到的压缩列表

  • 跳跃表是有序集合的底层实现之一

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作为集合的底层实现。我们可以这样理解整数集合,他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。

整数集合数据结构
typedef struct intset{ //编码方式 uint32_t enconding; // 集合包含的元素数量 uint32_t length; //保存元素的数组 int8_t contents[]; }

整数集合结构图
  • encoding:用于定义整数集合的编码方式

在上述数据结构图中我们可以看到,intset 在默认情况下会帮我们设定整数集合中的编码方式,但是当我们存入的整数不符合整数集合中的编码格式时,就需要使用到Redis 中的升级策略来解决Intset 中升级整数集合并添加新元素共分为三步进行:

  1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
  2. 将底层数组现有的所有元素都转换成新的编码格式,重新分配空间
  3. 将新元素加入到底层数组中

整数集合升级不仅可以提高灵活性,还能节约内存。整数集合是集合键的底层实现之一。整数集合的底层实现为数组,这个数组以有序,无重复的范式保存集合元素,在有需要时,程序会根据新添加的元素类型改变这个数组的类型。同时,升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存。但是整数集合只支持升级操作,不支持降级操作。

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

压缩列表组成结构
  • zlbytes:用于记录整个压缩列表占用的内存字节

关于压缩列表的几点总结:

  • 压缩列表是一种为了节约内存而开发的顺序型数据结构

持久化

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提供两种持久化的机制

save 900 1 #900秒内有一条key数据被修改就生成RDB文件

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

  • appendfsync no:当设置appendfsync为no时,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,这完全依赖于操作系统。对于大多数Linux系统来说,一般是每30秒进行一次fsync,将缓冲区中的数据同步到磁盘上;

主从复制

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

主从复制过程

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

上一篇 下一篇

猜你喜欢

热点阅读