赞
踩
在我读书的时候,我曾经很喜欢redis,听了相关的分享、看了相关的博客、读了相关的书、看了喜欢的源码,然后我写了一个总结:《这是全网最硬核redis总结,谁赞成,谁反对?》六万字大合集
现在,这篇文章大概五万多阅读,三千多收藏,三千多点赞。
但是,随着时间的推移,我渐渐的有点看不上这篇文章,主要是觉得写的太多太全了,像流水账一样,好像什么都说了,又好像什么都没说。
所以,现在我有了一些开发经验,有了一些心得体会,我准备写一个浓缩全是精华的分享,写一个初中高级开发看了,都能有收获的分享。
本文主要是告诉你为什么我们使用redis,以及使用时可能遇到的问题。
下文括号中,都是我在公司讲课时,详细讲到的地方,非括号是文章正文。
1、延时队列(股票定投/)
2、布隆过滤器
缓存,解决本地缓存命中率太低的问题
(举例发布时读db超时挂掉)
字典,是一种用于保存键值对的抽象数据结构,在Redis中的应用相当广泛,比如Redis的数据库就是使用字典作为底层实现的,对数据库的增删改查等操作也是构建在对字典的操作之上。
特点:
使用的哈希算法:murmurhash,链表处理冲突
(哈希打单点事故的案例分享)
(即使输入的键是有规律的,算法仍能给出一个很好的随机分布性,计算速度非常快,使用简单。因此在多个开源项目中得到应用,包括libstdc、libmemcached、nginx、hadoop等。)
rehash
(元素个数=数组长度扩容/bgsave时五倍,小于10%缩容 1)为ht[1]分配合理空间。2)将ht[0]中的数据rehash到ht[1]上。3)释放ht[0],将ht[1]设置为ht[0],ht[1]创建空表,为下次做准备。)
(我们维持一个变量rehashidx,设置为0,代表rehash开始,然后开始rehash,在这期间,每个对字典的操作,程序都会把索引rehashidx上的数据移动到ht[1]。
随着操作不断执行,最终我们会完成rehash,设置rehashidx为-1.)
Redis没有直接使用C语言传统的字符串表示,而是自己构建了名为简单动态字符串(simple dynamic string ,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。(除了保存数据库中的字符串值之外,SDS还被用作缓冲区,例如AOF的AOF缓冲区、客户端状态中的输入缓冲区等。)
- struct sdshdr {
-
- int len;//buf已使用字节数量
-
- int free;//未使用的字节数量
-
- char buf[];//用来保存字符串的字节数组
-
- };
数据结构如图表示:
相对c的改进:
(1)常数复杂度获取字符串长度
(2)杜绝缓冲区溢出(这里是指相加的时候)
(3)减少修改字符串时带来的内存重分配次数(空间预分配、惰性空间释放)
(还有二进制安全之类的)
(Redis只在两个地方用到了跳跃表,一个是实现有序集合,另一个是集群节点中用作内部数据结构。)
跳跃表是一种有序数据结构,在大多数情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树更为简单,所以有不少程序使用跳跃表代替平衡树。
数据结构如图表示:
Redis为何不用红黑树?优越性和特殊性
todo:演示两个数据结构的操作
链表:带表头、双端无环、带长度记录、多态
整数集合:各个项在数组中从小到大有序排列,并且数组中不包含任何重复项。
压缩列表
占空间小,由一系列特殊编码的连续内存块组成的顺序型数据结构,为了节省空间,Previous_entry_length甚至大小可以变化
todo:数据结构精讲\布隆过滤器\HyperLogLog等
对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象五种类型的对象,每种对象都用到了至少一种前面介绍的数据结构。
数据结构如图表示:
通过 encoding 属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码, 极大地提升了 Redis 的灵活性和效率。
(这种思想在其它语言/组件/项目中也有体现。因地制宜,根据场景选择适用的方案才是正路。
事实上redis新版加入了压缩表+链表的结构,也是基于这种思想。)
1.Redis在自己的对象系统中构建了一个引用计数技术(并未采用可达性分析)实现内存回收机制。
2.通过引用计数技术,实现对象共享机制,通过让多个数据库键共享一个对象来节省内存。数据库中相同值对象越多,对象共享机制就能节约越多的内存。
(引用计数:(1)在创建一个新对象时,引用计数的值会被初始化为1;
(2)当对象被一个新程序使用时,它的引用计数值会被增一;
(3)当对象不再被一个程序使用时,它的引用计数值会被减一;
(4)当对象的引用计数值变为0时,对象所占用的内存会被释放;
引用简单,可达性(图论,可达即有用)需要遍历。
循环引用的问题,可能是简洁的思想和对系统的自信吧)
todo:动态链接库和网络框架
我们之前所说的Redis 是单线程,主要是指 对外提供键值存储服务的主要流程是单线程的,也就是网络 IO 和键值对读写。其它功能如持久化、异步删除、集群数据同步等很早就是多线程。
那么Redis起初并不是多线程模型,主要原因:没必要
性能瓶颈不在 CPU
单线程模型,可维护性更高,开发/调试/维护的成本更低
单线程模型避免了线程间切换带来的性能开销
在单线程中使用多路复用 I/O技术也能提升Redis的I/O利用率(虽然本质还是阻塞的)
Linux多路复用技术,就是多个进程的IO可以注册到同一个管道上,这个管道会统一和内核进行交互。当管道中的某一个请求需要的数据准备好之后,进程再把对应的数据拷贝到用户空间中。
(科普:多线程的目的,就是通过并发的方式提升I/O利用率和CPU利用率。)
Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,对于小数据包,Redis 服务器可以处理 80,000 到 100,000 QPS。
todo:redis6.0
(redis6.0还是真香了。顺序、分身、监听。在select、poll、epoll这些调用会阻塞,收发消息不会阻塞。挖坑下次填)
机制一、设置最大空间:Redis 提供参数 maxmemory 配置 Redis 最大使用内存。
机制二、设置生存时间:可以给键设置生存时间(UNIX时间戳),到时间自动删除这个键。
redisdb结构的expires字典保存了所有的键的过期时间,我们称这个字典为过期字典。
机制三、过期键删除策略:
1)定时删除:创建一个定时器,到时间立即执行删除操作(对内存友好,因为能保证过期了立马删除,但是对cpu不友好)
2)惰性删除:键过期不管,每次获取键时检查是否过期,过期就删除(对cpu友好,但是只有在使用的时候才可能删除,对内存不友好)
3)定期删除:隔一段时间检查一次(具体算法决定检查多少删多少,需要合理设置)
机制四、淘汰策略
当Redis占用内存超出最大限制 (maxmemory) 时,采用可配置的一些策略 (maxmemory-policy) ,让Redis淘汰一些数据:
•volatile-random: 在设置了过期时间的key中,随机选择一些key,将其淘汰;
•allkeys-1Lru: 在所有的key中,选择最少使用的key (LRU) ,将其淘汰;
•allkeys-random: 在所有的key中,随机选择一些key,将其淘汰;
通过生成数据集的时间点快照(point-in-time snapshot)来实现持久化。
自动触发:
自动触发使用 save 相关配置触发,比如 “save m n”,表示在 m 秒内数据库存在 n 次修改时,自动触发 BGSAVE 。
手动触发:
SAVE:执行一个同步保存操作,将当前实例的所有数据快照以 RDB 文件的形式保存到磁盘中。
BGSAVE:用于在后台异步保存当前数据库的数据到磁盘。
命令 | save | bgsve |
IO类型 | 同步 | 异步 |
是否阻塞 | 是 | 是(fork内) |
复杂度 | O(n) | O(n) |
客户端是否阻塞 | 阻塞客户端命令 | 不阻塞客户端命令 |
是否消耗资源 | 不额外消耗资源 | 需要fork消耗资源 |
AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态,重启时再重新执行 AOF 文件中的命令以完成数据恢复。AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。
todo:自动重写
rdb优点:RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。
RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心。
RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点:发生故障时会丢失数据。虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。
每次保存 RDB 的时候,Redis 都要 fork出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。
优点:
使用 AOF 持久化会让 Redis 变得非常耐久,可以设置多种策略,发生故障停机,也最多只会丢失一秒钟的数据
AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令,也可以使用工具进行修复
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写。
AOF 文件有序地保存了对数据库执行的所有写入操作, (如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。)
缺点:
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此
AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样
todo:RDB/AOF的详细介绍
todo:事务
todo:多机可靠
todo:
比如说,有做缓存的,有做数据库的,也有用做分布式锁的。不过,他们遇见的“坑”,总体来说集中在四个方面:
CPU 使用上的“坑”,例如数据结构的复杂度、跨 CPU 核的访问;
内存使用上的“坑”,例如主从同步和 AOF 的内存竞争;
存储持久化上的“坑”,例如在 SSD 上做快照的性能抖动;
网络通信上的“坑”,例如多实例时的异常网络丢包。
《Redis设计与实现》
《Redis入门指南(第2版)》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。