Redis介绍

一、简介
         HackerNews在2012年发布了一份数据库的使用情况调查,结果显示有近12%的公司在使用Redis。国内如新浪微博、街旁和知乎,国外如GitHub、Stack Overflow、Flickr、暴雪和Instagram,都是Redis的用户。 
         
         Redis是一个开源的高性能键值对数据库,  在一台普通的笔记本电脑上,Redis可以在一秒内读写超过十万个键值。 它通过提供多种键值数据类型来适应不同场景下的存储需求,并借助许多高层级的接口使其可以胜任如缓存、队列系统等不同的角色。
 
         Redis是REmoteDIctionary Server(远程字典服务器)的缩写,它以字典结构存储数据,并允许其他应用通过TCP协议读写字典中的内容。同大多数脚本语言中的字典一样,Redis字典中的键值除了可以是字符串,还可以是其他数据类型。到目前为止Redis支持的键值数据类型如下:
●字符串类型
●散列类型
●列表类型
●集合类型
●有序集合类型
 
这种字典形式的存储结构与常见的MySQL 等关系数据库的二维表形式的存储结构有很
大的差异。举个例子,如下所示,可以这样存储一篇文章的数据(包括标题、正文、阅读量和标签):
title="Hello World!"
content="Blablabla..."
views=0
tags=["PHP","Ruby","Node.js"]
 
Redis数据库中的所有数据都存储在内存中,不过Redis提供了对 持久化 的支持,即将可以内存中的数据异步写入到硬盘中,同时不影响继续提供服务。
 
Redis可以为每个键设置生存时间(Time ToLive,TTL),生存时间到期后键会自动被删除,这一功能配合出色的性能让Redis可以作为缓存系统来使用。
 
Redis是单线程模型,但它的性能已经足够优异,在绝大部分场合下其性能都不会成为瓶颈。
 
Redis提供了一百多个命令(如图1-2所示),听起来很多,但是常用的却只有十几个,并且每个命令都很容易记忆。
 
二、支持数据类型
         redis默认有16个数据库,从0开始递增,与redis连接后默认选择0号数据库,可以随时使用SELECT命令更换数据库,如要选择1号数据库:
redis>SELECT1
OK
redis[1]>GET foo
(nil)
 
         Redis不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库存储B应用的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内存只有1MB左右,所以不用担心多个Redis实例会额外占用很多内存。
 
●字符串类型
1.赋值与取值
SETkey value
GETkey
SET和GET是Redis中最简单的两个命令,它们实现的功能和编程语言中的读写变量相
似,如key="hello"在Redis中是这样表示的:
redis>SET key hello
OK
 
2.递增数字
INCR key
字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值,用法为:
redis>INCR num
(integer) 1
redis>INCR num
(integer) 2
相应DECR key是递减命令。
 
3. 增加指定的整数
INCRBY key increment
redis>INCRBY bar 2
(integer)2
 
相应DECRBY key decrement减少指定的整数。
 
4.增加指定浮点数
INCRBYFLOAT key increment
INCRBYFLOAT 命令类似INCRBY命令,差别是前者可以递增一个双精度浮点数,如:
redis>INCRBYFLOAT bar 2.7
"6.7"
 
5. 向尾部追加值
APPEND key value
APPEND作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相
当于SET key value。返回值是追加后字符串的总长度。例如:
redis>SET key hello
OK
redis>APPEND key " world!"
(integer)12
此时key的值是"hello world!"。APPEND命令的第二个参数加了双引号,原因是该参数包
含空格,在redis-cli中输入需要双引号以示区分。
 
6.获取字符串长度
STRLEN key
STRLEN命令返回键值的长度,如果键不存在则返回0。例如:
redis>STRLEN key
(integer)12
 
 
7.同时获得/设置多个键值
MGET key [key …]
MSET key value [key value …]
MGET/MSET与GET/SET相似,不过MGET/MSET可以同时获得/设置多个键的键值。例
如:
redis>MSET key1 v1 key2 v2 key3 v3
OK
redis>GET key2
"v2"
redis>MGET key1 key3
1)"v1"
2)"v3"
 
8. 位操作
GETBIT key offset
SETBIT key offset value
BITCOUNT key [start] [end]
BITOP operation destkey key [key …]
一个字节由8个二进制位组成,Redis提供了4个命令可以直接对二进制位进行操作。为了演示,我们首先将foo键赋值为bar:
redis>SET foo bar
OK
bar的3个字母对应的ASCII码分别为98、97和114,转换成二进制后分别为1100010、
1100001和1110010,所以foo键中的二进制位结构如下图所示:

GETBIT命令可以获得一个字符串类型键指定位置的二进制位的值(0或1),索引从0开始:
redis>GETBIT foo 0
(integer)0
redis>GETBIT foo 6
(integer)1
 
SETBIT 命令可以设置字符串类型键指定位置的二进制位的值,返回值是该位置的旧值。如我们要将foo键值设置为aar,可以通过位操作将foo键的二进制位的索引第6位设为0,第7位设为1:
redis>SETBIT foo 6 0
(integer)1
redis>SETBIT foo 7 1
(integer)0
redis>GET foo
"aar"
 
如果要设置的位置超过了键值的二进制位的长度,SETBIT命令会自动将中间的二进制位
设置为0,同理设置一个不存在的键的指定二进制位的值会自动将其前面的位赋值为0:
redis>SETBIT nofoo 10 1
(integer)0
redis>GETBIT nofoo 5
(integer)0
BITCOUNT命令可以获得字符串类型键中值是1的二进制位个数,例如:
redis>BITCOUNT foo
(integer)10
可以通过参数来限制统计的字节范围,如我们只希望统计前两个字节(即"aa"):
redis>BITCOUNT foo 0 1
(integer)6
 
BITOP命令可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键
中。BITOP命令支持的运算操作有AND、OR、XOR 和NOT。如我们可以对bar和aar进行OR运算:
redis>SET foo1 bar
OK
redis>SET foo2 aar
OK
redis>BITOP OR res foo1 foo2
(integer)3
redis>GET res
"car"
 
 
●散列类型
散列类型(hash)的键值是一种字典结构,其存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型,换句话说,散列类型不能嵌套其他的数据类型。一个散列类型键可以包含至多232-1个字段。
 
1.基本操作
HSET key field value
HGET key field
HMSET key field value [field value …]
HMGET key field [field …]
HGETALL key
HSET命令用来给字段赋值,而HGET命令用来获得字段的值。用法如下:
redis>HSET car price 500
(integer)1
redis>HSET car name BMW
(integer)1
redis>HGET car name
"BMW"
 
当需要同时设置多个字段的值时,可以使用HMSET命令。例如,下面两条语句
HSETkey field1 value1
HSETkey field2 value2
可以用HMSET命令改写成
HMSETkey field1 value1 field2 value2
相应地,HMGET命令可以同时获得多个字段的值:
redis>HMGET car price name
1)"500"
2)"BMW
 
如果想获取键中所有字段和字段值却不知道键中有哪些字段时(如3.3.1节介绍的存储汽
车对象的例子,每个对象拥有的属性都未必相同)应该使用HGETALL命令。如:
redis>HGETALL car
1)"price"
2)"500"
3)"name"
4)"BMW
 
2. 判断字段是否存在
HEXISTSkey field
redis>HSET car price
(integer)1
 
3.增加数字
HINCRBYkey field increment
上一节的命令拾遗部分介绍了字符串类型的命令INCRBY,HINCRBY命令与之类似,可
以使字段值增加指定的整数。散列类型没有HINCR命令,但是可以通过HINCRBY keyfield 1来实现。
HINCRBY命令的示例如下:
redis>HINCRBY person score 60
(integer)60
 
 
●列表类型
列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或
者获得列表的某一个片段。列表类型内部是使用双向链表实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的), 借助列表类型,Redis还可以作为队列使用
 
LPUSH key value [value …]
RPUSH key value [value …]
LPUSH命令用来向列表左边增加元素,返回值表示增加元素后列表的长度。
redis>LPUSH numbers 1
(integer)
 
redis>LPUSH numbers  2  3
(integer)3
 
从列表两端弹出元素
LPOP key
RPOP key
有进有出,LPOP命令可以从列表左边弹出一个元素。LPOP命令执行两步操作:第一步是
将列表左边的元素从列表中移除,第二步是返回被移除的元素值。例如,从numbers列表左边弹出一个元素(也就是"3"):
redis>LPOP numbers
"3"
 
结合上面提到的4个命令可以使用列表类型来模拟栈和队列的操作:如果想把列表当做栈,则搭配使用LPUSH和LPOP或RPUSH和RPOP,如果想当成队列,则搭配使用LPUSH和RPOP或RPUSH和LPOP。
 
获取列表中元素的个数
LLEN key
当键不存在时LLEN会返回0:
redis>LLEN numbers
(integer)3
LLEN命令的功能类似SQL语句SELECTCOUNT(*) FROM table_name,但是LLEN的时间复杂度为0(1),使用时Redis会直接读取现成的值,而不需要像部分关系数据库(如使用InnoDB存储引擎的MySQL 表)那样需要遍历一遍数据表来统计条目数量。
 
获得列表片段
LRANGE key start stop
LRANGE命令是列表类型最常用的命令之一,它能够获得列表中的某一片段。LRANGE命令将返回索引从start到stop之间的所有元素(包含两端的元素)。与大多数人的直觉相同,Redis的列表起始索引为0:
redis>LRANGE numbers 0 2
1)"2"
2)"1"
3)"0"
LRANGE命令在取得列表片段的同时不会像LPOP一样删除该片段,另外LRANGE命令与很多语言中用来截取数组片段的方法slice有一点区别是LRANGE返回的值包含最右边的元素。
 
 
LRANGE命令也支持负索引,表示从右边开始计算序数,如"-1"表示最右边第一个元素,"-2"表示最右边第二个元素,依次类推:
redis>LRANGE numbers -2 -1
1)"1"
2)"0"
显然,LRANGE numbers 0 -1可以获取列表中的所有元素。另外一些特殊情况如下。
(1)如果start的索引位置比stop的索引位置靠后,则会返回空列表。
(2)如果stop大于实际的索引范围,则会返回到列表最右边的元素:
redis>LRANGE numbers 1 999
1)"1"
2)"0
 
 
删除列表中指定的值
LREMkey count value
LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根
据count值的不同,LREM命令的执行方式会略有差异:
●当count>0时LREM命令会从列表左边开始删除前count个值为value的元素;
●当count<0时LREM 命令会从列表右边开始删除前|count|个值为value的元素;
●当count=0是LREM命令会删除所有值为value的元素。例如:
redis>RPUSH numbers 2
(integer)4
redis>LRANGE numbers 0 -1
1)"2"
2)"1"
3)"0"
4)"2"
#从右边开始删除第一个值为"2"的元素
redis>LREM numbers -1 2
(integer)1
redis>LRANGE numbers 0 -1
1)"2"
2)"1"
3)"0"
 
 
 
●集合类型
集合的概念高中的数学课就学习过。在集合中的每个元素都是不同的,且没有顺序。集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是0(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算。
 
1.增加/删除元素
SADD key member [member …]
SREM key member [member …]
SADD命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。本
命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。例如:
redis>SADD letters a
(integer)1
redis> SADD letters a b c
(integer)2
 
第二条SADD命令的返回值为2是因为元素“a”已经存在,所以实际上只加入了两个元素。
SREM命令用来从集合中删除一个或多个元素,并返回删除成功的个数,例如
redis>SREM letters c d
(integer)1
由于元素“d”在集合中不存在,所以只删除了一个元素,返回值为1。
 
2.获得集合中的所有元素
SMEMBERS key
SMEMBERS命令会返回集合中的所有元素,例如:
redis>SMEMBERS letters
1)"b"
2)"a"
 
 
 
3.判断元素是否在集合中
SISMEMBER key member
判断一个元素是否在集合中是一个时间复杂度为0(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时SISMEMBER命令返回1,当值不存在或键不存在时返回0,例如:
redis>SISMEMBER letters a
(integer)1
redis>SISMEMBER letters d
(integer)0
 
 
4.集合间运算
SDIFF key [key …]
SINTER key [key …]
SUNION key [key …]
 
接下来要介绍的3个命令都是用来进行多个集合间运算的。
(1)SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A-B,代表所
有属于A且不属于B的元素构成的集合(如图3-13所示),即A-B={x|x∈A且x∈/B}。

{1, 2, 3}-{2, 3, 4}={1}
{2, 3, 4}-{1, 2, 3}={4}
SDIFF命令的使用方法如下:
redis>SADD setA 1 2 3
(integer)3
redis>SADD setB 2 3 4
(integer)3
redis>SDIFF setA setB
1)"1"
redis>SDIFF setB setA
1 )"4"
 
SDIFF 命令支持同时传入多个键,例如:
redis>SADD setC 2 3
(integer) 2
redis>SDIFF setA setB setC
1 ) "1"
计算顺序是先计算setA-setB,再计算结果与setC的差集。
 
 
SINTER命令用来对多个集合执行交集运算。集合A与集合B的交集表示为A∩B,代表所有属于A且属于B的元素构成的集合(如图3-14所示),即A∩B={x|x∈A且x∈B}。
{1,2, 3}∩{2, 3, 4}={2, 3}
SINTER命令的使用方法如下:
redis>SINTER setA setB
1)"2"
2)"3"
SINTER命令同样支持同时传入多个键,如:
redis>SINTER setA setB setC
1)"2"
2)"3"
 
 
SUNION命令用来对多个集合执行并集运算。集合A与集合B的并集表示为AUB,代表所有属于A或属于B的元素构成的集合(如图3-15所示),即AUB={x|x∈A 或x∈B}。
{1,2, 3}∪{2, 3, 4}={1, 2, 3,4}
SUNION命令的使用方法如下:
redis>SUNION setA setB
1)"1"
2)"2"
3)"3"
4)"4"
SUNION命令同样支持同时传入多个键,例如:
redis>SUNION setA setB setC
1)"1"
2)"2"
3)"3"
4)"4"
 
 
 
 
●有序集合类型
在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。有序集合类型在某些方面和列表类型有些相似。
(1)二者都是有序的。
(2)二者都可以获得某一范围的元素。
 
但是二者有着很大的区别,这使得它们的应用场景也是不同的。
(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问
中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。
(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分
的数据速度也很快(时间复杂度是O(log(N)))。
(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分
数)。
(4)有序集合要比列表类型更耗费内存。
有序集合类型算得上是 Redis的5种数据类型中最高级的类型了,在学习时可以与列表类
型和集合类型对照理解。
 
1.增加元素
ZADD key score member [score member …]
         ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会
用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。
         假设我们用有序集合模拟计分板,现在要记录Tom、Peter和David三名运动员的分数(分别是89分、67分和100分):
redis>ZADD scoreboard 89 Tom 67 Peter 100 David
(integer)3
 
这时我们发现Peter的分数录入有误,实际的分数应该是76分,可以用ZADD命令修改Peter
的分数:
redis>ZADD scoreboard 76 Peter
(integer)0
 
分数不仅可以是整数,还支持双精度浮点数:
redis>ZADD testboard 17E+307 a
(integer)1
redis>ZADD testboard 1.5 b
(integer)1
redis>ZADD testboard +inf c
(integer)1
redis>ZADD testboard -inf d
(integer)1
其中+inf和-inf分别表示正无穷和负无穷。
 
2.获得元素的分数
ZSCORE key member
示例如下:
redis>ZSCORE scoreboard Tom
"89"
 
 
3.获得排名在某个范围的元素列表
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。ZRANGE命令与LRANGE命令十分相似,如索引都是从0开始,负数代表从后向前查找(-1表示最后一个元素)。就像这样:
redis>ZRANGE scoreboard 0 2
1)"Peter"
2)"Tom"
3)"David"
redis>ZRANGE scoreboard 1 -1
1)"Tom"
2)"David"
 
如果需要同时获得元素的分数的话可以在ZRANGE命令的尾部加上WITHSCORES参数,
这时返回的数据格式就从“元素1, 元素2, …, 元素n”变为了“元素1, 分数1, 元素2, 分数2, …, 元素n, 分数n”,例如:
redis>ZRANGE scoreboard 0 -1 WITHSCORES
1)"Peter"
2)"76"
3)"Tom"
4)"89"
5)"David"
6)"100"
 
ZRANGE命令的时间复杂度为0(logn+m)(其中n为有序集合的基数,m为返回的元素个数)。如果两个元素的分数相同,Redis会按照字典顺序(即"0"<"9"<"A"<"Z"<"a"<"z"这样的顺序)来进行排列。
 
4.获得指定分数范围的元素
ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count]
ZRANGEBYSCORE命令参数虽然多,但是都很好理解。该命令按照元素分数从小到大的
顺序返回分数在min和max之间(包含min和max)的元素:
redis>ZRANGEBYSCORE scoreboard 80 100
1)"Tom"
2)"David"
 
 
5.增加某个元素的分数
Z INCRBY key increment member
ZINCRBY命令可以增加一个元素的分数,返回值是更改后的分数。例如,想给Jerry加4分:
redis>ZINCRBY scoreboard 4 Jerry
"60"
increment也可以是个负数表示减分,例如,给Jerry减4分:
redis>ZINCRBY scoreboard -4 Jerry
"56"
 
 
三、事务
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis的最小执行单
位,一个事务中的命令要么都执行,要么都不执行。
事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次执行这些命令。
例如:
redis>MULTI
OK
redis>SADD "user:1:following" 2
QUEUED
redis>SADD "user:2:followers" 1
QUEUED
redis>EXEC
1)(integer) 1
2)(integer) 1
 
Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客
户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。
 
除此之外,Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。如果不希望发生这种情况,也可以使用事务。
 
 
四、排序
SORT命令可以对列表类型、集合类型、有序集合类型进行排序:
redis>LPUSH mylist 4 2 6 1 3 7
(integer)6
redis>SORT mylist
1)"1"
2)"2"
3)"3"
4)"4"
5)"6"
6)"7"
 
在对有序集合类型排序时会忽略元素的分数,只针对元素自身的值进行排序。例如:
redis>ZADD myzset 50 2 40 3 20 1 60 5
(integer)4
redis>SORT myzset
1)"1"
2)"2"
3)"3"
4)"5"
 
除了可以排列数字外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素,就像这样:
redis>LPUSH mylistalpha a c e d B C A
(integer)7
redis>SORT mylistalpha
(error)ERR One or more scores can't be converted into double
redis>SORT mylistalpha ALPHA
1)"A"
2)"B"
3)"C"
4)"a"
5)"c"
6)"d"
7)"e"
 
SORT命令的 DESC参数可以实现将元素按照从大到小的顺序排列:
redis>SORT tag:ruby:posts DESC
1)"26"
2)"12"
3)"6"
4)"2"
 
可以结合limit使用
redis>SORT tag:ruby:posts DESC limit 0 1
1)"26"
2)"12"
3)"6"
4)"2"
 
 
五、消息通知
         说到队列很自然就能想到Redis的列表类型,3.4.2节介绍了使用LPUSH和RPOP命令实现队列的概念。如果要实现任务队列,只需要让生产者将任务使用LPUSH命令加入到某个键中,另一边让消费者不断地使用RPOP命令从该键中取出任务即可。
         可以借助BRPOP命令实现一旦有新任务加入任务队列就通知消费者。BRPOP命令和RPOP命令相似,唯一的区别是当列表中没有元素时BRPOP命令会一直阻塞住连接,直到有新元素加入。
 
 
六、支持脚本
Redis在2.6版推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。在Lua脚本中可以调用大部分的Redis命令,也就是说可以代码改写成Lua脚本后发送给Redis执行。使用脚本的好处如下。
(1)减少网络开销:原本代码需要向Redis发送N次请求,而使用脚本功能完成同样的操作只需要发送一个请求即可,减少了网络往返时延。
(2)原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话
说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有
功能都可以用脚本来实现。
(3)复用:客户端发送的脚本会永久存储在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。
 
测试:
新建test.lua,写入redis.call('set', 'foo', 'bar')并保存。
执行
$ redis-cli--eval  ./test.lua
$ redis-cliget foo
"bar"
 
 
七、redis管理
1.持久化
         Redis的强劲性能很大程度上是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中以某种形式同步到硬盘中,这一过程就是持久化。
         Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用。
 
a. RDB方式
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内
存中的所有数据进行快照并存储在硬盘上。进行快照的条件可以由用户在配置文件中自定
义,由两个参数构成:时间和改动的键的个数。当在指定的时间内被更改的键的个数大于指定的数值时就会进行快照。RDB是Redis默认采用的持久化方式,在配置文件中已经预置了3个条件:
save 900 1
save 300 10
save 60 10000
 
save参数指定了快照条件,可以存在多个条件,条件之间是“或”的关系。如上所说,save
900 1的意思是在15分钟(900秒钟)内有至少一个键被更改则进行快照。如果想要禁用自动快照,只需要将所有的save参数删除即可。
Redis默认会将快照文件存储在当前目录的dump.rdb文件中,可以通过配置dir和
dbfilename两个参数分别指定快照文件的存储路径和文件名。
 
Redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。这使得我们可以通过定时备份RDB文件来实现Redis数据库备份。RDB文件是经过压缩(可以配置rdbcompression参数以禁用压缩节省CPU占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。除了自动快照,还可以手动发送SAVE或BGSAVE命令让Redis执行快照,两个命令的区别在于,前者是由主进程进行快照操作,会阻塞住其他请求,后者会通过fork子进程进行快照操作。
 
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。 通常将一个记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
 
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有
数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发
生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考
虑使用AOF方式进行持久化。
 
 
b. AOF方式
默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
appendonly yes
 
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬
盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改:
appendfilename appendonly.aof
 
AOF文件是纯文本文件,其内容正是Redis客户端向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文件很小的情况下即使其中有很多冗余的命令我们也并不太关心。
 
在启动时Redis会逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,载入的速度相较RDB会慢一些。
 
需要注意的是虽然每次执行更改数据库内容的操作时,AOF都会将命令记录在AOF文件
中,但是事实上,由于操作系统的缓存机制,数据并没有真正地写入硬盘,而是进入了系统的硬盘缓存。在默认情况下系统每30秒会执行一次同步操作,以便将硬盘缓存中的内容真正地写入硬盘,在这30秒的过程中如果系统异常退出则会导致硬盘缓存中的数据丢失。一般来讲启用AOF持久化的应用都无法容忍这样的损失,这就需要Redis在写入AOF文件后主动要求系统将缓存内容同步到硬盘中。在Redis中我们可以通过appendfsync参数设置同步的时机:
# appendfsync always
appendfsync everysec
# appendfsync no
 
默认情况下Redis采用everysec 规则,即每秒执行一次同步操作。always表示每次执行写入
都会执行同步,这是最安全也是最慢的方式。no表示不主动进行同步操作,而是完全交由操作系统来做(即每30秒一次),这是最快但最不安全的方式。一般情况下使用默认值everysec就足够了,既兼顾了性能又保证了安全。
 
Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。此时重新启动Redis后Redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。
 
2.复制
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据。但是由于数据是存储在一台服务器上的,如果这台服务器的硬盘出现故障,也会导致数据丢失。为了避免单点故障,我们希望将数据库复制多个副本以部署在不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。这就要求当一台服务器上的数据库更新后,可以自动将更新的数据同步到其他服务器上,Redis提供了复制(replication)功能可以自动实现同步的过程。
 
同步后的数据库分为两类,一类是主数据库(master),一类是从数据库(slave)。主数据库
可以进行读写操作,当发生写操作时自动将数据同步给从数据库。而从数据库一般是只读的,
并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只
能拥有一个主数据库。
 
在Redis中使用复制功能非常容易,只需要在从数据库的配置文件中加入“slaveof主数据
库IP主数据库端口”即可,主数据库无需进行任何配置。
 
为了能够更直观地展示复制的流程,下面将进行简单的演示。我们要在一台服务器上启
动两个Redis实例,监听不同端口,其中一个作为主数据库,另一个作为从数据库。首先我们不加任何参数来启动一个Redis实例作为主数据库:
redis-server
该实例默认监听6379端口。然后加上slaveof参数启动另一个Redis实例作为从数据库,并
让其监听6380端口:
redis-server--port 6380 --slaveof 127.0.0.1 6379
此时在主数据库中的任何数据变化都会自动同步到从数据库中。我们打开redis-cli实例A
并连接到主数据库:
redis-cli
再打开redis-cli实例B并连接到从数据库:
redis-cli-p 6380
在实例A中使用SET命令设置一个键的值:
redisA>SET foo bar
OK
此时在实例B中就可以获得该值了:
redisB>GET foo
"bar"
但在默认情况下从数据库是只读的,如果直接修改从数据库的数据会出现错误:
redisB>SET foo hi
(error)READONLY You can't write against a read only slave.
 
可以通过设置从数据库的配置文件中的slave-read-only为no以使从数据库可写,但是对从
数据库的任何更改都不会同步给任何其他数据库,并且一旦主数据库中更新了对应的数据就
会覆盖从数据库中的改动。配置多台从数据库的方法也一样,在所有的从数据库的配置文件中都加上 slaveof参数指向同一个主数据库即可。
除了通过配置文件或命令行参数设置slaveof参数,还可以在运行时使用SLAVEOF命令修改:
redis>SLAVEOF 127.0.0.1 6379
如果该数据库已经是其他主数据库的从数据库了,SLAVEOF命令会停止和原来数据库的同步转而和新数据库同步。还可以使用SLAVEOF NO ONE来使当前数据库停止接收其他数据库的同步转成主数据库。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值