玩转Redis缓存

五种数据结构简介

Redis是使用C编写的,内部实现了一个struct结构体redisObject对象,通过结构体来模仿面向对象编程的“多态”,动态支持不同类型的value。作为一个底层的数据支持,redisObject结构体代码如下定义:

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
typedef struct redisObject {
   //对象的数据类型,占4bits,共5种类型
    unsigned type:4;        
    //对象的编码类型,占4bits,共10种类型
    unsigned encoding:4;
    //least recently used
    //实用LRU算法计算相对server.lruclock的LRU时间
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    //引用计数
    int refcount;  
    //指向底层数据实现的指针
    void *ptr;
} robj;

下面介绍type、encoding、ptr3个属性定义枚举。
type:redisObject的类型,字符串、列表、集合、有序集、哈希表

//type的占5种类型:
/* Object types */
#define OBJ_STRING 0    //字符串对象
#define OBJ_LIST 1      //列表对象
#define OBJ_SET 2       //集合对象
#define OBJ_ZSET 3      //有序集合对象
#define OBJ_HASH 4      //哈希对象

Redis的持久化

我们知道redis与memcached的一个很大的不同是redis可以将数据持久化到磁盘,能持久化意味着数据的可靠性的提升。
RDB(redis database)是一个磁盘存储的数据库文件,其中保存的是最后一次写入时内存数据的最后状态。由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化,这称为“半持久化模式”),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件,这称为“全持久化模式”)。
RDB的持久化方式通过配置的定时执行间隔定时将内存中的数据写入到一个新的临时RDB文件中,然后用这个临时文件替换上次持久化的RDB文件,如此不断的定时更替。
当redis server重启时,会检查当前配置的持久化方式,如果是AOF(Append Of File)则以AOF数据作为恢复数据,因为AOF备份的准确性往往比RDB更高。如果是只开启了RDB模式的话则会加载最新的RDB文件内容到内存中。
另外redis也提供了手动调用的命令来实施RDB备份,包括阻塞的持久化和非阻塞的持久化。

RDB持久化

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
RDB持久化的时间间隔可以配置,和该配置项一起配合使用的还有另一个指标“变更次数”,每次时间间隔时必须同时符合“间隔时间”和“变更次数”两个条件才会进行RDB持久化,否则当次的持久化过程会推迟到下一个时间间隔再判断是否符合条件。

AOF持久化

当Redis开启AOF持久化时,每次接收到操作指令后,先将操作命令和数据以格式化的方式追加到操作日志文件的尾部,追加成功后才进行内存数据库的数据变更。这样操作日志文件就保存了所有的历史操作过程。该过程与MySQL的bin.log、zookeeper的txn-log十分相似。
AOF保存的是每次操作的操作序列,相比较而言RDB保存的是数据快照,因此AOF的操作日志文件内容往往比RDB文件大。
需要注意的是,因为linux对文件的写操作采取了“延迟写入”手段,因此redis提供了always、everysec、no三种选择来决定直接调用操作系统文件写入的刷盘动作。
AOF先记录后变更的特性决定了数据的可靠性更高,因此当AOF和RDB持久化都配置时,Redis服务在重启后会优先选择AOF数据作为数据恢复标准。
执行AOF数据恢复时,Redis读取AOF文件中的“操作+数据”集,通过逐条重放的方式恢复内存数据库。
AOF文件会不断增大,它的大小直接影响“故障恢复”的时间,而且AOF文件中历史操作是可以丢弃的。AOF rewrite操作就是“压缩”AOF文件的过程,当然redis并没有采用“基于原aof文件”来重写的方式,而是采取了类似snapshot的方式:基于copy-on-write,全量遍历内存中数据,然后逐个序列到aof文件中。因此AOF rewrite能够正确反应当前内存数据的状态,这正是我们所需要的。rewrite过程中,对于新的变更操作将仍然被写入到原AOF文件中,同时这些新的变更操作也会被redis收集起来(buffer,copy-on-write方式下,最极端的可能是所有的key都在此期间被修改,将会耗费2倍内存),当内存数据被全部写入到新的aof文件之后,收集的新的变更操作也将会一并追加到新的aof文件中,此后将会重命名新的aof文件为appendonly.aof,此后所有的操作都将被写入新的aof文件。如果在rewrite过程中,出现故障,将不会影响原AOF文件的正常工作,只有当rewrite完成之后才会切换文件,因为rewrite过程是比较可靠的。

Redis事务

Redis事务通常会使用MULTI,EXEC,WATCH等命令来完成,redis实现事务的机制与常见的关系型数据库有很大的却别,比如redis的事务不支持回滚,事务执行时会阻塞其它客户端的请求执行等。

事务实现相关的指令
MULTI
用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,每一个指令的返回结果都是“QUEUED”。只有先执行MULTI指令后才能使用EXEC命令原子化地执行这个命令序列。总是返回OK。
EXEC
在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。EXEC指令的返回值是队列中多条指令的有序结果。
当在事务中使用了WATCH命令监控的KEY时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的队列命令集合。
DISCARD
清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。
如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。
WATCH
watch 用于在进行事务操作的最后一步也就是在执行exec 之前对某个key进行监视,如果这个被监视的key被改动,那么事务就被取消,否则事务正常执行。一般在MULTI 命令前就用watch命令对某个key进行监控。如果当前连接监控的key值被其它连接的客户端修改,那么当前连接的EXEC命令将执行失败。
WATCH命令的作用只是当被监控的键值被修改后阻止事务的执行,而不能保证其他客户端不修改这一键值。
UNWATCH
清除所有先前为一个事务监控的键。执行EXEC命令后会取消对所有键的监控,如果不想执行事务中的命令也可以使用UNWATCH命令来取消监控。UNWATCH命令,清除所有受监控的键。在运行UNWATCH命令之后,Redis连接便可以再次自由地用于运行新事务。

redis事务从开始到结束通常会通过三个阶段:
1)事务开始
2)命令入队
3)事务执行
标记事务的开始,MULTI命令可以将执行该命令的客户端从非事务状态切换成事务状态,这一切换是通过在客户端状态的flags属性中打开REDIS_MULTI标识完成, 在打开事务标识的客户端里,这些命令,都会被暂存到一个命令队列里,不会因为用户的输入而立即执行。客户端打开了事务标识后,只有命令: EXEC, DISCARD, WATCH,MULTI命令会被立即执行,其它命令服务器不会立即执行,而是将这些命令放入到一个事务队列里面,然后向客户端返回一个QUEUED回复 。redis客户端有自己的事务状态,这个状态保存在客户端状态mstate属性中。

事务的ACID性质详解
在redis中事务总是具有原子性(Atomicity),一致性(Consistency)和隔离性(Isolation),并且当redis运行在某种特定的持久化模式下,事务也具有持久性(Durability)。

原子性

事务具有原子性指的是事务中的多个操作当作一个整体来执行,服务器要么就执行事务中的所有操作,要么就一个操作也不执行。但是对于redis的事务功能来说,事务队列中的命令要么就全部执行,要么就一个都不执行,因此redis的事务是具有原子性的(有条件的原子性)。我们通常会知道两种关于redis事务原子性的说法:一种是要么事务都执行,要么都不执行;另外一种说法是redis事务,当事务中的命令执行失败后面的命令还会执行,错误之前的命令不会回滚。其实这个两个说法都是正确的,redis分语法错误和运行错误。

语法错误:如果redis出现了语法错误,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行。
运行错误:运行错误表示命令在执行过程中出现错误,比如用GET命令获取一个散列表类型的键值。这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果事务里有一条命令执行错误,其他命令依旧会执行(包括出错之后的命令)。
只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。

一致性

事务具有一致性指的是如果在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该仍然一致的。 “一致”指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。redis通过谨慎的错误检测和简单的设计来保证事务一致性。如果遇到运行错误,redis的原子性也不能保证,所以一致性也是有条件的一致性。

隔离性

事务的隔离性指的是即使有多个事务并发在执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同。 因为redis使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断。因此redis的事务总是以串行的方式运行的,并且事务也总是具有隔离性的 。

持久性

事务的持久性指的是当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质里面。 因为redis事务不过是简单的用队列包裹起来一组redis命令,redis并没有为事务提供任何额外的持久化功能,所以redis事务的持久性由redis使用的模式决定 :

当服务器在无持久化的内存模式下运行时,事务不具有持久性,一旦服务器停机,包括事务数据在内的所有服务器数据都将丢失 ;
当服务器在RDB持久化模式下运作的时候,服务器只会在特定的保存条件满足的时候才会执行BGSAVE命令,对数据库进行保存操作,并且异步执行的BGSAVE 不能保证事务数据被第一时间保存到硬盘里面,因此RDB持久化模式下的事务也不具有持久性 ;
当服务器运行在AOF持久化模式下,并且appedfsync的选项的值为always时,程序总会在执行命令之后调用同步函数,将命令数据真正的保存到硬盘里面,因此这种配置下的事务是具有持久性的;
当服务器运行在AOF持久化模式下,并且appedfsync的选项的值为everysec时,程序会每秒同步一次命令数据到磁盘因为停机可能会恰好发生在等待同步的那一秒内,这种可能造成事务数据丢失,所以这种配置下的事务不具有持久性。
过期数据清除
数据过期时间
通过EXPIRE key seconds命令来设置数据的过期时间。返回1表明设置成功,返回0表明key不存在或者不能成功设置过期时间。key的过期信息以绝对Unix时间戳的形式存储(Redis2.6之后以毫秒级别的精度存储)。这意味着,即使Redis实例没有运行也不会对key的过期时间造成影响。
key被DEL命令删除或者被SET、GETSET命令重置后与之关联的过期时间会被清除。
更新了存储在key中的值而没有用全新的值替换key原有值的所有操作都不会影响在该key上设置的过期时间。例如使用INCR命令增加key的值或者通过LPUSH命令在list中增加一个新的元素或者使用HSET命令更新hash字段的值都不会清除原有的过期时间设置。
若key被RENAME命令重写,比如本存在名为mykey_a和mykey_b的key一个RENAME mykey_b mykey_a命令将mykey_b重命名为本已存在的mykey_a。那么无论mykey_a原来的设置如何都将继承mykey_b的所有特性,包括过期时间设置。
EXPIRE key seconds应用于一个已经设置了过期时间的key上时原有的过期时间将被更新为新的过期时间。

过期数据删除策略–被动方式结合主动方式

当clients试图访问设置了过期时间且已过期的key时,这个时候将key删除再返回空,为主动过期方式。但仅是这样是不够的,因为可能存在一些key永远不会被再次访问到,这些设置了过期时间的key也是需要在过期后被删除的。因此,Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除,这种为被动过期方式。典型的方式为,Redis每秒做10次如下的步骤:
1)随机测试100个设置了过期时间的key
2)删除所有发现的已过期的key
3)若删除的key超过25个则重复步骤1
这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值