NoSQL数据库之Redis(理论篇)

x33g5p2x  于2020-10-30 发布在 Redis  
字(8.6k)|赞(0)|评价(0)|浏览(669)

1**、Redis概述**

Redis(RemoteDictionary Server的缩写)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,提供多种语言的API。它的出现在很大程度上补偿了Memcached这类key/value缓存系统的不足,在部分场合亦可以对关系型数据库起到很好的补充作用。

同Memcached缓存系统类似,但它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set——有序集合)和hash(哈希类型),同时,为了保证效率,数据都是缓存在内存中。不同的是,Redis会周期性地把更新的数据写入磁盘或者把修改操作以追加的方式写入到记录文件,并且在此基础上实现了master-slave(主从式)同步。

关于Redis的主要使用案例包括缓存、会话管理、PUB/SUB以及排行榜等。可以这么说,Redis是当前最受欢迎的键值存储数据库,它由BSD授权、采用优化的C语言代码编写,并支持众多的开发语言。由于其速度和易用性,Redis已经成为需要一流性能的Web、移动、游戏、广告技术、物联网应用程序的理想之选。

2**、Redis的优势**

A**、超快速的性能**

Redis的所有数据均位于其服务器的主内存中,而大部分的数据库管理系统则将数据存储在磁盘或SSD上。由于不再需要访问磁盘,Redis等内存数据库可以避免时间延迟,借助较简单的使用较少CPU指令的算法访问数据。执行典型操作的时间通常不超过1毫秒。

B**、内存数据结构**

Redis允许客户存储映射到各种数据类型的键,基本的数据类型是字符串,它可以是大小最多为512MB的文本或二进制数据。Redis还支持按添加顺序列出的字符串列表、无序字符串的集合、按分数排序的有序集、存储一系列字段和值的哈希表、计算一个数据集中唯一项目的HyperLogLog。使用Redis可以在内存中几乎存储所有类型的数据。

C、通用性和易用性

Redis带有许多工具,借助这些工具,可以加快开发和运营的进程并使之简单化,其中包括Pub/Sub,它会将要提供给订阅者的消息发布到渠道,是聊天和消息传送系统的理想之选;TTL密钥,它们可以拥有确定的生存时间,在该时间之后,它们会将自身删除,这有助于避免数据库中充满不需要的数据;原子计数器,可以确保竞争条件不会产生不一致的结果。

D、复制****和持久化

Redis采用的是主从架构,并支持异步复制,用户执行此类复制时可以将数据复制到多个从属服务器。它能够提供更好的读取性能 (因为在服务器之间可以拆分请求)和恢复功能(主服务器发生中断时)。

为了提供持久性,Redis支持时间点快照(将Redis数据集复制到磁盘)和创建仅附加文件(AOF),以在写入每个数据变更项时将其存储到磁盘。如果发生中断,这两种方法中的其中任何一种都能使Redis数据快速恢复。

E**、支持广泛的开发语言**

很难想象,Redis提供了一百多个开源客户端供其开发者使用。支持的语言包括Java、Python、PHP、C、C++、C/#、JavaScript、Ruby、R、Go等。

3**、Redis的使用案例**

A**、缓存**

Redis位于另一数据库的“前端”,可以创建一个高性能的内存中的缓存,以降低访问延迟、提高吞吐量、减轻关系型数据库或NoSQL数据库的负载。

B、会话管理

Redis非常适合会话管理任务,可借助会话密钥上适当的TTL直接使用 Redis作为快速键值存储库,来管理会话信息。一些在线应用程序(包括游戏、电子商务网站和社交媒体平台) 通常都需要进行会话管理。

C、实时排行榜

借助Redis有序集数据结构,元素可以保留在列表中,并按其分数进行排序。这样,用户便能够轻松创建动态排行榜,以展示谁是游戏赢家,或谁发布了最受欢迎的消息,或用于展示谁处于领先地位的任何其他内容。

D**、速率限制**

需要时,Redis可以测量并限制事件的速率。您可以使用与客户端API密钥关联的Redis计数器,计算某个特定时间段内的访问请求数量,并在超过限制时采取一定的措施。限速器通常用于限制论坛上的帖子数量、限制资源使用率,以及控制垃圾邮件的影响。

E**、**队列

借助Redis列表数据结构,客户能够轻松实施一个轻量级、持久化的队列。这类列表提供了原子操作和屏蔽功能,适用于各种需要可靠消息代理或循环表的应用程序。

F**、聊天和消息传送**

Redis支持PUB/SUB标准功能和模式匹配。这使得Redis能够支持高性能的聊天室、实时评论流以及服务器内部通信。您也可以使用PUB/SUB基于发布的事件触发操作。

4**、Redis常用数据类型**

与Memcached仅支持简单的key-value结构的数据记录不同,Redis支持的数据类型要丰富得多。最为常用的数据类型主要有五种:String、Hash、List、Set和Sorted Set。

A、Redis核心对象

Redis内部使用一个redisObject对象来表示所有的key和value。redisObject最主要的信息如下图所示:

type**:**代表一个value对象具体是何种数据类型;

encoding**:**代表不同数据类型在Reids内部的存储方式,比如:type=string,代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int,则代表实际Redis内部是按数值型类型存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如“123”,“456”这样的字符串;

**vm字段:**只有打开了Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。

B**、Redis五种常用数据类型**

(1**)**String

**常用****命令:**set/get/decr/incr/mget等;

**应用****场景:**String是最常用的一种数据类型,普通的key/value存储都可以归为此类;

**实现****方式:**String在Redis内部的存储,默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

(2**)**Hash

**常用命令:**hget/hset/hgetall等;

**应用场景:**我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;

**实现方式:**Redis的Hash实际上是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。Key是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转换成真正的HashMap,此时encoding为ht。

(3**)**List

**常用命令:**lpush/rpush/lpop/rpop/lrange等;

**应用场景:**Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;

**实现方式:**Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

(4**)**Set

**常用命令:**sadd/spop/smembers/sunion等;

**应用场景:**Redis set对外提供的功能与list类似,是一个列表的功能,特殊之处在于set是可以自动去重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否存在于一个set集合内的重要接口,这个也是list所不能提供的;

**实现方式:**set的内部实现是一个value永远为null的HashMap,实际就是通过计算hash的方式来快速去重,这也是set能提供判断一个成员是否存在于集合内的原因。

(5**)SortedSet**

**常用命令:**zadd/zrange/zrem/zcard等;

**应用场景:**Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供的一个优先级(score)参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

**实现方式:**Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

5**、Redis的持久****化**

Redis虽然是基于内存的存储系统,但它本身是支持内存数据持久化的,而且提供两种主要的持久化策略:RDB快照和AOF日志。

A、RDB快照

Redis支持将当前数据的快照存成一个数据文件的持久化机制。但是一个持续写入的数据库如何生成快照呢?Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。

我们可以通过Redis的save指令来配置RDB快照生成的时机,比如你可以配置当1分钟以内有10000次写入就生成快照,也可以配置当5分钟内有10次写入就生成快照,也可以多个规则一起实施,如下图所示。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。

Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。同时,Redis的RDB文件也是Redis主从同步内部实现中的一个环节。

但是,我们可以很明显的看到,RDB有它的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢失了。在某些业务下,这是可以忍受的,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不会很高。但是对于另外一些对数据安全性要求极高的应用,无法容忍数据的丢失,这时RDB就无能为力了,所以Redis引入了另一个重要的持久化机制:AOF日志。

B**、AOF日志**

AOF日志的全称是Append Only File,从名字上我们就能看出来,它是一个追加写入的日志文件。同一般数据库的binlog不同的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。当然,并不是发送给Redis的所有命令都要记录到AOF日志里面,只有那些会导致数据发生修改的命令才会追加到AOF文件。

既然每一条修改数据的命令都会生成一条日志,那么AOF文件是不是会很大?答案是肯定的,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像老AOF文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重写操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中,然后调用原子性的rename命令用新的AOF文件取代老的AOF文件。

AOF是一个写文件操作,其目的是将操作日志写到磁盘上,所以它也同样会遇到我们上面说的写操作的5个流程。那么写AOF的操作安全性又有多高呢。实际上这是可以设置的,在Redis中对AOF调用write(2)写入后,何时再调用fsync将其写到磁盘上,通过appendfsync选项来控制,下面appendfsync的三个设置项,安全强度逐渐变强。

1)appendfsync no

当设置appendfsync为yes的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。

2)appendfsync everysec

当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时,Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞,所以结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。

3)appednfsync always

当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。这三种appendfsync方式的配置如下图所示。

6、Redis内存管理与****数据淘汰机制

A**、**Redis内存管理机制

Redis的内存管理机制主要通过源码中的zmalloc.h和zmalloc.c两个文件来实现。Redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存的头部。

如下图所示,real_ptr是Redis调用malloc后返回的指针,Redis将内存的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr。当需要释放内存的时候,ret_ptr被传给内存管理程序,通过ret_ptr,程序可以很容易的计算出real_ptr的值,然后将real_ptr传给free释放内存。

Redis通过定义一个数组来记录所有的内存分配情况,这个数组的长度为ZMALLOC_MAX_ALLOC_STAT,数组的每一个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标。在源码中,这个数组为zmalloc_allocations,zmalloc_allocations[16]代表已经分配的长度为16bytes的内存块的个数。zmalloc.c中有一个静态变量used_memory用来记录当前分配的内存总大小。所以,总的来看,Redis采用的是包装的mallc/free,相较于Memcached的内存管理方法来说,要简单很多。

B**、最大内存设置**

默认情况下,在32位操作系统中,Redis最大使用3GB的内存,在64位操作系统中,则没有限制。在使用Redis时,应该对数据占用的最大空间有一个基本准确的预估,并为Redis设定最大使用的内存,否则,在64位操作系统中,Redis会无限制地占用内存(当物理内存被占满后会使用swap空间),容易引发各种各样的问题。

通过如下配置控制Redis使用的最大内存:

maxmemory 100mb

在内存占用达到了maxmemory后,再向Redis写入数据时,Redis会按照下面两种方式执行操作:

n根据配置的数据淘汰策略尝试淘汰数据,释放空间;

n如果没有数据可以淘汰,或者没有配置数据淘汰策略,那么Redis会对所有写请求返回错误,但读请求仍然可以正常执行。

在为Redis设置maxmemory时,需要注意:如果采用了Redis的主从同步,主节点向从节点同步数据时,会占用掉一部分内存空间,如果maxmemory过于接近主机的可用内存,可能会导致数据同步时内存不足。所以,设置的maxmemory不要过于接近主机可用的内存,预留一部分内存用作主从同步。

C**、**数据淘汰机制

Redis提供了如下5种数据淘汰策略:

volatile-lru:使用LRU算法进行数据淘汰(淘汰上次使用时间最早的,且使用次数最少的key),只淘汰设定了有效期的key;
*
allkeys-lru:使用LRU算法进行数据淘汰,所有的key都可以被淘汰;
*
volatile-random:随机淘汰数据,只淘汰设定了有效期的key;
*
allkeys-random:随机淘汰数据,所有的key都可以被淘汰;
*
volatile-ttl:淘汰剩余有效期最短的key。

最好为Redis指定一种有效的数据淘汰策略,以配合maxmemory设置,避免在内存使用满后发生写入失败的情况。一般来说,推荐使用的策略是volatile-lru,并辨识Redis中保存的数据的重要性。对于那些重要的,绝对不能丢弃的数据(如配置类数据等),应不设置有效期,这样Redis就永远不会淘汰这些数据。对于那些相对不是那么重要的,并且能够热加载的数据(比如缓存最近登录的用户信息,当在Redis中找不到时,程序会去DB中读取),可以设置上有效期,这样在内存不够时Redis就会淘汰这部分数据。Redis数据淘汰策略的配置方法如下:

maxmemory-policy volatile-lru /#默认是noeviction,即不进行数据淘汰

7**、Redis主从复制原理**

同MySQL主从复制的原因一样,虽然Redis读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否全量而分为全量同步和增量同步,下图为级联结构。

A**、全量同步**

Redis全量复制一般发生在Slave初始化(或短线重连)阶段,这时Slave需要将Master上的所有数据都复制一份,具体步骤如下:

1)从服务器连接主服务器,发送SYNC同步命令;

2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件,并使用缓冲区记录此后执行的所有写命令;

3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件(即RDB文件),并在发送期间继续记录被执行的写命令;

4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照文件;

5)主服务器快照文件发送完毕后开始向从服务器发送缓冲区中的写命令;

6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区中的的写命令。

整个同步过程如下图所示。需要注意的是,如果直接与Master连接的Slave数量非常多,一定不要一次性启动太多的从节点,否则会对主节点造成很大的IO压力,甚至宕机。

完成上面几个步骤后,就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的请求。

B**、增量同步**

Redis增量复制是指,Slave初始化完成后开始正常工作时,主服务器发生的写操作同步到从服务器的过程。增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

主从刚刚连接的时候,进行全量同步;全量同步结束后,进行增量同步。当然,如果有需要,slave在任何时候都可以发起全量同步。Redis的策略是,无论如何,首先会尝试进行增量同步,如果失败,则要求重新进行全量同步。这里也需要注意一个问题,因为只要slave启动,就会发送SYNC请求和主机进行全量同步,当出现多个slave断线重启时可能会导致master IO剧增而宕机。

参考文献:

——《CSDN博客》

——《百度百科》

转自https://mp.weixin.qq.com/s/tps-ul1dVcoibEnjikJ67Q

相关文章

微信公众号

最新文章

更多