过期时间设置
在redis中提供了expire命令设置一个键的过期时间,到期以后redis会自动删除它,这个在我们实际使用中是非常多的。
expire命令的使用方法为:expire key seconds
其中seconds参数表示键的过期时间,单位为秒。
expire返回值为1表示设置成功,0表示设置失败或者键不存在。
如果想知道一个键还有多久时间被删除,可以使用ttl命令:ttl key
当键不存在时,ttl命令返回-2
而对于没有给指定键设置过期时间的,通过ttl命令会返回-1
如果想取消键的过期时间设置,可以使用persist命令,如果该命令执行成功或者成功清除了过期时间,则返回1,否则返回0(键不存在或者本身就是永久的)
expire命令的seconds命令必须是整数,所有最小单位是1秒,如果想要更精确的控制键的过期时间,可以使用pexpire命令,当然实际过程中用秒的单位就够了,pexpire命令的单位是毫秒。即pexpire key 1000 等于 expire key 1;对应的pttl 以毫秒单位获取键的剩余有效时间。
还有一个针对字符串独有的过期时间设置方式
setex(String key,int seconds,String value)
过期删除的原理
redis的主键失效时如何实现的,即失效的主键是如何删除的?实际上,redis删除失效主键的方法主要有两种:
消极方法(passive way)
在主键被访问时如果发现他已经失效了,那么就删除它。
积极方法(active way)
周期性地设置累失效时间的主键中选择一部分失效的主键删除
对于那些从未被查询的key,即便他们已经过期,被动方式也无法清除,因为redis会周期性地随机测试一些key,已经过期的key将会被删掉,redis每秒会进行10此操作,具体操作流程:
1.随机的测试20个带有timeout信息的key
2.删除其中已经过期的key
3.如果超过25%的key被删除,则重复执行步骤1.
这是一个简单的概率算法,基于假设我们随机抽取的key代表了全部的key空间
Redis发布订阅
redis提供了发布订阅功能,可以用于消息的传输,redis提供了一组命令可以让开发者实现“发布/订阅”模式,该模式同样可以实现经常见的消息传递,它的实现原理是:
发布/订阅包含两种角色,分别是发布者和订阅者,订阅者可以订阅一个或多个频道,而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到该消息。
发布者发布消息的命令是publish,用法是:
PUBLISH channel message
比如向channel.1 发一个消息:hello
PUBLISH channel.1 “hello”
这样就实现了消息的发送,该命令的返回值表示接收到这条消息的订阅者数量,因为在执行这条命令的时候,还没有订阅者订阅该频道,所以返回0,另外值得注意的是消息发送出去不会持久化,如果发送之前没有订阅者,那么后续再有订阅者订阅该频道,之前的消息就收不到了。
订阅者订阅消息的命令是 subscribe
SUBSCRIBE channel [channel …]
该命令同时可以订阅多个频道,比如订阅channel.1频道,SUBSCRIBE channel.1
执行subscribe命令后客户端会进入订阅状态
结构图
channel分两类,一个是普通channel、另一个是pattren channel(规则匹配),producer1发布了一条消息【publish abc hello】,redis server 会发给abc这个普通channel上的所有订阅者,同时abc也匹配上了pattern channel的名字,所以这条消息也会发生给pattern channel *bc上所以的订阅者
redis如何做到数据持久化的
redis支持两种方式的持久化,一种是rdb方式,另一种是aof(append-only-file)方式,前者会根据指定的规则“定时”将内存中的数据存储在硬盘上,而后者在每次执行命令后将命令本身记录下来,两种持久化方式可以单独使用其中一种,也可以将两种方式结合使用。
RDB方式
当符合一定条件时,redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据非法的完整性不是非常明确,那RDB方式要比AOF方式更加高效,RDB方式确定最后一次持久化后的数据可能丢失。
fork的作用是复制一个与当前进程一样的进程,新进程的所有数据(变量,环境变量,程序计数器等)数据都和原进程一致,但是一个全新的进程,并作为原进程的子进程
redis会在以下几种情况下对数据进行快照
1.根据配置规则进行自动快照
2.用户执行save或者GBsave命令
3.执行flushall命令
4.执行赋值replication时
根据配置规则进行自动快照
redis允许用户自定义快照条件,当符合快照条件时,redis会自动执行快照操作,快照的条件用户可以在配置文件中配置。配置格式如下
save seconds count
第一个参数是时间窗口,第二个是键的个数,也就是说,在第一个时间参数配置范围内给修改的键的个数大鱼后面的changes时,即符合快照条件,redis默许配置了三个规则
save 900 1
save 300 10
save 60 10000
每条快照规则占一行,每条规则之间是或的关系,在900秒内有一个以上的键被更改则进行快照
用户执行save或bgsave命令
除了让redis自动进行快照以外,当我们对服务进行重启或者服务器迁移我们需要人工去干预备份,redis提供了两台命令来完成这个任务
save命令
当执行save命令时,redis同步做快照操作,在快照执行过程中会阻塞所有来自客户端的请求,当redis内存中的数据较多时,通过该命令将导致redis较长时间的不响应,所有不建议在生产环境上使用这个命令
bgsave
bgsave命令可以在后台异步的进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求,执行bgsave后,redis会立即返回ok表示开始执行快照操作。
通过lastsave命令可以获取最近一次执行快照的时间;(自动快照采用的是异步快照操作)
执行FLUSHALL命令
该命令在前面讲过,会清除redis在内存中的所有数据,执行该命令后,只要redis中配置的快照规则不为空,也就是save的规则存在,redis就会执行一次快照操作,不管规则是什么样的都会执行,如果没有定义快照规则,就不会执行快照操作。
执行复制replication时
该操作主要是在主从模式下,redis会在复制初始化时进行自动快照,这个会在后面讲到
这里只需了解当执行复制操作时,已经没有定义自动快照规则,并且没有手动执行过快照操作,它任然会生产RDB快照文件
AOF方式
当使用redis存储非临时数据时,一般需要打开AOF持久化来降低进程终止导致的数据丢失,AOF可以将redis执行的每一条命令追加到硬盘文件中,这一过程会降低redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘可以提高AOF的性能。
开启AOF
默认情况下redis是没有开启AOF(append-of-file)方式持久化,可以通过appendonly参数启用,在redis.conf中找到appendonly yes
开启AOF持久化后每执行一条会更改redis中数据的命令行,redis就会将该命令写入硬盘中的AOF文件,AOF文件的保存文件和RBD文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以在redis.conf中的属性appendfilename appendonlyh.aof修改
AOF的实现
AOF文件以纯文本的形式记录Redis执行写命令例如开启AOF持久化的情况下执行如下4条命令
set foo 1
set foo 2
set foo 3
get
redis会将前三条命令写入AOF文件中,通过vim的方式可以看到aof文件中的内容
//自动重新AOF文件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percetage 表示的是当前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写过,则以启动时的aof文件大小为依据。
auto-aof-rewrite-min-size 表示限制了允许重写的最小AOF文件大小,通常在AOF文件很小的情况下即使其中有很多冗余的命令我们不太关心。
另外,还可以通过bgrewriteaof命令手动执行aof,执行完以后冗余的命令依据被删除;
在启动时,redis会逐个执行aof文件中的命令来讲硬盘中的数据载入到内存中,载入的速度相对于rdb会慢一些。
AOF重写原理
redis可以在AOF文件体积变得过大时,自动的在后台对AOF进行重写:重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。
重写的流程是这样,主进程会fork一个子进程出来重写AOF,这个重写过程并不是基于原有的aof文件来做的,而是有点类似于快照的方式,全量遍历内存中的数据,然后逐个序列到aof文件中,在fork子进程这个过程中,服务端仍然可以对外提供服务,那这个时候重写的aof文件的数据和redis内存数据不一致了怎么办,不用担心,这个过程中,主进程的数据更新操作,会缓存到aof_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间收到的命令,当子进程重写完以后再把缓存中的数据追加到新的aof文件。
当所有的数据全部追加到新的aof文件中后,把新的aof文件重命名,此后所有的操作都会被写入新的aof文件。
如果在rewrite过程中出现故障,不会影响原理aof文件的正常工作,只有当rewrite完成后才会切换文件。因此这个rewrite过程是比较可靠的
Redis内存回收策略
redis中提供了多种内存回收策略,当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对象,释放这些对象占用的空间,那么选择淘汰哪些对象呢?
其中,默认的策略为noeviction策略,当内存使用达到阈值的时候,所有申请内存的命令都会报错
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
适用的场景,如果我们的应用对缓存的访问都是相对热点数据,那么可以选择这个策略
allkeys-random:随机移除某个key
适用的场景:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的淘汰数据
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
总结:
实际上redis实现的LRU并不是可靠的LRU,也就是名义上我们使用LRU算法淘汰内存数据,但是实际上被淘汰的键并不一定真正的最少使用的数据,这里涉及到一个权衡的问题,如果需要在所有的数据中搜索最符合条件的数据,那么一定会增加系统的开销,redis是单线程的,所以耗时的操作会谨慎一些,为了在一些成本内实现相对的LRU,早前redis是基于采样的lru,也就是放弃了从所有数据中搜索最优解,reids3.0版本之后,redis作者基于采样的lru进行了一些优化,目前是在一定成本内让结果更靠近真实的lru
Redis是单进程单线程?性能为什么这么快?
redis采样的一种非常简单的做法,单线程来处理来自所有客户端的请求,redis把任务封闭在一个线程中从而避免了线程安全问题,redis为什么是单线程?
官方解释是:CPU并不是redis的瓶颈所在,Redis的瓶颈主要在机器的内存和网络的宽带,那么redis能不能处理高并发请求呢?当然可以,至于怎么实现的,我们来具体了解一下(注意并发不等于并行,并发性IO流,意味着能够让一个计算单元来处理来自多个客户端的流请求,并发性,意味着服务器能够同时执行几个事情,具有多个计算单元)
多路复用
Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞,所有I/O操作一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致这个经常无法对其他客户提供服务,而I/O多路复用就是为了解决这个问题而出现的。
了解多路复用之前,先简单了解下几种I/O模型
(1)同步阻塞IO(BIO Blocking IO):即传统的IO模型
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK
(3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,也称为异步阻塞IO,Java中的selector和linux中epoll都是这种模型
(4)异步IO(Asychronous IO ):即经典的Proactor设计模式,也称为异步非阻塞IO
同步和异步、阻塞和非阻塞的区别:
同步和异步,指的是用户线程和内核的交互方式
阻塞和非阻塞,指用户线程调用内核IO操作的方式,是阻塞还是非阻塞
就像java中使用多线程做异步处理的概念,通过多线程去执行一个流程,主线程可以不用等待,而阻塞和非阻塞我们可以理解为假如在同步流程或异步流程中做IO操作,如果缓冲区数据还没准备好,IO的这个过程会阻塞。