Redis入门总结(一):redis配置文件,五种数据结构,线程模型和持久化方式

(尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/90321396冷血之心的博客)

关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

快速导航:

Redis入门总结(一):redis配置文件,五种数据结构,线程模型和持久化方式

Redis入门总结(二):主从复制,事务和发布订阅

Redis入门总结(三):redis实现分布式锁的正确姿势

这篇文章主要总结了:什么是redis,redis的特点,配置文件,五种数据结构以及其底层实现原理,线程模型,缓存穿透,缓存雪崩以及缓存与数据库的读写不一致的问题;在此基础上,介绍了RDB和AOF两种数据持久化模式。

  1. Redis:REmote DIctionary Server(远程字典服务)

    1. 定义:

      1. 由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库。是完全开源免费的,用C语言编写的,遵守BSD协议,高性能的(key/value)分布式内存数据库,基于内存运行支持持久化NoSQL数据库。

    2. 优点(特点):

      1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

      2. 支持丰富数据类型,支持String,List,Set,Sorted Set,Hash

      3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

      4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

    3. Redis配置文件(redis.conf):

      1. 在Redis的解压目录下有个很重要的配置文件 redis.conf,我们来依次阐述各部分的重要配置。

      2. include:

        1. 可以将多个配置文件引入

      3. modules:

        1. 通过loadmodule配置可以将自定义模块引入

      4. network:

        1. bind:绑定redis服务器网卡IP,默认为127.0.0.1,即本地回环地址。这样的话,访问redis服务只能通过本机的客户端连接,而无法通过远程连接。如果bind选项为空的话,那会接受所有来自于可用网络接口的连接。

        2. port:指定redis运行的端口,默认是6379。由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口。

        3. timeout:设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接。默认值为0,表示不关闭。

        4. tcp-keepalive :单位是秒,表示将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞,官方给出的建议值是300s,如果设置为0,则不会周期性的检测。

      5. general:

        1. daemonize:设置为yes表示指定Redis以守护进程的方式启动(后台启动)。默认值为 no

        2. pidfile:配置PID文件路径,当redis作为守护进程运行的时候,它会把 pid 默认写到 /var/redis/run/redis_6379.pid 文件里面

        3. loglevel :定义日志级别。默认值为notice,有如下4种取值:

          1. debug(记录大量日志信息,适用于开发、测试阶段)

          2. verbose(较多日志信息)

          3. notice(适量日志信息,使用于生产环境)

          4. warning(仅有部分重要、关键信息才会被记录)

        4. logfile :配置log文件地址,默认打印在命令行终端的窗口上

        5. databases:设置数据库的数目。默认的数据库是DB 0 ,可以在每个连接上使用select  <dbid> 命令选择一个不同的数据库,dbid是一个介于0到databases - 1 之间的数值。默认值是 16,也就是说默认Redis有16个数据库。

      6. snapshotting:(持久化相关配置)

        1. save:这里是用来配置触发 Redis的持久化条件,也就是什么时候将内存中的数据保存到硬盘。默认如下配置:

          1. save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存

          2. save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存

          3. save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

          4. 如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。

          5. 可以直接一个空字符串来实现停用:save ""

        2. stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

        3. rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。

        4. rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

        5. dbfilename :设置快照的文件名,默认是 dump.rdb

        6. dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。使用上面的 dbfilename 作为保存的文件名。

      7. replication:

        1. slave-serve-stale-data:默认值为yes。当一个 slave 与 master 失去联系,或者复制正在进行的时候,slave 可能会有两种表现:

          1. 如果为 yes ,slave 仍然会应答客户端请求,但返回的数据可能是过时,或者数据可能是空的在第一次同步的时候

          2. 如果为 no ,在你执行除了 info he salveof 之外的其他命令时,slave 都将返回一个 "SYNC with master in progress" 的错误

        2. slave-read-only:配置Redis的Slave实例是否接受写操作,即Slave是否为只读Redis。默认值为yes。

        3. repl-diskless-sync:主从数据复制是否使用无硬盘复制功能。默认值为no。

        4. repl-diskless-sync-delay:当启用无硬盘备份,服务器等待一段时间后才会通过套接字向从站传送RDB文件,这个等待时间是可配置的。这一点很重要,因为一旦传送开始,就不可能再为一个新到达的从站服务。从站则要排队等待下一次RDB传送。因此服务器等待一段时间以期更多的从站到达。延迟时间以秒为单位,默认为5秒。要关掉这一功能,只需将它设置为0秒,传送会立即启动。默认值为5。

        5. repl-disable-tcp-nodelay:同步之后是否禁用从站上的TCP_NODELAY 如果你选择yes,redis会使用较少量的TCP包和带宽向从站发送数据。但这会导致在从站增加一点数据的延时。  Linux内核默认配置情况下最多40毫秒的延时。如果选择no,从站的数据延时不会那么多,但备份需要的带宽相对较多。默认情况下我们将潜在因素优化,但在高负载情况下或者在主从站都跳的情况下,把它切换为yes是个好主意。默认值为no。

      8. security:

        1. rename-command:命令重命名,对于一些危险命令例如:

          1. flushdb(清空数据库)

          2. flushall(清空所有记录)

          3. config(客户端连接后可配置服务器)

          4. keys(客户端连接后可查看所有存在的键)                   

        2. 作为服务端redis-server,常常需要禁用以上命令来使得服务器更加安全,禁用的具体做法是是:

          1. rename-command FLUSHALL ""

            1. 也可以保留命令但是不能轻易使用,重命名这个命令即可:

          2. rename-command FLUSHALL abcdefg

            1. 重启服务器后则需要使用新命令来执行操作,否则服务器会报错unknown command。

      9. clients:

        1. maxclients设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。  描述符数-32(redis server自身会使用一些),如果设置 maxclients为0 ,表示不作限制。

        2. 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。

      10. memory management(内存管理):

        1. maxmemory:设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。描述符数-32(redis server自身会使用一些),如果设置 maxclients为0 。表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。

        2. maxmemory-policy :当内存使用达到最大值时,redis使用的清除策略。

          1. volatile-lru   利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )

          2. allkeys-lru   利用LRU算法移除任何key

          3. volatile-random 移除设置过过期时间的随机key

          4. allkeys-random  移除随机key

          5. volatile-ttl   移除即将过期的key(minor TTL)

          6. noeviction  noeviction   不移除任何key,只是返回一个写错误 ,默认选项

        3. maxmemory-samples :LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存)。随意你可以选择样本大小进行检,redis默认选择3个样本进行检测,你可以通过maxmemory-samples进行设置样本数。

      11. APPEND ONLY MODE:

        1. appendonly:默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,  可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入appendonly.aof文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认值为no。

        2. appendfilename :aof文件名,默认是"appendonly.aof"

        3. appendfsync:aof持久化策略的配置;no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快;always表示每次写入都执行fsync,以保证数据同步到磁盘;everysec表示每秒执行一次fsync,可能会导致丢失这1s数据

        4. no-appendfsync-on-rewrite:在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。   设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。

        5. auto-aof-rewrite-percentage:默认值为100。aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。

        6. auto-aof-rewrite-min-size:64mb。设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。

        7. aof-load-truncated:aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项,出现这种现象  redis宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes。

      12. LUA SCRIPTING:

        1. lua-time-limit:一个lua脚本执行的最大时间,单位为ms。默认值为5000.

      13. REDIS CLUSTER:

        1. cluster-enabled:集群开关,默认是不开启集群模式。

        2. cluster-config-file:集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。 这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件。请确保与实例运行的系统中配置文件名称不冲突。默认配置为nodes-6379.conf。

        3. cluster-node-timeout :可以配置值为15000。节点互连超时的阀值,集群节点超时毫秒数

        4. cluster-slave-validity-factor :可以配置值为10。在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period     如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移

        5. cluster-migration-barrier :可以配置值为1。master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。

        6. cluster-require-full-coverage:默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。  设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。

    4. redis线程安全问题:

      1. 单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性)

      2. redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。

    5. redis相比memcached有哪些优势?

      1. memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

      2. redis的速度比memcached快很多

      3. redis可以持久化其数据

      4. Redis支持数据的备份,即master-slave模式的数据备份。

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

      6. value大小:redis最大可以达到1GB,而memcache只有1MB

    6. Redis支持的数据类型:

      1. 我们所说的其支持的数据类型都是在说其存储的value的类型,redis的Key的类型都是字符串。

      2. string:

        1.  redis 中字符串 value 最多可以是 512M。

        2. 场景:做一些计数功能的缓存

      3. list:

        1. list 列表,它是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),它的底层实际上是个链表。

        2. 场景:可以当作一个简单消息队列功能,做基于redis的分页功能

      4. set:

        1. 是一个String类型的无序集合。

        2. 场景:全局去重

      5. sorted set:

        1. 是一个String类型的有序集合,通过给每一个元素一个固定的分数score来保持顺序。

        2. 场景:做排行榜应用,取TopN操作,范围查找

      6. hash:

        1. hash 是一个键值对集合,是一个 string 类型的 key和 value 的映射表,key 还是key,但是value是一个键值对(key-value)

        2. 场景:存放一些结构化的信息

    7. 系统常用命令:

      1. OBJECT ENCODING    key :显示五种数据类型的底层数据实现结构

      2. BGSAVE:后台异步保存当前数据库的数据到磁盘(持久化)

      3. BGREWIRTEAOF:手动触发AOF重写操作,用于减小AOF文件体积

    8. redis数据类型的底层数据结构:

      1. 通过OBJECT ENCODING key 可以查看当前key的底层数据结构。

      2. 简单动态字符串(simple dynamic string,SDS):

        1. SDS 数据类型的定义

          1. len 保存了SDS保存字符串的长度

          2. buf[ ] 数组用来保存字符串的每个元素

          3. free 记录了 buf 数组中未使用的字节数量

        2. 使用SDS而不是C语言原生的字符串的优点:

          1. 常数复杂度获取字符串长度

          2. 杜绝缓冲区溢出:

            1. 当进行字符串修改时,可以根据len属性检查空间是否满足,不满足会扩容,防止缓冲区溢出

          3. 减少字符串修改时的内存重新分配次数:

            1. C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。

            2. SDS在字符串修改时存在空间预分配和惰性空间释放

          4. 二进制安全:

            1. 二进制中可能包含空字符串。C语言原生的字符串以空字符结尾,所以不可以正确的存取

            2. SDS中以len属性来判断字符串是否结束

          5. 兼容部分C语言中的函数库

          6. SDS 还可以作为缓冲区(buffer):包括 AOF 模块中的AOF缓冲区以及客户端状态中的输入缓冲区

      3. 链表:

        1. Redis中自己实现了链表,特性如下:

          1. 双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。

          2. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。  

          3. 带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。

          4. 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。

      4. 字典:

        1. 字典又称为符号表或者关联数组、或映射(map),是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。

        2. C 语言中没有内置这种数据结构的实现,所以字典依然是 Redis自己构建的。Redis 的字典使用哈希表作为底层实现。

        3. 扩容和收缩:当哈希表保存的键值对太多或者太少时,就要通过 rehash(重新散列)来对哈希表进行相应的扩展或者收缩。

          1. 如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表

          2. 相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。

          3. 重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。

          4. 所有键值对都迁徙完毕后,释放原哈希表的内存空间。

        4. 触发扩容的条件:

          1. 服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于1。

          2. 服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于5。

          3. 负载因子 = 哈希表已保存节点数量 / 哈希表大小

        5. 渐近式 rehash

          1. 扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。如果保存在Redis中的键值对只有几个几十个,那么 rehash 操作

          2. 可以瞬间完成。

          3. 但是如果键值对有几百万,几千万甚至几亿,那么要一次性的进行 rehash,势必会造成Redis一段时间内不能进行别的操作。

          4. 所以Redis采用渐进式 rehash,这样在进行渐进式rehash期间,字典的删除查找更新等操作可能会在两个哈希表上进行,第一个哈希表没有找到,就会去第二个哈希表上进行查找。但是进行 增加操作,一定是在新的哈希表上进行的。

      5. 跳表:

        1. 对于一个有序的链表,我们依然只能通过遍历来查找一个元素,效率低下。

        2. 跳表就是将有序的链表分层,相当于是创建了多个分层索引,当链表元素个数增大的时候,跳表的查询效率显著提升。

        3. 跳表的特点:

          1. 由很多层结构组成;

          2. 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;

          3. 最底层的链表包含了所有的元素;

          4. 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);

          5. 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

        4. Redis中为什么使用跳表而不使用查询复杂度同样为log(N)的平衡树?

          1. 跳表构建起来比平衡树容易

          2. 跳表支持范围搜索

      6. 整数集合(intset):

        1. 用于保存整数值的集合抽象数据类型,它可以保存类型为int16_t、int32_t 或者int64_t 的整数值,并且保证集合中不会出现重复元素。

        2. 整数集合的每个元素都是 contents 数组的一个数据项,它们按照从小到大的顺序排列,并且不包含任何重复项

        3. length 属性记录了 contents 数组的大小。

      7. 压缩列表:

        1. 压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构

        2. 一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

        3. 压缩列表的原理:

          1. 压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存

    9. Redis数据类型的实现原理:

      1. Redis底层所使用的数据结构我们已经学习了,但是redis并不是直接使用这些数据结构,而是构建了一个对象系统。

      2. 每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象。

      3. Redis中的每个对象都是由 redisObject 结构来表示:

        1. type属性:记录了对象的类型,五种数据类型string,list,set.sort set ,hash。  命令:type key

        2. encoding编码:记录了对象的底层编码(每个对象都至少有两种编码)

          1. OBJECT ENCODING    key :显示五种数据类型的底层数据实现结构(编码)

        3. *ptr:指向底层数据结构的指针

      4. 五大数据类型对象的编码:

        1. 字符串对象:

          1. 字符串是Redis最基本的数据类型,不仅所有key都是字符串类型,其它几种数据类型构成的元素也是字符串。注意字符串的长度不能超过512M。

          2. 编码:字符串对象的编码可以是int,raw或者embstr。

        1. int 编码:保存的是可以用 long 类型表示的整数值

        2. raw 编码(长):保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)

        3. embstr 编码(短)(只读):保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)

        4. raw和embstr编码的区别:

          1. embstr与raw都使用redisObject和sds保存数据,区别在于:

          2. embstr的使用只分配一次内存空间(因此redisObject和sds是连续的)

          3. raw需要分配两次内存空间(分别为redisObject和sds分配空间)。

          4. 因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。

          5. 而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。

        5. 编码可以转换吗?

          1. 当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw。

          2. 对于 embstr 编码,由于 Redis 没有对其编写任何的修改程序(embstr 是只读的),在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了44个字节。

        6. 列表对象(list):

          1. 列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)。

          2. 当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

            1. 列表保存元素个数小于512个

            2. 每个元素长度小于64字节

          3. 不能满足这两个条件的时候使用 linkedlist 编码。

          4. 也就是说元素又多又长就用linkedlist,否则就用ziplist

        7. 哈希对象(hash):

          1. 哈希对象的编码可以是 ziplist 或者 hashtable。

          2. 当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

            1. 列表保存元素个数小于512个

            2. 每个元素长度小于64字节

          3. 不能满足这两个条件的时候使用 hashtable编码。

        8. 集合对象(set):

          1. 集合对象 set 是 string 类型(整数也会转换成string类型进行存储)的无序集合。

          2. 集合对象的编码可以是 intset 或者 hashtable。intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合中。

          3. 当集合同时满足以下两个条件时,使用 intset 编码:

            1. 集合对象中所有元素都是整数

            2. 集合对象所有元素数量不超过512

          4. 不能满足这两个条件的就使用 hashtable 编码。

        9. 有序集合对象:

          1. 有序集合对象是有序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。

          2. 有序集合的编码可以是 ziplist 或者 skiplist

            1. ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

            2. skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:

              1. 字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

              2. 这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。

              3. 说明:其实有序集合单独使用字典或跳跃表其中一种数据结构都可以实现,但是这里使用两种数据结构组合起来,原因是假如我们单独使用 字典,虽然能以 O(1) 的时间复杂度查找成员的分值,但是因为字典是以无序的方式来保存集合元素,所以每次进行范围操作的时候都要进行排序;假如我们单独使用跳跃表来实现,虽然能执行范围操作,但是查找操作有 O(1)的复杂度变为了O(logN)。因此Redis使用了两种数据结构来共同实现有序集合。

          3. 当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:

            1. 保存的元素数量小于128;

            2. 保存的所有元素长度都小于64字节。

          4. 不能满足上面两个条件的使用 skiplist 编码。

      5. refcount(引用计数):

        1. Redis自己构建了一个内存回收机制,通过在 redisObject 结构中的 refcount 属性实现。这个属性会随着对象的使用状态而不断变化:

          1. 创建一个新对象,属性 refcount 初始化为1

          2. 对象被一个新程序使用,属性 refcount 加 1

          3. 对象不再被一个程序使用,属性 refcount 减 1

          4. 当对象的引用计数值变为 0 时,对象所占用的内存就会被释放。

        2. 如果出现循环引用导致内存溢出的时候,我们在配置文件中设置了redis的内存清除策略。

        3. 共享内存:

          1. 当key不相同但是value相同时,redis将数据库键的值指针指向一个现有值的对象,将被共享的值对象引用refcount 加 1

          2. Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)

          3. 虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)

      6. lru属性:

        1. 该属性记录了对象最后一次被命令程序访问的时间。

        2. 使用 OBJECT IDLETIME 命令可以打印给定键的空转时长,通过将当前时间减去值对象的 lru 时间计算得到。

        3. lru 属性除了计算空转时长以外,还可以配合前面内存回收配置使用。

        4. 如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占用超过maxmemory指定的值时,Redis会优先选择空转时间最长的对象进行释放。

    10. redis单线程为什么执行速度如此之快?

      1. 完全内存计算

      2. 单线程,避免了上下文切换

      3. 使用了多路I/O复用,一个线程监控多个IO流,实现及时响应。

    11. redis的线程模型:

      1. redis的线程模型是一种多路I/O复用模型,具体模型图如下所示:

      2. 简单来说,就是redis-client在操作的时候,会产生具有不同事件类型的socket。

        1. 在服务端,有一段I/O多路复用程序,将其置入队列之中。

        2. 然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。

      3. I/O多路复用机制:

        1. 目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,I/O多路复用就是通过一种机制

        2. 一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

        3. select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

        4. 优点:

          1. 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

      4. 常见的多路复用函数:

        1. select函数:

          1. 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。

          2. 有最大监听连接数1024个的限制

          3. 如果任何一个sock(I/O stream)出现了数据,select没有返回具体是哪个返回了数据,需要采用轮询的方式去遍历获取

          4. 线程不安全(当你在一个线程中已经监听该socket,另一个线程想要将该socket关闭,则结果会不可预知)

          5. “If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”

        2. poll函数:

          1. 去掉了1024的限制(使用链表搞定)

          2. 不再修改传入的参数数组

          3. 依然是线程不安全的

        3. epoll函数:

          1. epoll 不仅返回socket组里面数据,还会告诉你具体哪个socket有数据

          2. 线程安全

    12. Redis可能出现的问题:

      1. 缓存雪崩

        1. 缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都到数据库上,从而导致数据库连接异常。

        2. 解决方案:

          1. 给缓存的失效时间,加上一个随机值,避免集体失效。

          2. 使用互斥锁,但是该方案吞吐量明显下降了。

          3. 双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。

      2. 缓存穿透

        1. 故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

        2. 解决方案:

          1. 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试

          2. 采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。

          3. 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回

      3. 缓存的并发竞争

        1. 多个子系统来并发竞争更新一个Key

        2. 解决方案:

          1. 使用一个分布式锁,谁占有谁更新

          2. 使用队列,将更新操作变成串行

          3. 指定更新的顺序,每次更新都有一个version,当每个子系统更新时发现当前version大于自己的version,那么放弃更新

      4. 缓存和数据库双写一致性

        1. 高并发请求下会导致数据不一致的问题,如果业务需要数据保持强一致性,那么不可以使用缓存。

        2. 解决方案:

          1. 双删延时

            1. 删除缓存数据

            2. 更新数据库数据

            3. 隔固定的时间再次删除缓存

          2. 更新数据库产生的binlog订阅(使用canal),将有变化的key记录下来,并且不断的去删除缓存(如果上次删除缓存失败)

    13. Redis数据的过期回收策略与内存淘汰机制:

      1. 定期删除+惰性删除组合

      2. 定期删除:

        1. 当给数据设置的缓存时间到期之后会自动删除,但是比较消耗CPU,redis是单线程执行的,如果高并发情况下会影响系统性能

      3. 定期删除+惰性删除组合:(这个才是使用的策略)

        1. redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

        2. 惰性删除:

          1. 在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

      4. 内存淘汰:

        1. 如果在数据过期之后,你没有去获取该数据,那么就不会被惰性删除,该数据一直保存,redis内存会越来越高。

        2. 当内存不足时,通过我们在上边所介绍的redis.conf配置文件中的配置的maxmemory-policy策略来进行内存淘汰

    14. Redis持久化策略:

      1. redis是一个基于内存,并且支持数据持久化的NoSql数据库。有两种持久化策略:

      2. RDB(快照方式 snapshotting)(全量持久化):

        1. 把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里

        2. 自动触发方式:

          1. 我们在配置文件redis.conf中配置的snapshotting 指定了当多长时间内有多少次save命令时自动触发持久化操作

          2. 将这块的save配置注释掉或者配置为空字符串,表示关闭RDB持久化方式

        3. 手动触发方式:

          1. 通过bgsave命令,在后台异步进行生成快照的操作,同时还可以响应客户都按的请求。

          2. 通过redis进程fork操作创建子进程,生成快照由子进程负责,完成后自动结束,请求只会在fork阶段被阻塞

        4. 快照恢复:

          1. 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis就会自动加载文件数据至内存了。Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

        5. 优缺点:

          1. 存在数据丢失的问题

          2. 恢复大数据集时比AOF方式快

          3. RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)

      3. AOF(append-only-file)(增量持久化):

        1. 通过记录redis服务器所执行的写命令来记录数据库状态

        2. redis.conf 配置文件的 APPEND ONLY MODE 下我们可以设置AOF持久化

        3. AOF 保存文件的位置和 RDB 保存文件的位置一样,都是通过 redis.conf 配置文件的 dir 配置

        4. 数据恢复:

          1. 重启 Redis 之后就会进行 AOF 文件的载入

          2. 异常修复命令:redis-check-aof --fix 进行修复

        5. AOF日志重写:

          1. AOF文件可能会随着服务器运行的时间越来越大,可以利用AOF重写的功能,来控制AOF文件的大小。

          2. AOF重写功能会首先读取数据库中现有的键值对状态,然后根据类型使用一条命令来替代前的键值对多条命令

          3. 使用命令 bgrewriteaof 来实现AOF重写

        6. AOF重写缓存区:

          1.  Redis 是单线程工作,如果重写AOF 需要比较长的时间,那么在重写 AOF 期间,Redis将长时间无法处理其他的命令,解决办法是将 AOF 重写程序放到子程序中进行,这样有两个好处:

            1. 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其他命令。

            2. 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

          2. 子进程重写导致的问题:

            1. 因为子进程在进行 AOF 重写期间,服务器进程依然在处理其它命令,这新的命令有可能也对数据库进行了修改操作,使得当前数据库状态和重写后的 AOF 文件状态不一致。

          3. 数据状态不一致解决办法:

            1. Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。

            2. 当子进程完成 AOF 重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF 重写缓冲区的内容都写到新的 AOF 文件中。

        7. 优点:

          1. AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。

          2. AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使用 redis-check-aof 工具也很容易修正 AOF 文件。

          3. AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据。

        8. 缺点:

          1. 对于具有相同数据的的 Redis,AOF 文件通常会比 RDF 文件体积更大。

          2. 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB 比 AOF 具好更好的性能保证。

          3. RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 方式更健壮。官方文档也指出,AOF 的确也存在一些 BUG,这些 BUG 在 RDB 没有存在。

      4. 持久化策略选择:

        1. AOF更安全,可将数据及时同步到文件中,但需要较多的磁盘IO,AOF文件尺寸较大,文件内容恢复相对较慢, 也更完整。

        2. RDB持久化,安全性较差,它是正常时期数据备份及 master-slave数据同步的最佳手段,文件尺寸较小,恢复数度较快。

以上内容摘抄总结与网络,感谢各位前辈的总结与铺垫。接下来我会继续更新Redis相关文章,全部会以这种笔记形式给出,大家对笔记中不太懂的地方,可以发表评论,我们一起研究学习,此处权当给大家列个学习提纲了。

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~

本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。

关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

发布了262 篇原创文章 · 获赞 2004 · 访问量 191万+

猜你喜欢

转载自blog.csdn.net/qq_25827845/article/details/90321396
今日推荐