Redis 基础知识

一、数据结构

(1)字符串键:支持字符串,数字和二进制数据

1)储存字符串值(STRLEN,GETRANGE,SETRANGE 不支持中文操作,如果想要 Redis 正常显示中文,需要使用 redis-cli --raw 参数来设定)

SET key value:设置键 key 的值为 value,多次调用 SET 会覆盖原先的值;时间复杂度为 O(1)

GET key:获取键 key 的值,时间复杂度为 O(1)

SET key value NX:当键 key 不存在的时候才设置为 value;否则返回 nil。该操作表示一定不会覆盖原先的 key 的值,时间复杂度为 O(1)

SET key value XX:当键 key 存在的时候才设置为 value;否则返回 nil。该操作表示一定会覆盖原先的 key 的值,时间复杂度为 O(1)

SETNX key value:与 SET key value NX 的作用一致,即当 key 不存在的时候才设置为 value,时间复杂度为 O(1)

SETXX key value:与 SET key value XX 的作用一致,即当 key 存在的时候才设置为 value,时间复杂度为 O(1)

MSET key value [key value..]:一次性设置多个 key 和 value 对应的键值对,时间复杂度为 O(N),N 为键值对的数目

MGET key [key..]:一次性获取多个 key 对应的值,时间复杂度为 O(N),N 为键值对的数目

MSETNX key value [key value...]:当多个 key 都不存在的时候,一次性设置多个键值对;有一个 key 存在的话,返回 0,表示失败,时间复杂度为 O(N),N 为键值对的数目

GETSET key new-value:将 key 的值设置为 new-value 并且返回 key 的旧值 old-value,时间复杂度为 O(1)

def GETSET(key,new-value)

old-value=GET(key)

SET(key,new-value)

return old-value

APPEND key value:将 value 追加到 key 对应值的后面,时间复杂度为 O(n),n 为 value 的长度

STRLEN key:返回 key 的值的长度,时间复杂度为 O(1),因为 redis 会保存字符串的长度

索引:字符串的索引从 0 开始,到 N-1 结束,N 为字符串的长度,也可以是负数索引,即从 -1 开始,表示字符串的最后一个字符,到 -N 结束,表示字符串的第一个字符,N 为字符串的长度

SETRANGE key index value:表示从 key 对应的值的 index 位置开始,用 value 覆盖 key 对应的值,注意这里的 index 只能是正数索引,返回最终的字符串的长度,时间复杂度为 O(N),N 为 value 的长度,例如

SET msg "hello"

SETRANGE msg 1 "app"

GET msg  返回 happo

GETRANGE key start end:返回从 key 的值的 start 索引位置开始,到 end 索引位置结束的字符串,start 和 end 表示的是一个闭区间,因此也包含 start 和 end 所在位置的字符,start 和 end 可以是正数或者是负数的索引,例如:

set msg "hello world"

GETRANGE msg 0 6  返回 hello w

GETRANGE msg -1 -5 返回 world

2)储存数字

储存可以转换为 64 位整数的数字或者转化为 IEEE-754 的浮点数

INCRBY key num:为 key 的数字值增加 num,返回修改之后的值

DECRBY key num:为 key 的数字值减少 num,返回修改之后的值

INCR key:相当于 INCRBY key 1,给 key 的值增加 1

DECR key:相当于 DECR key 1,给 key 的值减 1

INCRBYFLOAT key num:为 key 的浮点数值增加 num,浮点数没有 DECR 的操作,可以传递负值来实现相同功能

对于数字来说,也可以使用 STRLEN,APPEND,GETRANGE,SETRANGE 这些命令,Redis 在执行的时候,先将数字转换成对应的字符串,然后进行相应的操作。

3)储存二进制数据

二进制数据的索引从左到右依次递减的,例如一个字节的二进制数据,索引从左到右为 7,6,5...0

SETBIT key index value:将 key 对应的二进制数据的 index 位置的值改成 value,返回原先的旧值,时间复杂度为 O(1)

GETBIT key index:获取 key 对应的二进制数据的 index 位置的值,时间复杂度为 O(1)

BITCOUNT key [start] [end]:默认情况下(即不设定 start 和 end)统计整个二进制数据中,值为 1 的位数;如果设定了 start 和 end 则表示从 start 开始到 end 结束这段区间里面值为 1 的位数

BITOP operation destkey key [key,...]:对一个或多个二进制数据执行位元操作 operation,并且将结果保存到 destkey 中,operation 可以是:AND (与)、OR(或)、XOR(异或)、NOT(非),其中只有 NOT 操作只能携带一个参数,时间复杂度为 O(N),N 为操作的 key 的数量,结果返回的是 destkey 的长度,相当于使用 STRLEN destkey 一样。

使用示例:统计在线人数

每个用户都会有唯一的 id,将 id 与二进制数据的索引位置一一关联,如果用户在线,则使用 SETBIT 将该索引位置的值设置为 1,否则设置为 0,最后使用 BITCOUNT 统计在线人数,还可以使用 BITOP 命令来做进一步的数据统计,例如一段时间都在线的人数(与)

使用实例:热门图片的缓存

将热门图片的数据读如到 Redis 的二进制数据中,并且缓存到内存中。

(2)散列键

存储多个域值对。

一个散列可以由多个域值对组成,散列的域和值都可以是文字、整数、浮点数、二进制数据。

同一个散列里面每个域都是独一无二的,但是域的值没有唯一性要求。

HSET key field value:给散列键为 key ,名为 field 的域设置值 value

HGET key field:获取散列键为 key,名为 field 的域的值

HSETNX key field value:只有当散列键 key 中不存在名为 field 的域设置值 value,如果 field 已经存在,则不进行操作

HEXISTS key field:检查散列键 key 中是否包含名为 field 的域

HDEL key field[ field1....]:删除散列键 key 中名为 field 的域,可以同时删除多个域的值,时间复杂度为 O(N),N 为删除 field 的数量,返回值为被删除的域值对的数量

HLEN key:返回散列键 key 的域值对数目

HMSET key field value[ field1 value1...]:可以同时设置散列键 key 的多个域值对,时间复杂度为 O(N),N 表示设置的域值对的数量。

HMGET key field[ field1...]:可以同时获取散列键 key 的多个域的值,时间复杂度为 O(N),N 为需要获取的域的数量。

HKEYS key:获取散列键 key 中所有的域

HVALS key:获取散列键 key 中所有的值

HGETALL key:获取散列键 key 中所有的域值对

域的值为数字的话:

HINCRBY key field increment:为散列键 key 中名为 field 的域的值加上整数增量 increment

HINCRBYFLOAT key field increment:为散列键 key 中名为 field 的域的值加上浮点数增量 increment

(3)小结:散列键和字符串键的区别

1)使用散列可以将数据集中放置在同一个地方,而不是像字符串键那样任意的放在数据库中,除了方便数据管理,还可以避免数据的误操作

2)使用散列键可以很方便的避免键名冲突,数据库中每个键的作用都是固定的、单一的,储存的信息都是被隔离的。

3)使用散列键可以减少内存的占用,一般情况下,使用散列键和字符串键保存相同数量的键值对,散列键更节约内存,因为在数据库创建的每个键都带有数据库附加的管理信息,所以数据库中键越多,管理数据库的键的 CPU 占用也就越多。当散列中域值对数量很少的时候,redis 会自动使用一种占用内存非常少的数据结构来做散列的底层实现。

4)但是也有散列键无法做到的,如果你需要使用二进制位操作命令例如 BITOP,SETBIT,GETBIT 的话就只能使用字符串键,因为目前 redis 只支持字符串键进行操作;另外如果你想使用过期功能,也需要使用字符串键,因为目前 redis 只支持对键进行过期操作,而不能对散列里面的域做过期操作。

(4)列表List:有序可重复

一个列表可以包含一个或多个项,每个项按照他们被推入到列表的位置来排列。

LPUSH key value[ value1,...]:从列表的左侧推入多个 value 到键 key 中

RPUSH key value[ value1,...]:从列表的右侧推入多个 value 到键 key 中

LPOP key:从列表的最左侧弹出一个 value 出来

RPOP key:从列表的最右侧弹出一个 value

LLEN key:获取列表的长度

LINDEX key index:获取键为 key 的列表中索引为 index 的项,index 可以是正数,从左往右从 0 开始,也可以是负数,从右往左从 -1 开始

LRANGE key start stop:获取键 key 的列表中索引从 start 到 stop 的所有项,start 和 stop 都可以是正数或者是负数

LSET key index value:设定键为 key 的列表中索引为 index 的项的值,如果 index 超过列表的长度,则返回一个错误,当设定表头 0 或者表尾 -1 的值时,时间复杂度为 O(1),其他的为 O(N),N为列表的长度

LINSERT key BEFORE|AFTER pivot value:找到键为 key 的列表中值为 pivot 的项,并且根据命令中的 BEFORE 或者 AFTER 来在找到的项的前面或者后面插入一个值,如果找不到 pivot 则返回 -1,如果没有键 key 则返回0,插入成功则返回当前列表的长度,时间复杂度为O(N),N 为当前列表的长度。

LREM key count value:从键为 key 的列表中根据 count 的值来移除指定 value:

如果 count>0,则从表头向表尾搜索,移除最多 count 个值为 value 的列表项

如果 count<0,则从表尾向表头搜索,移除最多 count 个值为 value 的列表项

如果 count=0,则移除列表中所有值为 value 的列表项

LTRIM key start stop:对键为 key 的列表进行修剪,让列表只保存指定范围从 start 到 stop内的列表项,start 和 stop 都可以是负数,命令执行成功时返回 OK,时间复杂度为 O(N),N 为被移除列表项的数目。

BLPOP key [ key1,...] timeout:阻塞式弹出,从左往右依次寻找键为 key,key1...的列表项,如果发现有非空列表,则从该列表的最左端弹出一个项,否则一直等待,直到超过指定的时间 timeout,如果将 timeout 设置为 0 则表示永远等待

BRPOP key [ key1,...] timout:阻塞式弹出,从左往右依次寻找键为 key,key1... 的列表项,如果发现有非空列表,则从该列表的最右端弹出一个项,否则一直等待,直到超过指定的时间 timeout,如果将 timeout 设置为 0 则表示永远等待

注意:在 BLPOP 和 BRPOP 命令的执行过程中,如果有其他客户端对列表执行了 PUSH 操作,那么 redis 会在第一时间将该列表的最左或者最右侧的项弹出给阻塞客户端,并且 redis 遵从先来先服务的原则,即当有多个客户端因为同一个列表阻塞时,redis 在获取到列表项时会先返回给阻塞时间最长的那个客户端。

举例:一个推送消息的服务 --- 消息队列

消息由一个客户端产生,要推送给所有关注它的其他客户端,此时可以使用如下方式:

客户端产生消息

------> web 服务器记录消息到数据库,并且将消息 PUSH 到一个 messageQueue 列表中

消息服务器:一直等待该列表中的项目,使用类似于 BRPOP messageQueue 0 的命令,一直等待消息的产生,然后推送给所有的关注者客户端的时间线内(客户端的时间线也是使用列表实现的)

(5)集合:无序不可重复

SADD key element [ element1,...]:一次性向集合 key 中添加多个元素,如果某个元素,在集合中已经存在的话,那么该元素将被忽略,返回添加成功的元素的个数,时间复杂度为 O(N),N 为新添加元素的个数

SREM key element [ element1,...]:一次性从集合 key 中移除多个元素,如果某个元素字集合中不存在的话,那么该元素将被忽略,返回移除成功的元素的个数,时间复杂度为 O(N),N 为被移除元素的个数

SISMEMBER key element:检查给定的元素是否存在于集合 key 中,如果元素存在返回1,如果元素不存在或者集合 key 不存在的话,则返回 0,时间复杂度为 O(1)

SCARD key:返回集合所包含的元素的个数,也叫集合的基数,时间复杂度为 O(1)

SMEMBERS key:返回集合中所有元素,当集合中元素的个数很大时,该命令可能会造成服务器阻塞,时间复杂度为 O(N)

注意:集合中的元素都是无序的,因此对统一集合两次执行返回所有元素的命令,返回的元素的顺序将不一致,如果想存储有序的且可重复的值,可以使用列表;如果想存储有序且不重复的值,可以使用有序集合

SPOP key:从集合 key 中随机的弹出一个元素

SRANDMEMBER key [count]:如果没有指定 count,那么就从集合中随机返回一个元素,如果 count 为正数且小于集合的长度,那么从集合中随机的返回 count 个不重复的元素组成的数组,如果 count 大于集合的长度,那么就返回集合中所有的元素;如果 count 为负数,那么从集合中返回一个 count 的绝对值大小的数组,但是数组里面的元素可能会重复出现多次。和 SPOP 不一样,SRANDMEMBER 不会移除集合中的元素。

SDIFF key [ key1,...]:计算多个集合的差集,时间复杂度为 O(N),N 为所有参与差集计算的元素个数

SDIFF destkey key [ key1,...]:计算多个集合的差集,并且将结果保存到 destkey 中,时间复杂度为 O(N),N 为参与差集计算的元素个数

SINTER key [ key1,...]:计算多个集合的交集,时间复杂度为 O(N*M),N 为所有集合中最小集合的基数,M 为集合的个数

SINTER destkey key [ key1,...]:计算多个集合的交集,并且将计算结果保存到 destkey 中,时间复杂度为 O(N*M),N为所有集合中最小集合的基数,M 为集合的个数

SUNION key [ key1,...]:计算多个集合的并集,时间复杂度为 O(N),N 为所有参与并集计算的元素个数

SUNION destkey key [ key1,...]:计算多个集合的并集,并且将结果保存到 destkey 中,时间复杂度为 O(N),N 为参与并集计算的元素的个数

(6)有序集合:有序不可重复

跟集合相似,不同的是,有序集合中每个元素都会对应有一个浮点数的分值,每个元素根据这个分值从小到大排序,有序集合中元素不可重复,但是元素对应的浮点数分值可以重复。

ZADD key score element [ score1 element1...]:添加元素到有序集合中,可以一次性添加多个元素,时间复杂度为 O(M*log(N)),M 为添加元素的个数,N 为有序集合的长度

ZREM key element [ element1,...]:移除有序集合中的元素及其对应的分值,可以一次性移除多个元素,时间复杂度为 O(M*log(N)),M 为 移除元素的个数,N 为有序集合的长度

ZSCORE key element:返回有序集合中指定元素的分值

ZINCRBY key increment element:给指定元素的分值添加一个增量,也可以添加一个负值,来达到 DEC 的功能,时间复杂度为 O(log(N)),N 为有序集合的长度

ZCARD key:返回有序集合中元素的个数,时间复杂度为 O(1)

ZRANK key element:返回有序集合中指定元素的索引(索引按照元素的分值从小到大依次为 0,1,2,...)

ZREVRANK key element:返回有序集合中指定元素的索引(索引按照元素的分值从大到小依次为 0,1,2,也就是有序集合的第一个元素为 Length-1,最后一个元素的索引为 0)

ZRANGE key start stop [WITHSCORES]:返回有序集合中按照分值从小到大排序,索引在 start 和 stop 之间的元素,也可以使用 WITHSCORES 选项来指定返回集合元素和对应的分值,start 和 stop 可以是负数,时间复杂度为 O(log(N)+M),N 为有序集合的长度,M 为返回元素的个数

ZREVRANGE key start stop [WITHSCORES]:返回有序集合中按照分值从大到小排序,索引在 start 和 stop 之间的元素,也可以使用 WITHSCORES 选项来指定返回集合元素和对应的分值,start  和 stop 可以是负数,时间复杂度为 O(log(N)+M),N 为有序集合的长度,M 为返回元素的个数

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]:返回有序集合中分值中按照分值升序排列后在 min  和 max 之间的元素,可以使用 WITHSCORES 选项来指定返回集合元素和对应的分值,也可以使用 LIMIT 配合 offset count 选项来指定查询时跳过多少个元素,并指定返回的集合元素的个数,时间复杂度为 O(log(N)+M)

ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]:返回有序集合中按照分值降序排列后在 min  和 max 之间的元素,可以使用 WITHSCORES 选项来指定返回集合元素和对应的分值,也可以使用 LIMIT 配合 offset count 选项来指定查询时跳过多少个元素并指定返回的集合元素的个数,时间复杂度为 O(log(N)+M)

ZCOUNT key min max:返回有序集合按照分值升序排列后,分值在 min 和 max 之间元素的数量,时间复杂度为 O(log(N))

ZREMRANGEBYRANK key start stop:移除有序集合中按照分值从小到大排序后,索引在 start  和 stop 之间的元素,start  和 stop 可以是负数,时间复杂度为 O(log(N)+M)

ZREMRANGEBYSCORE key min max:移除有序集合中按照分值从小到大排序后,分值在 min  和 max 之间的元素,时间复杂度为 O(log(N)+M)

ZUNIONSTORE destkey numberSets key1 key2...:将多个有序集合进行并集计算,destkey 表示结果集合,numberSets 表示参与并集计算的集合个数,后面是参与并集计算的集合,该操作的时间复杂度为 O(N)+O(M*log(M)),N 表示参与并集运算的集合个数,M 为结果集的长度。

ZINTERSTORE destkey numberSets key1 key2,...:将多个有序集合进行交集计算,destkey 表示结果集合,numberSets 表示参与交集计算的集合个数,后面是参与交集计算的集合,该操作的时间复杂度为 O(N*K)+O(M*log(M)),N 表示参与并集运算的集合个数,K 为参与交集的所有集合中长度最小的集合的长度,M 为结果集的长度。

(7)HyperLogLog:Redis 2.8.9 以后增加的数据结构

HyperLogLog 可以接受多个元素作为输入, 并给出输入元素的基数(即集合中不同元素的数量)的估算值,即使输入元素的数量或者体积非常大,计算基数所需要的空间总是固定的,而且很小。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数,但是 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素的本身,所以它不能返回输入的各个元素。

PFADD key element [ element1,...]:将任意数量的元素添加到指定的 HyperLogLog 里面,这个命令可能会对 HyperLogLog 进行修改,以便反映新的基数估算值,如果 HyperLogLog 的基数估算值在命令执行之后出现了变化,那么命令返回 1,否则返回 0.时间复杂度为 O(N),N 为添加元素的数量

PFCOUNT key [ key1,key2,...]:当只给定一个 HyperLogLog 时,命令会返回给定 HyperLogLog 的基数估算值,如果给定多个 HyperLogLog,命令会先对给定的 HyperLogLog 进行并集计算,得到一个合并后的 HyperLogLog,然后返回这个合并 HyperLogLog 的基数估算值作为结果返回,当命令作用于一个 HyperLogLog 时,时间复杂度为 O(1),并且平均常数时间很低;如果作用于多个 HyperLogLog 时,时间复杂度为 O(N),并且常数时间也比处理单个 HyperLogLog 时大得多。

PFMERGE destkey sourcekey [ sourcekey1,...]:将多个 HyperLogLog 合并成一个 HyperLogLog,时间复杂度为 O(N),N 为需要合并的 HyperLogLog 的数量

二、数据库管理以及附加功能

(1)数据库管理

TYPE key:返回键 key 储存的值类型。时间复杂度为 O(1)

none:键不存在

string:字符串或者是 HyperLogLog(二进制值储存)

hash:散列

list:列表

set:集合

zset:有序集合

DEL key [ key1...]:删除一个或多个键,不存在的会被忽略,返回被成功删除的键的个数,时间复杂度为 O(N),N 为被删除键的数量。

EXISTS key:检查给定的键是否存在于 Redis 数据库中,存在返回 1,否则返回 0。时间复杂度为 O(1)

RENAME key newkey:将键名从 key 更改成 newkey,如果 newkey 已经存在,那么 key 的值将覆盖 newkey 的值,如果键 key 不存在或者 key 与 newkey 同名,返回错误。时间复杂度为 O(1)

RENAMENX key newkey:只有当 newkey 不存在的时候,才会将键名从 key 更改成 newkey,修改成功返回 1,失败返回 0。时间复杂度为 O(1)

(2)排序命令

SORT key [ By pattern ] [ LIMIT offset count ] [ GET pattern  [ GET pattern ]] [ ASC|DESC ] [ ALPHA ] [ STORE destination ]:对数据库中键为 key 的值进行排序,时间复杂度为 O(N+M*log(M)),其中 N 为键 key 的值的数量,M 为返回的值的数量。

SORT key:将输入键包含的值解释为浮点数,然后对这些浮点数进行排序

redis> RPUSH numbers 9 5 1 3 2

(integer) 5

reids>SORT numbers #默认按照值的大小从小到大排序

"1"

"2"

"3"

"5"

"9"

SORT key ASC|DESC:将键 key 的值按照指定的升序或者降序的方式排列

redis>RPUSH list 3 1 5

(integer)3

redis>SORT list ASC

"1"

"3"

"5"

redis>SORT list DESC

"5"

"3"

"1"

SORT key ALPHA:默认情况下,SORT 会将键 key 包含的值解释成浮点数,然后对浮点数进行排序,但是通过 ALPHA参数,我们可以让 SORT 命令基于字典序对文字进行排序

redis>SADD names "peter" "jack" "tom"

(integer)3

redis>SORT names

(error)ERR One or more scores can't be converted into double #不能被解释成浮点型

redis>SORT names ALPHA

"jack"

"peter"

"tom"

一般情况下,有序集合按照分值进行排序,但是可以使用 SORT 以及 ALPHA 参数让有序集合按照元素本身来排序,例如:

redis>ZADD scores 4 "peter" 5 "jack" 6 "tom"

(integer)3

redis>ZRANGE scores 0 -1

"peter"

"jack"

"tom"

redis>SORT scores ALPHA

"jack"

"peter"

"tom"

SORT key BY pattern:默认情况下,SORT 会根据键 key 包含的元素值来作为权重进行排序,但是通过 BY pattern 选项,可以让 SORT 命令通过其他键的值作为权重来进行排序

redis>SADD names "peter" "jack" "tom"

(integer)3

redis>SORT names ALPHA

"jack"

"peter"

"tom"

redis>MSET peter-score 4 jack-score 5 tom-score 3

(integer)3

redis>SORT names BY *-score

# 首先获取到 names 里面的元素 "peter","jack","tom",并且按照 *-score 的模式找到对应的键 peter-score,jack-score,tom-score 所对应的值,然后根据这些值的大小作为排序的权重

"tom"

"peter"

"jack"

SORT key GET pattern:

redis>SADD names "peter" "jack" "tom"

(integer)3

redis>SORT names ALPHA

"jack"

"peter"

"tom"

redis>MSET peter-name "Peter Hanson" jack-name "Jack Sparrow" tom-name "Tom Edward"

OK

redis>SORT names ALPHA GET *-name

#首先根据字典序顺序将集合排序并且将元素取出,分别为 jack peter tom,然后将查询到的值放到 *-name 中(jack-name,peter-name,tom-name),最后从集合中返回这些键对应的值

"Jack Sparrow"

"Peter Hanson"

"Tom Edward"

SORT key GET pattern GET pattern,...:获取多个外部键中的值

redis>SADD names "peter" "jack" "tom"

(integer)3

redis>SORT names ALPHA

"jack"

"peter"

"tom"

redis>MSET peter-name "Peter Hanson" jack-name "Jack Sparrow" tom-name "Tom Edward"

OK

redis>MSET peter-id "001" jack-id "002" tom-id "003"

redis>SORT names ALPHA GET # GET *-name GET *-id

# 获取多个外部集合中对应键的值,其中 GET# 表示获取到进行排序的值本身

"jack"

"Jack Sparrow"

"002"

"peter"

"Peter Hanson"

"001"

"tom"

"Tom Edward"

"003"

SORT key LIMIT offset count:默认情况下,SORT 会返回被排序的所有值,但是可以通过使用 LIMIT offset count 来指定让命令返回结果之前先跳过 offset 个值,最终只返回 count 个值

redis>RPUSH numbers 8 1 3 9 2 0 6 7 4 5

(integer)10

redis>SORT numbers LIMIT 0 3

0

1

2

redis>SORT numbers LIMIT 3 3 #跳过前 3 个值,并且只返回 3 个元素

3

4

5

SORT key STORE destkey:将排序结果储存到 destkey 中

redis>RPUSH numbers 8 1 3 9

(integer)10

redis>SORT numbers STORE SortedNumbers

(integer)4

redis>LRANGE SortedNumbers 0 -1

1

3

8

9

也可以把以上的选项拼在一起,组成一个强大的排序工具,如下:

redis>SORT team-member-ids BY *-KPI GET # GET *-name GET *-KPI

#通过 KPI 值对成员的 ID 进行排序,根据排序结果依次获取成员的 ID,名字和 KPI

redis>SORT names ALPHA DESC GET # GET *-id GET *-name LIMIT 0 5 STORE profiles

#对 names 里面的值按照字典序降序排列后,获取前 5 个排序结果并依次取得值本身,id,name 并存储到 profiles 中

(3)获取键命令

RANDOMKEY:从当前数据库中随机返回一个键,该键不会被删除,如果数据库中不包含任何键,则返回 nil,时间复杂度为 O(1)

KEYS pattern:返回当前数据库中,所有匹配给定模式的键

KEYS *:返回数据库中的所有键

KEYS h?llo:返回数据库中所有 h  和 llo 中间至少有一个字母的键

KEYS h*llo:返回数据库中所有 h  和 llo 中间可以有 0 个或多个字母 的键

KEYS h[ae]llo:返回数据库中键名为 hallo 和 hello 的键,[] 表示可选项

KEYS 命令会一次性遍历整个数据库来获取与模式匹配的键,因为如果数据库中有几千万或者更多的键时,KEYS 命令势必会引起数据库的阻塞,为了解决这个问题,Redis 2.8.0 开始增加了 SCAN 命令,该命令会以渐进的方式,分多次遍历数据库

SCAN cursor [MATCH pattern][COUNT number]:cursor 是遍历过程中的游标值,在开始一次新的遍历时,需要先将 cursor设置为 0,之后每次执行 SCAN 命令,都会返回一个新的游标值,在下次执行 SCAN 命令时将该游标值传入来继续上次的遍历,如果 SCAN命令返回的游标值为 0表示数据库遍历结束

MATCH pattern 表示匹配的模式,类似于 KEYS 命令中的模式

COUNT number 表示遍历返回的键的数量,默认是 10

 

KEYS

SCAN

处理方式

一次性遍历数据库中的所有键

循序渐进,分多次遍历数据库中的键

是否阻塞服务器?

可能会(如果数据库中键很多)

不会

是否出现重复值?

不会

可能会

时间复杂度

O(N)

每次执行的时间复杂度为 O(1)

SSCAN key cursor [MATCH pattern][COUNT number]:代替可能会阻塞服务器的 SMEMBERS 命令,遍历集合中包含的元素

HSCAN key cursor [MATCH pattern][COUNT number]:代替可能会阻塞服务器的 HGETALL 命令,遍历集合中的所有元素

ZSCAN key cursor [MATCH pattern][COUNT number]:代替可能会阻塞服务器的 ZRANGE 命令,遍历集合中的所有元素

(4)数据库命令

DBSIZE:获取数据库中所有键值对的数量,时间复杂度为O(1)

FLUSHDB:清空当前的数据库,时间复杂度为 O(N),N 为当前数据库中包含的键值对数量

SELECT num:选择当前的数据库,Redis 服务器默认提供 16 个数据库,分别从 0 到 15,可以使用 SELECT num 命令来切换数据库

MOVE key targetdb:将数据库中的键移动到另外一个数据库,如果该键在当前数据库中没有或者已经存在于另外一个数据库,那么该命令将不做任何操作,时间复杂度为 O(1)

FLUSHALL:清空 Redis 服务器中所有数据库中的键值对,命令总是返回 OK,时间复杂度为 O(N),N 为被删除的键值对的数量

(5)键过期命令

EXPIRE key seconds:将 key 的生存时间设定为指定的秒数,时间复杂度为 O(1),如果给定的键不存在,那么返回 0,如果返回 1 表示设置成功。当一个键的生存时间到了指定的时间,Redis 就会自动将这个键删除

PEXPIRE key milliseconds:将 key 的生存时间设定为指定的毫秒数,时间复杂度为 O(1)

EXPIREAT key UNIX:将 key 的过期时间设定为指定的秒级 UNIX 时间戳,时间复杂度为 O(1),当键的生存时间小于当前的时间,Redis 就自动将该键删除

PEXPIREAT key UNIX:将 key 的过期时间设定为指定的毫秒级 UNIX 时间戳,时间复杂度为 O(1)

TTL key:返回键的剩余生存时间,单位是 秒,返回 -2 表示该键不存在,返回 -1 表示该键没有设置生存时间,返回 >=0 表示返回的剩余生存时间

PTTL key:返回键的剩余生存时间,单位是 毫秒,时间复杂度为 O(1)

PERSIST key:移除键上已经设定的过期时间设置,使得该键为持久化状态,设置成功返回 1,如果该键没有设置生存时间或者过期时间,那么返回 0,时间复杂度为 O(1)

SETEX key seconds value:这个命令从 Redis 2.0 开始增加,相当于原子的执行了 SET 命令和 EXPIRE 命令

PSETEX key milliseconds value:这个命令从 Redis 2.6.0 开始增加,相当于原子的执行了 SET 命令和 PEXPIRE 命令

SET key value [EX seconds][PX milliseconds]:从 Redis 2.6.12 增加该命令格式,与 SETEX,PSETEX 的功能一样

(6)订阅发布功能

该功能下有四个角色:

1)订阅者:通过订阅频道或者模式来获取消息的客户端

2)频道:构建在服务器内部,负责接收发布者发布的消息,并且将消息发送给所有订阅者

3)模式:构建在服务器内部,通过对频道的模式匹配来找到频道,当频道接收到消息时,模式也会将消息发送给模式的订阅者

4)发布者发布消息的客户端

订阅命令:

SUBSCRIBE channel [channel1,...]:订阅一个或多个频道,时间复杂度为 O(N),N 为订阅的频道数

redis>SUBSCRIBE news::it

1)"subscribe" #订阅频道时返回的消息

2)"news::it"    #被订阅的频道

3)(Integer)1   #客户端目前订阅频道的数量

1)"message"  #表示这是从频道接收到的消息

2)"news::it"    #消息的频道来源

3)"hello"        #消息的内容

PSUBSCRIBE pattern [ pattern1,...]:订阅一个或多个模式,时间复杂度为 O(N),N 为订阅的模式数量

pattern 参数可以包含 glob 风格的匹配符(类似于正则表达式),比如:

news::* 可以匹配以 news:: 开头任意的频道

news::[ie]t 可以匹配 news::it 和 news::et 的频道

news::?t 可以匹配以 news::开头, t 结尾,中间还有1个或多个字符的频道

redis>PSUBSCRIBE news::it

1)"psubscribe" #订阅频道时返回的消息

2)"news::it"    #被订阅的频道

3)(Integer)1   #客户端目前订阅频道的数量

1)"pmessage"  #表示这是从频道接收到的消息

2)"news::[ie]t"  #被匹配的模式

3)"news::it"    #消息的频道来源

4)"hello"        #消息的内容

UNSUBSCRIBE [channel [ channel1,...] ]:退订一个或多个频道,如果没有带 channel 参数的话,那么将退订所有已订阅的频道

PUNSUBSCRIBE [ pattern [ pattern1,...] ]:退订一个或多个模式,如果没有 pattern 参数,那么将退订所有已订阅的模式

发布命令:

PUBLISH channel message:将消息发送到指定的频道,命令返回值为接收到消息的订阅者数量,时间复杂度为 O(N),N 为接收到消息的订阅者数量,包括通过频道订阅和模式订阅来接收消息的订阅者。

查看被订阅的频道数、查看频道的订阅者数、查看被订阅的模式数

PUBSUB CHANNELS [ pattern]:返回目前至少有一个订阅者的频道数。可以使用参数 pattern 来指定只查询与模式匹配的频道,时间复杂度为 O(N),N 为服务器中被订阅频道的总数量

PUBSUB NUMSUB [channel1;channel2,...]:返回给定频道的订阅者数量,频道之间用分号隔开,时间复杂度为 O(N),N 为给定频道的数量

PUBSUB NUMPAT:返回被订阅的模式数,时间复杂度为 O(1)

(7)流水线功能 PIPELINE

在一般情况下,用户每执行一个 Redis 命令,客户端和服务器就要进行一次通信:客户端向服务器发送请求命令,而服务器端则将命令的执行结果返回给客户端。

在大多数情况下,执行命令时的绝大部分时间都消耗在了发送命令和接收回复上,因此减少客户端与服务器的通信次数可以有效地提高程序的执行效率。

流水线可以将多条命令打包,一次性发送给服务器,服务器也会在一次回复中把所有被执行命令的执行结果返回给客户端,这样就减少了客户端与服务器的通信次数,也减少了客户端消耗的时间,从而提高了程序的执行效率。

(8)事务功能

MULTI:开启一个事务,在这个命令执行之后,客户端发送给服务器的执行命令并不会马上被执行,而是被放入到一个事务队列中,返回 QUEUED 表示该命令已入队列

redis>MULTI

OK

redis>SET msg "hello world"

QUEUED

redis>EXPIRE msg 5

QUEUED

DISCARD:取消事务,放弃执行事务队列中所有命令,时间复杂度为 O(1)

redis>MULTI

OK

redis>SET msg "hello world"

QUEUED

redis>EXPIRE msg 5

QUEUED

redis>DISCARD

OK

EXEC:按照命令被放入到事务队列中的顺序,执行事务队列中的所有命令。命令的时间复杂度为所有命令的时间复杂度之和,命令的返回值是一个列表,列表包含了事务队列中所有命令的执行结果。下面模拟了 SETEX 或者是 SET ...... EX 命令的:

redis>MULTI

OK

redis>SET msg "hello world"

QUEUED

redis>EXPIRE msg 5

QUEUED

redis>EXEC

WATCH key [ key1, key2... ]:乐观锁,监视数据库的键,防止多个事务同时执行时,对共有资源的竞争,配合事务执行

UNWATCH:取消对所有键的监视

DISCARD:除了可以取消当前的事务,还可以取消对所有键的监视

(9)Lua 脚本

前面学习了流水线,事务以及乐观锁的概念,但是这些功能也有局限性,例如流水线功能虽然可以将客户端的指令存储在队列中,一次性传送给服务器,但是如果存储在队列里面的指令是很复杂的,而且这一操作需要执行多次,即客户端要给服务器发送多条复杂指令集,那么这个时候流水线就显得很无力,对网络资源也造成了消耗。而事务和乐观锁能保证多条指令要么一起执行,要么都不执行,但是加锁也是需要谨慎,而且如果有的指令并不需要事务的功能,但是为了保持同步的效果,就不得不因为要使用乐观锁而加入事务控制,造成额外的性能损耗

为了解决以上问题,从 Redis 2.6 开始引入了 Lua 脚本功能,该功能有如下优点:

1.使用 Lua 脚本可以在服务器端执行 Redis 命令,一般的数据处理操作可以使用 Lua 语言或者 Lua 解释器里面的函数库来完成。

2.所有的脚本都是以事务的形式执行的,脚本执行过程不会被打断,也不会引起任何的竞争条件,可以使用脚本代替事务和乐观锁功能。

3.所有的脚本都是可重用的,也就是说,重复执行相同操作时,只要调用存储在服务器里面的脚本缓存,不需要重新发送脚本,从而节省了网络资源,并且实现了流水线的功能

EVAL script numberkeys [ key [ key... ]] [ arg [ arg ]]:执行 Lua 脚本,script 表示 Lua 脚本,numberkeys 表示脚本要处理的数据库键的数量,后面的 [ key [ key...]] 可以在脚本中以数组的形式获取到,例如 KEYS[1],KEYS[2]。[arg [ arg...]] 表示脚本要用到的参数,在脚本中可以通过 ARGV[1],ARGV[2] 的方式获取到

redis>EVAL "return 'hello world'" 0

"hello world"

redis>EVAL "return 1+1" 0

(integer)2

redis>EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 "msg" "age" "123" "hello world"

1)"msg"

2)"age"

3)"123"

4)"hello world"

使用 redis.call 或者 redis.pcall 可以在 Lua 脚本中使用 Redis 命令

redis>EVAL "return redis.call('ping')" 0

redis>EVAL "return redis.call('DBSIZE')" 0

redis>SET msg "hello"

redis>EVAL "return redis.call('GET',KEYS[1])..' world'" 1 msg

hello world

redis.call 和 redis.pcall 都可以用来执行 Redis 命令,它们的区别在于当执行 Redis 命令出错时,redis.call 命令会返回出现错误的脚本的名字以及 EVAL 命令的错误信息,而 redis.pcall 只会返回 EVAL 命令的错误信息

EVALSHA sha1 numberkeys [ key [ key...]] [ arg [ arg...]]:任何 Lua 脚本通过 EVAL 命令执行的,都会被存储到服务器的 脚本缓存中,用户只要通过 EVALSHA 命令,并指定需要执行的脚本的 SHA1 值,就可以在不用传送脚本的情况下,再次执行该脚本

SCRIPT EXISTS sha1 [ sha1,..]:检查 sha1 值所代表的脚本是否被加入到脚本缓存中,是的话返回 1,否则返回 0

SCRIPT LOAD script:将脚本加入到服务器的脚本缓存中,方便之后的 EVALSHA 命令使用

SCRIPT FLUSH:清空脚本缓存中的所有脚本

SCRIPT KILL:杀死运行超时的脚本,如果脚本已经执行过写入操作,那么还要使用 SHUTDOWN NOSAVE 命令强制服务器不保存数据

Lua 环境下的一些函数库:

标准库:

base 库:包含 Lua 的核心函数,比如 assert,tostring,error,type

string 库:包含用于处理字符串的函数,比如 find,format,len,reverse

table 库:包含用于处理表格的库,比如 concat,insert,remove,sort

math 库:包含常用的数学计算函数,比如 abs,sqrt,log

debug 库:包含用于调试程序所需的函数,比如 sethook,gethook

外部库:

struct 库:在 C 语言的结构和 Lua 语言的值之间进行转换

cjson 库:将 Lua 值转换成 json 对象,或者将 json 对象转换成 Lua 值

cmsgpack 库:将 Lua 值编码成 MessagePack 格式,或者从 MessagePack 格式解码成 Lua 值

还有一个用于计算 sha1 值的函数 redis.sha1hex

三、服务器配置与管理

(1)服务器配置

配置服务器的方式有三种,分为启动时和运行时的配置:

1)启动时的配置又分为两种,一种是以启动参数的方式配置服务器的启动项,例如在之前选择 Redis 的数据库时,使用 SELECT num 命令来选择,num 的范围默认是 0-15,因为 Redis 默认分配了 16 个数据库,此时就可以使用 $redis-server --<option> value --<option> value... 的方式,如下所示:

$redis-server --databases 32

会将 Redis 默认分配的数据库数量由 16 改成 32,再例如:

$redis-server --port 10086

则会将 Redis 的服务端口设定为 10086。

2)另外一种方式是将参数配置在一个文件中,通过服务器启动时指定配置文件的路径来进行配置 $redis-server <config file path>,例如有如下配置文件 redis.conf:

databases 32

port 10086

那么就可以通过如下方式去载入配置文件来达到配置服务器的效果:

$redis-server redis.conf

3)另外一种是运行时的配置,例如:

redis>CONFIG GET <option>

会获取当前的配置项的值,例如:

redis>CONFIG GET lua-time-limit

"lua-time-limit"

"5000

可以使用如下命令:

redis>CONFIG SET <option> <value>

的方式来设定运行时的配置,例如:

redis>CONFIG SET lua-time-limit 3000

会将 Lua 脚本执行的超时时间从 5000 毫秒更改成 3000 毫秒。

当然并不是所有的配置都可以在运行时配置,例如默认分配的数据库数量,服务器的端口等就需要在服务器启动时设置好

以上的配置方式中,因为运行时的配置仅仅在此次运行过程中有效,在下次重启服务器时,运行时的配置又将恢复成默认或者是配置文件中的值,所以如果需要使得运行时的设置在之后的启动过程中也生效的话,可以使用 CONFIG REWRITE 命令将运行时的配置写入到配置文件中,这样在下次服务器启动并载入配置文件时,就可以使得之前的运行时配置生效。

常用的配置选项如下:

选项

作用

默认值

可否在线修改?

port <num>

服务器的监听端口号

6379

timeout <seconds>

在客户端空闲多长时间后,

服务器会与之断开连接

0(不会主动断开)

loglevel <level>

服务器记录日志的级别

notice

databases <num>

数据库的数量

16

requirepass <pswd>

客户端连接服务器的密码

(空密码)

maxmemory <bytes>

可用的最大内存

(不限制)

lua-time-limit <ms>

Lua 脚本可运行的最大时间

5000

(2)RDB 持久化

因为 Redis 服务器将数据存储在内存里面,而一旦服务器被关闭、或者运行服务器的主机本身被关闭的话,存储在内存里面的数据就会消失不见。

如果仅仅将 Redis 用作缓存的话,那么这种数据丢失带来的问题并不大,但如果将 Redis 用作数据库的话,这种数据丢失就无法接受了。

为了在 Redis 服务器关闭时,将保留在数据库中的数据保留下来,Redis 提供了 RDB 和 AOF 两种持久化功能,这两种功能可以将存储在内存里面的数据库数据以文件的形式保存到硬盘里面,这样的话,即使服务器关闭,也不会丢失已经保存到硬盘里面的数据。

服务器在重新启动时,通过载入持久化文件来还原服务器在关闭之前的数据库数据,或者使用持久化文件来进行数据备份和数据迁移

RDB 持久化功能可以将服务器包含的所有数据库数据以二进制文件存储到硬盘里

通过在服务器启动时载入 RDB 文件,服务器可以根据 RDB 文件的内容,还原服务器原有的数据库数据。

在 Redis 服务器创建 RDB 文件的情况中,以下三种是最常见的:

1.服务器执行客户端发送的 SAVE 命令;

2.服务器执行客户端发送的 BGSAVE 命令;

3.使用 save 配置选项设置的自动保存条件,服务器自动执行 BGSAVE

1)SAVE 命令

通过使用客户端向服务器发送 SAVE 命令,可以命令服务器去创建一个新的 RDB 文件

在执行 SAVE 命令的过程中(也就是创建 RDB 文件的过程中),Redis 服务器将阻塞,无法处理客户端发送的命令请求,只有在 SAVE 命令执行完毕之后(也就是 RDB 文件创建完毕之后),服务器才会重新开始处理客户端发送的命令请求

如果 RDB 文件已经存在,那么服务器将自动使用新的 RDB 文件去替换旧的 RDB 文件。

SAVE 命令的时间复杂度为 O(N),N 为服务器中所有数据库包含的键值对数量总和

2)BGSAVE(Background Save)

执行 BGSAVE 命令同样可以创建一个新的 RDB 文件,这个命令和 SAVE 命令的区别在于,BGSAVE 不会造成 Redis 服务器阻塞:在执行 BGSAVE 命令的过程中,Redis 服务器仍然可以正常的处理其他客户端发送的命令请求。

BGSAVE 命令不会造成服务器阻塞的原因在于:

1.当 Redis 服务器接收到 BGSAVE 命令的时候,它不会自己来创建 RDB 文件,而是通过 fork() 来生成一个子进程,然后由子进程负责创建 RDB 文件,而自己则继续处理客户端的命令请求;

 

2.当子进程创建好 RDB 文件并退出时,它会向父进程(也就是负责处理命令请求的 Redis 服务器)发送一个信号,告知它 RDB 文件已经创建完毕;

3.最后 Redis 服务器(父进程)接收子进程创建的 RDB 文件,BGSAVE 命令执行完毕

BGSAVE 是一个异步命令,发送命令的客户端会立即得到回复,而实际的操作在回复之后才开始,BGSAVE 命令的复杂度和 SAVE 一样,都是 O(N),N 为数据库包含的所有键值对数量之和。

3)save <seconds> <changes>:自动创建 RDB 文件

为了让 Redis 服务器可以自动进行 RDB 持久化操作,Redis 提供了 save 配置选项,通过这个选项可以设置多个保存条件,每当保存条件中的任意一个被满足时,服务器就会自动执行 BGSAVE 命令,该选项的效果是:如果距离上一次创建 RDB 文件已经过去了 seconds 秒,并且服务器的所有数据库总共发生了不少于 changes 次修改(包括添加、删除和更新),那么执行 BGSAVE。

每次创建 RDB 文件之后,服务器就会将设置的时间计数器和次数计数器清零,并重新开始计数,所以多个保存条件的效果是不会叠加的

4)RDB 文件概览

(3)AOF 持久化

1)RDB持久化的缺点:

1.创建 RDB 文件需要将服务器所有数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以创建 RDB 文件的操作不能过于频繁

2.如果在等待创建下一个 RDB 文件的过程中,服务器遭遇了意外停机,那么用户将丢失最后一个创建 RDB 文件之后,数据库发生的所有修改。

2)解决方法:

Redis 提供了 AOF 功能,也就是用户可以根据自己的需要对 AOF 持久化进行调整,让 Redis 在遭遇意外停机时不丢失任何数据或者只丢失一秒钟的数据

3)AOF 持久化运行原理:数据保存和数据还原

AOF 持久化保存数据库数据的方法是:每当有修改数据库的命令被执行时,服务器就会将被执行的命令写入到 AOF 文件的末尾。

因为 AOF 文件里面存储了服务器执行过的所有数据库修改命令,所以给定一个 AOF 文件,服务器要重新执行一遍 AOF 文件里面包含的所有命令,就可以达到还原数据库数据的目的。

4)安全性问题:通过配置选项来调整 AOF 持久化的安全性

虽然服务器每执行一个修改数据库的命令,就会将被执行的命令写入到 AOF 文件,但这并不意味着 AOF 持久化不会丢失任何数据。

目前常见的操作系统中,执行系统调用 write 函数,将一些内容写入到某个文件中,为了提高效率,系统通常不会直接将内容写入到硬盘里,而是先将内容写入到一个内存缓冲区里面,等到缓冲区被填满或者用户执行 fsync 调用和 fdatasync 调用时,才将储存在缓冲区里面的内容写入到硬盘里面。

对于 AOF 持久化来说,当一条命令真正被写入到硬盘里面时,这条命令才不会因为停机而意外丢失数据。

因此,AOF 持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间。

为了控制 Redis 服务器在遭遇意外停机时丢失的数据量,Redis 为 AOF 持久化提供了 appendfsync 选项,这个选项的值可以是 always、everysec 或者 no,默认值是 everysec,这些值的意思分别为:

1.always:服务器每写入一个命令,就调用一次 fdatasync,将缓冲区里面的命令写入到硬盘里面,零丢失,速度最慢。

2.everysec:服务器每秒钟调用一次 fdatasync,将缓冲区里面的命令写入到硬盘里面,最多丢失一秒钟内执行的命令数据,速度较快。

3.no:服务器不主动调用 fdatasync,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失的命令数量不固定,速度快。

5) AOF 重写:创建一个没有冗余内容的新 AOF 文件(命令的合并)

为了让 AOF 文件的大小控制在合理范围,Redis 提供了 AOF 重写功能,通过这个功能,服务器可以产生一个新的 AOF 文件:

1.新的 AOF 文件记录的数据库数据和原有的 AOF 文件记录的一样;

2.新的 AOF 文件使用尽可能少的命令来记录数据库数据, 因此新的 AOF 文件的体积通常会比原有 AOF 文件的体积小得多;

3.AOF 重写期间,服务器不会阻塞,可以正常处理客户端发送的命令请求。

6)两种方法可以触发 AOF 重写

1.客户端向服务器发送 BGREWRITEAOF 命令

2.设置配置选项来自动执行 BGREWRITEAOF 命令:

auto-aof-rewrite-min-size <size>:触发 AOF 重写的最小文件体积

auto-aof-rewrite-min-size 64mb 表示当 AOF 文件体积超过  64mb 时重写          AOF 文件

auto-aof-rewrite-percentage <percent>:当 AOF 文件的体积超过 auto-aof-rewrite-min-size 选项设定的体积,并且超过指定的 percent 之后,触发 AOF 重写。如果服务器刚启动,还没有进行 AOF 重写,那么使用服务器启动时载入的 AOF 文件的体积作为基准值。如果将这个值设定为 0 表示关闭自动 AOF 重写。例如:

auto-aof-rewrite-percent 100 表示当 AOF 文件的体积是 auto-aof-rewrite-min-size 选项设定的体积的 2 倍时进行 AOF 重写。

(4)服务器管理

1)PING 命令

用于测试网络连接状态或者服务器状态,或者用于测量延迟值,客户端向服务器发送 PING 命令,如果客户端和服务器连接正常,并且服务器运作正常,那么命令将返回一个 PONG

2)INFO [section] 命令

查看服务器的各项信息和统计数值

通过给定的参数 section 来指定命令要返回的信息内容。

3)MONITOR 命令

查看服务器正在执行的命令。

格式为 时间戳 [数据库号码 IP地址:端口号] 被执行的命令,例如:

1378822099.421623 [0 127.0.0.1:56604] "PING"

4)慢查询功能:发现执行缓慢的命令

Redis 的慢查询功能用于将执行时间超过指定时长的命令记录起来,并向用户展示那些被记录的命令,方便用户发现运行缓慢的命令,并进行针对性的优化。

慢查询功能可以通过以下两个配置选项来设置:

slowlog-log-slower-than <microseconds>

将执行时长超过 microseconds 微妙的命令记录下来,如果将 microseconds 设置成负数表示关闭慢查询功能。默认值是 10000,即0.01 秒

slowlog-log-max-len <length>

慢查询日志的最大数量,当记录的日志超过这个数量时,新日志就会覆盖旧日志(FIFO),默认值是 128。

SLOWLOG GET [number]

返回服务器目前记录的慢查询日志,如果给定了可选 number 参数,那么只返回最多 number 条日志,否则返回所有慢查询日志。

SLOWLOG LEN

查看目前已有的慢查询日志数量。

SLOWLOG RESET

删除所有慢查询日志。

5)requirepass 选项和 AUTH 命令:服务器加锁与解锁

通过配置选项 requirepass <password> 用户可以为服务器设置密码。

当客户端连接一个带密码的服务器时,它必须执行 AUTH <password> 命令来进行解锁,否则这个客户端就不能执行除 AUTH 以外的其他命令。

6)shutdown 命令:关闭服务器

SHUTDOWN [option]

在不给定 option 参数的情况下,服务器会先执行持久化操作:

如果打开了 AOF 持久化,那么调用 fdatasync,确保之前执行的命令都能够被写入到硬盘

如果打开了 RDB 持久胡并且数据库已经发生了变化,那么执行 SAVE 命令。

在以上操作都完成之后,服务器关闭。

option 选项的值可以使 save 或者 nosave:

SHUTDOWN save 在关闭之前总是执行 SAVE 命令,用于在没有开启 RDB 持久化的情况下,创建一个 RDB 文件来保存数据;

SHUTDOWN nosave 在关闭之前不会执行 SAVE 命令,用于在数据库可能已经出错的情况下,避免将错误的数据保存到 RDB 文件里面。

7)Redis 管理工具:RedisLive、Redis-Commander 和 RedMon

四、Redis 多机功能介绍

(1)复制和 Sentinel:扩展系统处理读请求的能力,并提高系统的可用性

1)复制:创建具有相同数据库的拷贝服务器

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器称为主服务器(master),而通过复制创建出来的服务器称为从服务器(slave)。主从服务器两者拥有相同的数据库数据:只要主从服务器之间的网络连接正常,主服务器就会一直将发生在自己身上的数据更新同步给从服务器,从而一直保证主从服务器的数据相同

因为主从服务器拥有相同的数据库数据,所以从服务器在执行客户端发送的读命令时,获得的结果与主服务器执行相同的读命令获得的结果是一样的。因此,用户可以将原本由主服务器处理的一部分甚至全部的读命令请求转交给从服务器处理,从而降低主服务器在处理读命令请求方面的负载,扩展整个系统处理读命令请求的能力。

通过添加从服务器可以线性的扩展整个系统处理读命令请求的能力。

2)从服务器的创建和使用:使用 SLAVEOF 命令或者 slaveof 选项

Redis 提供了两种方法为某个主服务器创建从服务器:

1.给一个服务器发送 SLAVEOF <master-ip> <master-port> 命令使其成为从服务器,也可以通过发送 SLAVEOF no one 将其由从服务器变成主服务器(数据库已有的数据会被保留)

2.在启动服务器时设置 slaveof <master-ip> <master-port> 配置选项来使服务器称为从服务器。

3)处理服务器下线

在一个由主服务器和从服务器组成的系统中,主服务器或者从服务器都有可能会下线,但是不同服务器下线带来的影响并不相同:

1.如果下线的是从服务器,那么整个系统处理读命令请求的性能将有所下降,但整个系统仍然可以继续处理写请求和读请求,所以这种下线不会导致系统停机。

2.因为在整个系统中只有主服务器一个能处理写请求,所以如果下线的是主服务器,那么整个系统只能处理读请求而无法处理写请求,从而导致系统停机。

让系统重新上线的方法:向系统中的一个从服务器发送 SLAVEOF no one 命令让它变成新的主服务器,并且向其他从服务器发送 SLAVEOF 命令,让它们复制新的主服务器。

4)Sentinel:监视主从服务器,并且在主服务器下线时自动进行故障转移

虽然上面介绍的方法可以让系统重新上线,但手动执行这些操作实在太麻烦,为此,Redis 提供了 Sentienl 程序,用户可以使用 Sentienl 来自动检测主从服务器的状态,并在主服务器下线时,自动执行故障转移操作,让系统重新上线

通过执行 Redis 安装文件夹中的 redis-sentienl 程序可以启动一个 sentienl 实例:

$redis-sentienl sentienl.conf

因为 Redis 的 Sentienl 实际上是一个运行在 Sentienl 模式下的 Redis 服务器,所以可以使用如下命令来启动一个 Sentienl 实例:

$redis-server sentienl.conf --sentienl

启动 Sentienl 时需要指定配置文件,该文件记录了要监视的主服务器以及相关的配置参数。

每个 Sentienl 实例可以监视任意多个主服务器,以及被监视的主服务器属下的所有从服务器

多个 Sentienl 实例可以监视同一个主服务器,监视相同的主服务器的这些 Sentienl 实例会自动互连,组成一个分布式的 Sentienl 网络,互相通信并交换彼此关于被监视服务器的信息。

当一个 Sentienl 认为被监视的服务器已经下线,它会向网络中的其他 Sentienl 进行确认,判断该服务器是否真的已经下线。

如果下线的服务器时主服务器,那么 Sentienl 网络将对下线主服务器进行自动故障转移:通过将下线主服务器的某个从服务器提升为新的主服务器,并让其它从服务器转为复制新的主服务器,以此来让系统重新回到上线状态。

5)Sentinel 的配置:指定监视的主服务器以及相关的监视参数

Sentienl 在启动时必须指定相应的配置文件:$redis-sentienl sentienl.conf

一个 Sentienl 配置文件至少包含一个监视配置选项,即用于指定被监视主服务器的相关信息:

sentienl monitor <name> <ip> <port> <quorum>

其中 name 是用户为被监视主服务器设置的名字,ip 和 port 是被监视主服务器的 IP 地址和端口号,quorum 是指确认这个主服务器已下线所需要的最少 Sentienl 数量,例如:

sentienl monitor mymaster 127.0.0.1 6379 2 表示要监视的主服务器的名字为 mymaster,它的 IP 地址为 127.0.0.1,端口号是 6379,至少需要 2 个 Sentienl 同意才能确认这个主服务器已经下线。

Sentienl 可以自动发现并且监视主服务器属下的所有从服务器,所以用户只需要给出主服务器的地址和端口号即可。

如果在同一个机器上运行了多个 Sentienl 实例,用户还需要通过 port <number> 选项来为每个 Sentienl 设置不同的端口号,如果不进行设置,那么 Sentienl 的默认端口号为 26379。例如:

#sentienl1.conf

port 26379

sentinel monitor master 127.0.0.1 6379 2

#sentienl2.conf

port 26380

sentienl monitor master 127.0.0.1 6379 2

(2)twemproxy:Redis 代理服务器

除了读性能的问题之外,还可能遇上写性能的问题,因此有时候用户需要同时扩展系统的读请求和写请求的能力。为了解决这个问题,我们也需要使用多台服务器来处理客户端的请求。

达到这个目的的其中一种方式是使用分片(shard),这项技术的核心原理就是将整个数据库分为多个部分,使用不同的服务器来存储不同的部分,并负责处理相应部分的命令请求(包括读请求和写请求)

twemproxy 兼容 Redis 和 Memcached,允许用户将多个 Redis 服务器添加到一个服务器池里面,并通过用户选择的散列函数和分布函数将来自客户端的命令请求分发给服务器池中的各个服务器。

通过使用 twemproxy,可以将数据库分片到多台 Redis 服务器上,并使用这些服务器来分担系统负载和数据库容量,向池里面添加更多服务器可以线性的扩展系统处理命令请求的能力,以及系统能够保存的数据量

安装、配置和运行 twemproxy

1.安装 twemproxy

下载并解压压缩包:http://code.google.com/p/twemproxy/download/list

$./configure

$make

$sudo make install

2.使用 yml 格式编写配置文件 nutcracker.yml:

my_redis_server_group: #服务器池的名字,支持创建多个服务器池

listen:127.0.0.1:22121 #这个服务器池的监听地址和端口号

hash:fnv1a_64  #键散列算法,用于将键映射为一个散列值

distribution:ketama #键分布算法,决定键被分布到哪个服务器

redis:true #代理 Redis 命令请求,默认代理 Memcached 请求

servers:    #池中各个服务器的地址和端口号以及权重

- 127.0.0.1:6379:1

- 127.0.0.1:6380:1

- 127.0.0.1:6381:1

3.$ nutcracker -c nutcracker.yml

用户可以使用 Redis 客户端连接上 twemproxy,并向 twemproxy 发送命令请求,接收到命令请求的 twemproxy 会根据用户指定的散列函数和分布函数来决定将命令交给哪个服务器来处理。

当 twemproxy 获取到命令请求的回复时,就会将命令回复返回给发送命令请求的客户端,整个代理过程,对于客户端来说是完全透明的。

键分布:通过散列函数和分布函数来将数据库的键放置到不同的服务器里面

当客户端向 twemproxy 发送一个针对键 key 的命令请求时,twemproxy 会根据以下计算公式计算出应该由服务器池中的哪个服务器来处理这个命令请求:

#计算键 key 的散列值

hash_value=hash_function(key)

#根据键 key 的散列值,计算出键 key 应该被分布到哪个服务器里面

#其中 all_servers_in_pool 是一个列表,它包含了池中的所有服务器的 IP 地址和端口号

#比如 ['127.0.0.1:6379','127.0.0.1:6380','127.0.0.1:6381']

server=distribution_function(hash_value,all_servers_in_pool)

#将请求发送给服务器

forward_request(server,request)

twemproxy 提供了多种散列函数给用户,用户可以通过修改配置文件中 hash 选项了来指定散列函数:

1.包括 one at a time 算法在内的 Jenkins 函数

2.包括 crc16、crc32 和 crc32a 在内的循环冗余检验和函数

3.md5 散列算法

4.包括 fnv1_64、fnv1a_64、fnv1_32 和 fnv1a_32 在内的 Fowler Noll Vo 散列函数

5.有 Paul Heish 发明的 heish 散列算法

6.murmur 散列算法

用户可以通过修改 distribution 选项的值来改变 twemproxy 分布键的方式,可选值为:

1.ketama:一致性 hash,主要用于在实现缓存服务时,防止某个服务器下线而造成缓存大量失效

2.modula:取模运算,相当于依靠散列函数本身来分布各个键

3.random:不考虑散列值,直接随机的将键分布到池中的某个服务器,在访问键时,也随机选择一个服务器来访问

散列标签:默认情况下,twemproxy 会根据键的整个键名来计算键的散列值,但如果用户通过 hash_tag 选项指定了散列标签,那么 twemproxy 只会根据键名中被散列标签包围的部分来计算键的散列值。例如:用户设置了 hash_tag 为 {},那么以下两个键将计算出相同的散列值:

bbs::{user}::123456

bbs::{user}::255255

服务器上下线管理:自动移除下线服务器和自动添加重新上线的服务器

服务器下线:默认情况下,当服务器池中的某个服务器下线时,twemproxy 会向所有发送给该服务器的命令请求返回一个连接错误:(error)ERR Connection refused

自动移除下线服务器:twemproxy 提供了 auto_eject_hosts <bool>server_failure_limit <N> 这两个配置选项:当 auto_eject_hosts 选项的值为 true,并且 twemproxy 在连续 N 次向同一个服务器发送命令请求但都遇到错误时,twemproxy 就会将该服务器标记为下线,并将原来由这个服务器负责处理的键交给池中的其他在线的服务器来处理。

自动添加重新上线的服务器:twemproxy 提供了 server_retry_timeout <time> 选项,time 参数的格式为毫秒。

当一个服务器被 twemproxy 判断为下线后,在 time 毫秒之内,twemproxy 不会再尝试向下线的服务器发送命令请求,但是在 time 毫秒之后,服务器会尝试重新向下线服务器发送命令请求:

1.如果命令请求能正常执行,那么 twemproxy 就会撤销对该服务器的下线判断,并再次将键交给那个服务器来处理。

2.但如果服务器还是不能正常处理命令请求,那么 twemproxy 会继续将原本应该交给下线服务器的键转交给其他服务器来处理,并等待下一次重试的来临。

(3)集群:Redis 分布式数据库的实现

Redis 集群是一个由多个 Redis 服务器组成的分布式网络服务器集群,集群中的各个服务器被称为节点,这些节点会相互连接并进行通信。

分布式的 Redis 集群没有中心节点,所以不必担心某个节点称为整个集群的瓶颈。

Redis 集群的每个节点有两种角色可选,一个是主节点,一个是从节点:其中主节点用于储存数据,而从节点则是某个主节点的复制品。

当用户需要处理更多的读请求时,可以添加从节点来扩展系统的读性能,因为 Redis 集群重用了单机 Redis 复制特性的代码,所以集群的复制行为和之前介绍的单机复制特性的行为一样。

1)节点故障检测和自动故障转移

Redis 集群的主节点内置了类似 Redis Sentienl 的节点故障检测和自动故障转移功能,当集群中的某个节点下线时,集群中的其他在线主节点会注意到,并对已下线的主节点进行故障转移。但是在集群里面,故障转移是由集群中的其他在线主节点负责进行,所以集群不必使用 Redis Sentienl。

2)分片

集群使用分片来扩展数据库的容量,并将命令请求的负载交给不同的节点来分担。

集群将整个数据库分为 16384 个槽(slot),所有键都属于这 16384 个槽的其中一个,计算键 key 属于哪个槽的方法为:

slot_number=crc16(key)%16384

其中 crc16 为 16 位的循环冗余校验和函数。

集群中的每个节点都可以处理 0 至16384 个槽,当 16384 个槽都有某个节点在负责处理时,集群进入上线状态,并开始处理客户端发送的数据命令请求。

例如,我们有三个主节点 7000、7001 和 7002,那么可以:

将槽 0-5460 指派给节点 7000 负责处理

将槽 5461-10922 指派给节点 7001 负责处理

将槽 10923-16383 指派给节点 7002 负责处理

3)转向

对于一个被指派了槽的主节点来说,这个主节点只会处理属于指派给自己的槽的命令请求。

如果一个节点接收到了和自己处理的槽无关的命令请求,那么节点会向客户端返回一个转向错误,并告诉客户端,哪个节点才是负责处理这条命令的,之后客户端需要根据错误中包含的地址和端口号重新向正确的节点发送命令请求。

(4)集群搭建:设置和建立一个集群

搭建一个 Redis 集群需要执行以下步骤:

1.创建多个节点

2.为每个节点指派槽,并将多个节点连接起来,组成一个集群

3.当集群数据库的 16384 个槽都有节点负责处理时,集群进入上线状态

接下来是一个包含六个节点的 Redis 集群,其中三个是主节点,三个是从节点,即每个主节点都有一个从节点:

1).创建多个节点

为了让 Redis 服务器以集群模式运行, 我们需要在启动服务器时,打开服务器的集群模式选项:

cluster-enabled yes

如果多个 Redis 服务器运行在同一个机器里面,还需要为每个节点指定不同的端口号

port 7000

我们可以将这两个配置写入到 redis.conf 文件里面,然后执行以下命令来启动一个节点:

$ redis-server redis.conf

创建多个节点则需要提供多个 redis.conf 配置文件,里面都是如下内容:

port <number>

cluster-enabled yes

然后分别执行

$ redis-server redis.conf

就可以创建多个节点了,

2).创建集群

创建出 6 个节点之后,需要让这 6 个节点相互连接以构成一个集群,然后为三个主节点指派槽,并为三个主节点分别设置一个从节点。

创建集群的操作可以通过位于 Redis 安装文件夹的 redis-trib.rb 程序完成,它具有创建集群,检查集群的上线情况和槽指派情况、对集群进行重新分片、向集群添加新节点或从集群中移除节点功能。

以下是创建三个主节点和三个从节点的集群命令:

$ redis-trib.rb create --replicase 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

其中 create 表示创建一个集群,--replicase 1 表示让 redis-trib.rb 为集群中的每个主节点设置一个从节点,之后的是各个节点的 IP 地址和端口号。输入该命令后,redis-trib 会为各个节点指派槽以及角色,并询问用户是否接受这种节点配置。在确认之后,redis-trib.rb 会向各个节点发送指令,将它们连接成一个集群,并指派槽和角色。然后 redis-trib.rb 会对集群进行测试,检查是否每个节点都按照原先展示的配置设置好了。如果整个数据库的 16384 个槽都有节点在处理,那么集群就进入上线状态,之后用户就可以开始向集群发送命令请求了。

(5)集群客户端

目前主要的 Redis 集群客户端有如下这些:

1.redis-rb-cluster:使用 Ruby 编写的 Redis 集群客户端,集群客户端的官方实现。

2.predis:Redis 的 PHP 客户端,支持集群功能

3.jedis:Redis 的 Java 客户端,支持集群功能

4.StackExchange.Redis:Redis 的 C# 客户端,支持集群功能。

5.内置的 redis-cli:在启动时给定 -c 参数即可进入集群模式,支持部分集群功能。

(6)结论

如果需要完整的分片、复制和高可用性,并且要避免使用代理带来的性能瓶颈和资源消耗,那么选择使用 Redis 集群;如果只需要一部分特性(比如只需要分片,但不需要复制和高可用)那么可以单独选择 twemproxy、Redis 复制和 Redis Sentienl 中的一个或多个。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值