@xxliixin1993
2016-07-11T14:26:11.000000Z
字数 8083
阅读 3162
database
redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;
AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。
如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样。
RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。
redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。
对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
虽然RDB有不少优点,但它的缺点也是不容忽视的。如果你对数据的完整性非常敏感,那么RDB方式就不太适合你,因为即使你每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失。所以,redis还提供了另一种持久化方式,那就是AOF。
AOF,英文是Append Only File,即只允许追加不允许改写的文件。
如前面介绍的,AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍,就这么简单。
我们通过配置redis.conf中的appendonly yes就可以打开AOF功能。如果有写操作(如SET等),redis就会被追加到AOF文件的末尾。
默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。
如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,也没有关系,redis提供了redis-check-aof工具,可以用来进行日志修复。因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。举个例子或许更形象,假如我们调用了100次INCR指令,在AOF文件中就要存储100条指令,但这明显是很低效的,完全可以把这100条指令合并成一条SET指令,这就是重写机制的原理。
在进行AOF重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响AOF文件的可用性,这点大家可以放心。
AOF方式的另一个好处,我们通过一个“场景再现”来说明。某同学在操作redis时,不小心执行了FLUSHALL,导致redis内存中的数据全部被清空了,这是很悲剧的事情。不过这也不是世界末日,只要redis配置了AOF持久化方式,且AOF文件还没有被重写(rewrite),我们就可以用最快的速度暂停redis并编辑AOF文件,将最后一行的FLUSHALL命令删除,然后重启redis,就可以恢复redis的所有数据到FLUSHALL之前的状态了。是不是很神奇,这就是AOF持久化方式的好处之一。但是如果AOF文件已经被重写了,那就无法通过这种方法来恢复数据了。
虽然优点多多,但AOF方式也同样存在缺陷,比如在同样数据规模的情况下,AOF文件要比RDB文件的体积大。而且,AOF方式的恢复速度也要慢于RDB方式。
如果你直接执行BGREWRITEAOF命令,那么redis会生成一个全新的AOF文件,其中便包括了可以恢复现有数据的最少的命令集。
如果运气比较差,AOF文件出现了被写坏的情况,也不必过分担忧,redis并不会贸然加载这个有问题的AOF文件,而是报错退出。这时可以通过以下步骤来修复出错的文件:
1.备份被写坏的AOF文件
2.运行redis-check-aof –fix进行修复
3.用diff -u来看下两个文件的差异,确认问题点
4.重启redis,加载修复后的AOF文件
AOF重写的内部运行原理,我们有必要了解一下。
在重写即将开始之际,redis会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
与此同时,主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中。
当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中了。
语法:set 键名称 值
set name lx
语法:get 键名
get name
设置键时,先判断一下该键是否存在,如果key已经存在,返回0。如果不存在再创建。
语法:setnx 键名称 值
setnx name lx
设置key对应的值为string类型的value,并指定此键值对应的有效期。
语法:setex 名称 有效期 值
setnx name 10 lx
一次设置多个key的值,成功返回ok表示所有的值都设置了,失败返回0表示没有任何值被设置。
语法: mset 键名1 值1 键名2 值2
mset name lx age 18
相当于set name lx
set age 18
一次设置多个key的值,成功返回ok表示所有的值都设置了,失败返回0表示没有任何值被设置,但是不会覆盖已经存在的key,只设置不存在的key
语法: msetnx 键名1 值1 键名2 值2
msetnx name lx age 18
获取key的value值的范围内的子字符串
语法: getrange 键名 起始范围 终止范围
getrange name 0 5
获取name的前5个字符
一次获取多个key的值,如果对应key不存在则对应返回nil。
语法: mget 键名1 键名2
mget name age
对key的值做加加操作,并返回新的值。
语法: incr 键名
incr age
同incr类似,加指定值,key不存在时候会设置key,并认为原来的value是0。
语法: incrby 键名 加多少
incrby age 20
对key的值做减减操作。 decrby 同decr类似,减指定值
取指定key的value值的长度。
语法: strlen 键名
strlen name
给指定key的字符串追加value,返回新字符串值的长度。
语法: append 键名 添加内容
append name andTomIsCat
Redis hash是一个string类型的field和value的映射表。它的添加、删除操作都是0(1)(平均)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。
设置hash field为指定值,如果 key不存在,则先创建。
语法: hset 哈希名称 字段名称 值
hset User1 name lx
取出hash field的值。
语法:hget 哈希名称 字段名称
hget User1 name
设置hash field为指定值,如果key不存在,则先创建,如果存在则返回0。
语法: hsetnx 哈希名称 字段名称 值
hsetnx User1 name lx
同时设置hash的多个field
语法:hmset 哈希名称 field1 key1 value1 field2 key2 value2
hmset User1 name lx Admin1 name zhang
获取全部指定的hash field。
语法:hmget 哈希名称 field1 field2
hmget User1 name age email
指定的 hash field加上给定的值。
语法:hincrby 哈希名称 key1 添加步长
hincrby User1 age 10
测试指定的 field是否存在。存在返回1,不存在返回0
语法:hexists 哈希名称 key1
hexists User1 age
返回指定hash的field数量.
语法:hlen 哈希名称
hlen User1
删除指定hash的field
语法:hdel 哈希名 field
hdel User1 name
返回hash的所有field
语法:hkeys 哈希名
hkeys User1
返回hash的所有 value。
语法:hvals 哈希名
hvals User1
获取某个hash中全部的field及value
语法:hgetall 哈希名
hgetall User1
list是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等,操作中key 理解为链表的名字。redis的list类型其实就是一个每个子元素都是string 类型的双向链表。我们可以通过push、pop操作从链表的头部或者尾部添加删除元素,这样list即可以作为栈,又可以作为队列。
在key对应list的头部添加字符串元素。
语法:lpush 链表名称 值内容
lpush ListTest1 'han'
获取链表里面的值
语法:lrange 链表名称 起始 终止
lrange ListTest1 0 2
ps:lrange 链表名称 0 -1
注意0 和 -1 表示取值范围,从头部到尾部。
在key对应list的尾部添加字符串元素。
语法:rpush 链表名称 值内容
rpush ListTest1 'zhang'
在key对应list的特定位置前或后添加字符串。
语法:linsert 链表名称 BEFORE 已存在作为位置的值 添加值
INSERT list1 BEFORE "bar" "Yes"
设置list中指定下标的元素值。注:下标从0开始计算
语法:lset 链表名称 下标值 添加值
lset ListTest1 0 'lee'
根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。
语法:lrem 链表名称 COUNT VALUE
LREM mylist -2 "hello"
从尾向头搜索两个hello删除
保留指定key的值范围内的数据,就是对一个列表进行修剪。
语法:ltrim 链表名称 起始值 终止值
ltrim ListTest1 0 2
只保留下标为0到下标为2的数据
从list的头部删除一个元素,并返回删除元素。
语法:lpop 链表名称
lpop ListTest1
从 list的尾部删除一个元素,并返回删除元素。
语法:rpop 链表名称
rpop ListTest1
从第一个list的尾部移除元素并添加到第二个list的头部。
语法:rpoplpush 链表名称1 链表名称2
rpoplpush ListTest1 mylist
返回链表名称指定index位置的元素。
语法:lindex 链表名称 下标
lindex ListTest1 0
返回对应list的长度。
语法:llen 链表名称
llen ListTest
set是集合,它是string类型的无序集合。set是通过hashTable实现的、添加、删除和查找的复杂度都是0(1)。对集合我们可以取并集、交集、差集。通过这些操作我们可以实现sns中的好友推荐和blog的tag功能。
向集合中添加元素。
语法:sadd 集合名 元素
sadd SetTest1 lx
获取集合中内容
语法:smembers 集合名称
smembers SetTest1
删除名称为key的set中的元素.
语法:srem 集合名称 key
srem SetTest1 lx
随机返回并删除set中一个元素。
语法:spop 集合名称
spop SetTest1
返回给定集合之间的差集。不存在的集合 key 将视为空集。
语法:sdiff 集合名称1 集合2
sdiff myset1 myset2
返回指定集合的差集并存在新的集合种
语法:sdiffstore 新的集合 集合1 集合2
sdiffstore NewSet myset1 myset2
返回所有给定集合的交集.
语法:sinter 集合1 集合2
sinter myset1 myset2
返回指定集合的交集并存在新的集合种
语法:sinterstore 新的集合 集合1 集合2
sinterstore NewSet myset1 myset2
返回所有给定集合的并集
语法:sunion 集合1 集合2
sunion myset1 myset2
返回指定集合的并集并存在新的集合种
语法:sunionstore 新的集合 集合1 集合2
sunionstore NewSet myset1 myset2
移除第一个集合的value并添加到第二个集合种
语法:smove 集合1 集合2 集合1中移除的值
smove SetTest1 myset1 lx
返回集合种的元素个数。
语法:scard 集合名
scard SetTest
测试集合种是否是名称为key的的元素,存在返回1 不存在返回0
语法:sismember 集合名 key
sismember SetTest lx
随机返回set的一个元素,但不删除元素
语法:srandmember 集合名
srandmember SetTest lx
sorted set是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解为有两列的mysql表,一列存value,一列存顺序。操作中的key理解为zset的名字。
向zset集合中添加元素。如果该元素存在,则更新其顺序。
语法:zadd 集合名 序号 内容
zadd ZSetTest 1 lx
获取有序集合中的内容
语法:zrange 集合名 起始值 终止值 withscore
zrange ZSetTest 0 -1 withscore
删除名称为key的zset中的元素。
语法:zrem 集合名 要删除的元素的key
zrem ZSetTest lx
对有序集合中指定成员的分数加上增量 increment可以通过传递一个负数值 increment ,让分数减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。
当 key 不存在,或分数不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。当key不是有序集类型时,返回一个错误。分数值可以是整数值或双精度浮点数。
语法:zincrby 集合名 增长量 key
zincrby ZSetTest 10 lx
返回key在zset中元素的排名,即下标。
语法:zrank|zrevrank 集合名 key
zrank|zrevrank ZSetTest lx
获取有序集合中的内容
语法:zrevrange 集合名 起始值 终止值 withscore
zrevrange ZSetTest 0 -1 withscore
返回集合中score在给定区间的元素
语法:zrangebyscore 集合名 起始值 终止值 withscore
zrangebyscore ZSetTest 2 3 withscore
返回集合中score在给定区间的数量。
语法:zcount 集合名 起始值 终止值
zcount ZSetTest 2 3
返回集合中元素的个数
语法:zcard 集合名
zcard ZSetTest
用于移除有序集中,指定排名(rank)区间内的所有成员。
redis 127.0.0.1:6379> ZADD salary 2000 jack
(integer) 1
redis 127.0.0.1:6379> ZADD salary 5000 tom
(integer) 1
redis 127.0.0.1:6379> ZADD salary 3500 peter
(integer) 1
redis 127.0.0.1:6379> ZREMRANGEBYRANK salary 0 1 # 移除下标 0 至 1 区间内的成员
(integer) 2
redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 有序集只剩下一个成员
1) "tom"
2) "5000"
<div class="md-section-divider"></div>
移除有序集中,指定分数(score)区间内的所有成员。
redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示有序集内所有成员及其 score 值
1) "tom"
2) "2000"
3) "peter"
4) "3500"
5) "jack"
6) "5000"
redis 127.0.0.1:6379> ZREMRANGEBYSCORE salary 1500 3500 # 移除所有薪水在 1500 到 3500 内的员工
(integer) 2
redis> ZRANGE salary 0 -1 WITHSCORES # 剩下的有序集成员
1) "jack"
2) "5000"