一、redis单线程为什么性能好
1. 解释
1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的
3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
4)使用多路I/O复用模型,非阻塞IO
5)使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
2. 那么为什么Redis是单线程的
1)官方答案:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)
二、持久化机制
1. 持久化概念
1)持久化可以理解为存储,就是将数据存储到一个不会丢失的地方, 如果把数据放在内存中,电脑关闭或重启数据就会丢失,所以放在内存中的数据不是持久化的,而放在磁盘就算是一种持久化
2)Redis 的数据存储在内存中,内存是瞬时的,如果 linux 宕机或重启,又或者 Redis 崩溃或重启,所有的内存数据都会丢失,为解决这个问题,Redis 提供两种机制对数据进行持久化存储,便于发生故障后能迅速恢复数据
3)Redis提供的持久化机制
(1)RDB持久化 该机制是指在指定的时间间隔内将内存中的数据集快照写入磁盘
(2)AOF持久化 该机制将以日志的形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的
2. RDB (Redis DataBase)方式
1)RDB介绍
(1)在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
(2)RDB保存的是dump.rdb文件
(3)Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失
2)如何触发RDB快照
(1)配置文件中默认的快照配置(冷拷贝后重新使用:可以cp dump.rdb dump_new.rdb)
(2)使用命令save或者bgsave
a. save:save时只管保存,其它不管,全部阻塞 (同步)
b. bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间(异步)
c. 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
3)如何恢复
(1)将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,通过config get dir可获取目录
4)如何停止
(1)动态所有停止RDB保存规则的方法:redis-cli config set save ""
5)优势
(1)适合大规模的数据恢复(相比于AOF机制,如果数据集很大,RDB的启动效率会更高)
(2)对数据完整性和一致性要求不高
(3)一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复
(4)对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上
(5)性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了
6)劣势
(1)在一定间隔时间做一次备份,所以如果redis意外宕掉的话,就会丢失最后一次快照后的所有修改
(2)fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑(由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟)
3. AOF(Append Only File)方式
1)AOF介绍
(1)以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作(AOF保存的是appendonly.aof文件)
2)AOF启动/修复/恢复
(1)正常恢复
a. 启动:修改默认的appendonly no,改为yes;
b. 将有数据的aof文件复制一份保存到对应目录(目录通过config get dir命令获取);
c. 恢复:重启redis然后重新加载;
(2)异常恢复
a. 启动:修改默认的appendonly no,改为yes;
b. 修复:使用redis-check-aof --fix命令进行修复;
c. 恢复:重启redis然后重新加载;
3)rewrite
(1)rewrite介绍
a. AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof;
(2)重写原理
a. AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似;
(3)触发机制
a. Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发;
4)同步策略
a. 每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好;
b. 每秒同步:appendfsync everysec 异步操作,每秒记录,如果一秒内宕机,有数据丢失;
c. 不同步:appendfsync no 从不同步;
5)优势
a. 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它
b. 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题
c. 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性
d. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建
6)劣势
a. 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb;
b. 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效
三、redis过期策略
1. 设置过期时间
1)expire key time(以秒为单位) -- 这是最常用的方式
2)setex(String key, int seconds, String value) -- 字符串独有的方式
注意:
a. 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
b. 如果没有设置时间,那缓存就是永不过期
c. 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
2. 三种过期策略
1)定时删除
(1)含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
(2)优点
a. 保证内存被尽快释放
(3)缺点
a. 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
b. 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
c. 没人用
2)惰性删除
(1)含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null
(2)优点
a. 删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
(3)缺点
a. 若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
3)定期删除
(1)含义:每隔一段时间执行一次删除过期key操作
(2)优点
a. 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
b. 定期删除过期key--处理"惰性删除"的缺点
(3)缺点
a. 在内存友好方面,不如"定时删除"
b. 在CPU时间友好方面,不如"惰性删除"
(4)难点
a. 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)
3. Redis采用的过期策略 —— 惰性删除+定期删除
1)惰性删除流程
(1)在进行get或setnx等操作时,先检查key是否过期
(2)若过期,删除key,然后执行相应操作
(3)若没过期,直接执行相应操作
2)定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
(1)遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
(2)检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
a. 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
b. 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
c. 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除
4. RDB对过期key的处理 —— 过期key对RDB没有任何影响
1)从内存数据库持久化数据到RDB文件
(1)持久化key之前,会检查是否过期,过期的key不进入RDB文件
2)从RDB文件恢复数据到内存数据库
(2)数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)
5. AOF对过期key的处理 —— 过期key对AOF没有任何影响
1)从内存数据库持久化数据到AOF文件
(1)当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
(2)当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
2)AOF重写
(1)重写时,会先判断key是否过期,已过期的key不会重写到aof文件
四、redis内存淘汰机制
1. 概念
1)redis 内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略
2. 配置
1)maxmemory : maxmemory为0的时候表示我们对Redis的内存使用没有限制
2)maxmemory-policy :选择淘汰策略
ps: redis支持动态改配置,无需重启
3. 过程
1)首先,客户端发起了需要申请更多内存的命令(如set)
2)然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存
3)最后,如果上面都没问题,则这个命令执行成功
4. 内存淘汰策略
1)volatile-lru :从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。redis并不是保证取得所有数据集中最近最少使用的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集
2)volatile-ttl :从已设置过期时间的数据集中挑选将要过期的数据淘汰。redis并不是保证取得所有数据集中最近将要过期的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集
3)volatile-random :从已设置过期时间的数据集中任意选择数据淘汰。当内存达到限制的时候无法写入非过期时间的数据集
4)allkeys-lru :从数据集中挑选最近最少使用的数据淘汰。当内存达到限制的时候,对所有数据集挑选最近最少使用的数据淘汰,可写入新的数据集
5)allkeys-random :从数据集中任意选择数据淘汰,当内存达到限制的时候,对所有数据集挑选随机淘汰,可写入新的数据集
6)no-enviction :当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错
5. 如何选择淘汰策略
1)allkeys-lru:如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略
2)allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略
3)volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction
4)volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存
五、redis常见问题分析(穿透、击穿、雪崩)
1. 缓存穿透
1)概念
(1)访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉
2)解决方案
(1)采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤
(2)访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间
2. 缓存雪崩
1)概念
(1)大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩
2)解决方案
(1)可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效
(2)做二级缓存策略。A1设置为短期失效,A2设置为长期失效,A1时效时可以访问A2
(3)可以通过缓存reload机制,预先去更新缓存,在即将发生大并发前手动触发加载缓存
3. 缓存击穿
1)概念
(1)一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增
2)解决方案
(1)在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key
六、 redis优化
1. 影响redis性能的因素
1)网络带宽和延迟。在执行基准测试前使用ping快速检测客户端和服务器端的延迟是一个良好的做法。对于带宽,比较好的做法是估计Gbits/s 的吞吐量和网络的理论带宽值比较。在很多实际的情况,Redis的吞吐量在网络之前会受限于CPU
2)CPU也会是一个重要因素。由于单线程的,Redis受益于快速的含有巨大缓存的CPU
3)内存的速度和容量对于小的对象影响不大。但对于大于10KB的对象,可能对需要注意。通常购买昂贵的快速内存模块并不是真正的很有效
4)Redis在虚拟机上运行慢。虚拟化对很多普通操作来说代价太高了,Redis并没有增加多少开销在所需的系统调用和网络中断上
5)客户端和服务器在一台机器运行,对于基准测试TCP/IP回送和UNIX域套接字都可以使用。取决于平台,但UNIX域套接字比TCP/IP回送增加50%的吞吐量
6)当大量使用 pipelining时,UNIX域套接字获得的性能好处会减少
7)当以太网访问Redis时,在数据大小小于以太网数据包的大小(大约1500字节)时,聚集命令使用 pipelining会非常有效
8)在多CPU套接字服务器,Redis的表现变得依赖于NUMA配置和处理位置
9)在高端配置,客户端连接的数量也是一个重要的因素。基于epool/kqueue模型,Redis的事件循环是相当可伸缩的
10)在高端的配置,通过调优NIC(s)配置和相关中断可能取得高吞吐量
11)根据平台,Redis编译可以使用不同的内存分配器,这可能有不同的行为在原始速度,内部和外部的碎片方面
参考网址
注:文章是经过参考其他的文章然后自己整理出来的,有可能是小部分参考,也有可能是大部分参考,但绝对不是直接转载,觉得侵权了我会删,我只是把这个用于自己的笔记,顺便整理下知识的同时,能帮到一部分人。
ps : 有错误的还望各位大佬指正,小弟不胜感激