Redis原理

过期时间
Redis提供两个设置过期时间的方式:
  1. expire key seconds
  2. setex(String key, int seconds, String value)
实现过期的原理:
  1. 消极方式(passive way):在主键被访问时如果发现它已经失效,那么就删除它;
  2. 积极方式:周期性的从设置了过期时间的key中选择一部分的key进行删除;
  • 随机测试20个带有timeout信息的key,对已经失效的key进行删除。
  • 如果超过25%的key被删除则重复执行整个流程。
发布和订阅
Redis提供了一组命令可以让开发者实现”发布/订阅”模式(publish/subscribe)。该模式同样可以实现进程间的消息传递。发布/订阅模式包含两种 角色,分别是发布者和订阅者。订阅者可以订阅一个或多个频道,而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到该消息。
发布者发布消息命令是PUBLISH,用法: PUBLISH channel message;
//比如向channel.1发一条消息’hello’:
PUBLISH channel.1 “hello”
订阅者订阅消息的命令是 SUBSCRIBE,用法: SUBSCRIBE channel [channel  ];
//比如订阅channel.1的频道:
SUBSCRIBE channel.1
//执行SUBSCRIBE命令后客户端会进入订阅状态
说明: PUBLISH命令的返回值表示接收到这条消息的订阅者数量。 如果发布命令时还没有订阅者订阅该频道,则返回0。另外值得注意的是 消息发送出去不会持久化,如果发送之前没有订阅者,那后续再有订阅者订阅该频道,之前的消息就收不到了;
结构图:
结构说明:channel分两类,一个是普通channel、另一个是pattern channel(规则匹配), producer1发布了一条消息【publish abc hello】,Redis server发给abc这个普通channel上的所有订阅者,同时abc也匹配上了pattern channel的名字,所以这条消息也会同时发送给pattern channel *bc上的所有订阅者。
应用举例:Redis集群的 哨兵就是利用Redis的发布订阅机制实现对整个Redis集群进行监控的。
持久化方式
Redis支持两种方式的持久化,一种是RDB方式、另一种是AOF(append-only-file)方式。前者根据指定的规则“定时”将内存中的数据存储在硬盘上,而后者在每次执行命令后将命令本身记录下来。两种持久化方式可以单独使用其中一种,也可以将这两种方式结合使用。
  • RDB
当符合一定条件时,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Redis会在以下几种情况下对数据进行快照:
1.根据配置规则进行自动快照;(自动快照采用的是异步快照操作)
Redis允许用户自定义快照条件,当符合条件时自动执行快照操作。快照的条件可以由用户在配置文件中配置。配置格式如下:
save [seconds] [keynum]
说明:第一个参数是时间,第二个是键个数,也就是说,在第一个时间参数配置范围内被更改的键的个数大于后面的keynum时,即符合快照条件。redis默认配置了三个规则
save 900 1
save 300 10
save 60 10000
每条快照规则占一行,每条规则之间是“或”的关系。 在900秒(15分)内有一个以上的键被更改则进行快照。
2.用户执行SAVE或者BGSAVE命令;
说明:除了让Redis自动进行快照以外,当我们对服务进行重启或者服务器迁移我们需要人工去干预备份。redis提供了两条命令来完成这个任务。通过LASTSAVE命令可以获取最近一次成功执行快照的时间;
(1). save命令
当执行save命令时,Redis同步做快照操作,在快照执行过程中会阻塞所有来自客户端的请求。当redis内存中的数据较多时,通过该命令将导致Redis较长时间的不响应。所以不建议在生产环境上使用这个命令,而是推荐使用bgsave命令。
(2). bgsave命令 
bgsave命令可以在后台异步地进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求。执行BGSAVE后,Redis会立即返回ok表示开始执行快照操作。
3.执行FLUSHALL命令;
该命令会清除redis在内存中的所有数据。执行该命令后,只要redis中配置的快照规则不为空,也就是save的规则存在。redis就会执行一次快照操作。不管规则是什么样的都会执行。如果没有定义快照规则,就不会执行快照操作。
4.执行复制(replication)时;
该操作主要是在主从模式下,redis会在复制初始化时进行自动快照。只需要了解当执行复制操作时,即使没有定义自动快照规则,并且没有手动执行过快照操作,它仍然会生成RDB快照文件。
  • AOF(append only file)
当使用Redis存储非临时数据时,一般需要打开AOF持久化来降低进程终止导致的数据丢失。AOF可以将Redis执行的每一条写命令追加到硬盘文件中,这一过程会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘可以提高AOF的性能。
开启AOF
Redis默认没有开启AOF方式的持久化,可以通过appendonly参数启用,在redis.conf中找到"appendonly yes”。开启AOF持久化后每执行一条会更改Redis中的数据的命令后,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是apendonly.aof;可以在redis.conf中的属性"appendfilename appendonly.aof”修改;
AOF的重写
AOF文件以纯文本的形式记录Redis执行的写命令。
例如开启AOF持久化的情况下执行如下4条命令:
set foo 1
set foo 2
set foo 3
get foo
Redis会将前3条命令写入AOF文件中,通过vim可以看到aof文件中的内容。会发现AOF文件的内容正是Redis发送原始通信的内容,从内容中我们发现Redis只记录了3条命令。查询命令是不会写入AOF文件的。有个问题是前两条命令其实是冗余的,因为这两条命令的结果会被第三条命令覆盖。随着执行的命令越来越多,AOF文件会越来越大,但内存中实际有效的数据可能并不大,这就会造成磁盘空间浪费以及Redis数据还原时间较长。因此我们希望Redis可以自动优化AOF文件。实际上Redis也考虑到了这个问题。可以配置一个条件,每当达到一定条件时Redis就会重写AOF文件。
条件配置为:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
说明:auto-aof-rewrite-percentage 表示当前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_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间收到的命令,当子进程重写完再把缓存中的数据追加到新的AOF文件中。当所有的数据全部追加到新的AOF文件后,把新的AOF文件重命名,此后所有写操作都会被写入新的AOF文件中。如果重写过程中出现故障,不回影响原有的AOF文件,只有当rewrite完成后才会切换文件,因此这个rewrite过程还是比较可靠的。
Redis内存回收策略
noeviction:默认策略,当内存使用达到阈值的时候,所有引起申请内存的命令会报错;
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
适合的场景:如果我们的应用对缓存的访问都是热点数据,可以使用这个策略;
allkeys-random:随机移除某个key;
适合的场景:如果我们的应用对缓存的key的访问概率相等,可以使用这个策略;
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
适合的场景:这种策略使得我们可以向Redis提示哪些key更适合淘汰,我们可以自己控制。
总结:实际上Redis实现的LRU并不是可靠的LRU,名义上使用LRU算法淘汰内存数据,但实际上被淘汰的键并不一定是真正最少使用的数据。这里涉及到一个权衡的问题,如果在所有数据中筛选最符合条件的数据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。为了在一定成本内实现相对的LRU,早期Redis版本基于采样的LRU。Redis3.0之后,基于采样的LRU做了一些优化,目的是在一定成本内让结果更靠近真实LRU。
在Redis中使用Lua脚本
使用Redis时会面临的一些问题:
原子性问题
Redis虽然是单线程的,但是仍然会存在线程安全问题。当然,这个线程安全问题并不是来源于Redis服务器内部。而是Redis作为数据服务器,是提供给多个客户端使用的。多个客户端的操作就相当于同一进程下的多个线程。如果多个客户端之间没有做好数据的同步策略,就会产生数据不一致问题。举个例子,多个客户端的命令之间没有做请求同步,导致实际的执行顺序可能会不一致,最终无法满足原子性。
效率问题
Redis本身吞吐量是非常高的,在实际使用过程中,有一个非常重要的因素影响Redis的吞吐量,那就是网络。我们在使用Redis实现某些特定功能时,很可能需要多个命令或者多个数据类型的交互才能完成。那么这种多次网络请求对性能的影响比较大。虽然Redis做了一些优化,比如提供了pipeline管道操作,但它有一定的局限性,就是执行的多个命令和相应之间是不存在相互依赖关系的。所以我们需要一种机制能够编写一些具有业务逻辑的命令来减少网络请求。
Redis内嵌了对Lua环境的支持,允许开发者使用Lua语言编写脚本传到Redis中执行。 Redis客户端可以使用Lua脚本直接在服务端 原子 的执行多个Redis命令。
使用脚本的好处:
1.减少网络开销:在Lua脚本中可以把多个命令放到同一个脚本中运行;
2.原子操作:Redis会将整个脚本作为一个整体执行,中间不回被其他命令插入;
3.复用性:客户端发送的脚本会存储在Redis中,意味着其他客户端可以复用这一脚本来完成同样的逻辑;
扩展:Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能;
在Lua脚本中调用Redis命令
在Lua脚本中调用Redis命令,可以使用redis.call函数调用。比如我们调用string类型的命令:
redis.call('set’,'name','John')
local value = redis.call('get’,'name')
redis.call函数的返回值就是redis命令的执行结果,redis.call函数会将redis的5种类型的返回值转化为对应的Lua数据类型。在很多情况下我们需要脚本可以有返回值, redis会自动将脚本返回值的Lua数据类型转换为Redis的返回值类型。在脚本中可以使用return语句将值返回给客户端,如果没有执行return,默认返回为nil。
客户端执行lua脚本:
  EVAL命令格式:[EVAL][脚本内容][key参数数量][key…][arg…]
通过key和arg两个参数向脚本中传递参数,它们的值可以在脚本中分别使用KEYS和ARGV这两个类型的全局变量访问。比如我们在Redis客户端中调用脚本实现一个set命令:
lua脚本内容:return redis.call('set',KEYS[1],ARGV[1])    //KEYS和ARGV必须大写
eval “return redis.call(‘set',KEYS[1],ARGV[1])” 1 name John
注意:EVAL命令根据key的数量(例子中的参数’1’)来将后面所有参数分别存入脚本中KEYS和ARGV两个表类型的全局变量,当脚本不需要参数时也不能省略[key参数数量]这个参数,如果没有参数则为0。如:eval “return redis.call(‘get','name')” 0
EVALSHA命令格式:[EVALSHA][脚本SHA1摘要][key参数数量][key…][arg…]
通过eval执行lua脚本,在脚本较长的情况下,每次调用脚本都要把脚本传给Redis服务端,比较占用带宽。为了解决这个问题,Redis提供了EVALSHA命令允许开发者通过脚本内容的SHA1摘要来执行脚本。该命令用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要。
1.Redis在执行EVAL命令时会计算脚本的SHA1摘要并将脚本记录在脚本缓存中。
2.执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了就执行,否则返回“NOSCRIPT No matching script,Please use EVAL”。  
通过以下案例来演示EVALSHA命令效果:
script load “return redis.call(‘get’,’name’)”    //将脚本加入缓存并生成SHA1摘要;
evalsha “a5a402e90df3eaeca2ff03d56d99982e05cf6574” 0    //在Redis客户端根据脚本摘要调用缓存的脚本;
经验:我们在调用eval命令前,先执行evalsha命令,如果提示脚本不存在,则再调用eval命令。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值