redis基础知识

redis键操作的命令

redis是一个使用 ANSI C 编写的开源、支持网络、基于内存、分布式、可选持久性的key-value键值对存储数据库。其键对应的值可以是多种类型的数据结构,下面是一些对redis键操作的基本命令。

  1. 获得某个键的值
1
KEYS pattern		//?单个字符 *任意字符 []括号间的任意字符带-时表示范围 \?转义
  1. 获得所有的键值
1
KEYS *
  1. 判断一个键是否存在
1
EXISTS key
  1. 删除键
1
DEL key [key ...]
  1. 获得键值的数据类型
1
TYPE key

字符串类型(string)

字符串类型是Redis中最基本的类型,能存储任何形式的字符串,包括二进制。允许存储的数据的最大容量是512MB。字符串类型是其他4种数据类型的基础,其他数据类型从某种角度上只是组织字符串的形式不同。虽说是字符串,但是和C语言中的字符串还是有些区别的。

常用命令

  1. 赋值
1
SET key value
  1. 取值
1
GET key
  1. 递增键值(当存储的整数时,可用此命令递增,返回递增后的值,不存在键时会默认为0,不是整数时会出错)
1
INCR key
  1. 递减键值
1
DECR key
  1. 增加指定整数
1
INCRBY key num
  1. 减少指定整数
1
DECRBY key num
  1. 增加指定浮点数
1
INCRBYFLOAT key num
  1. 向尾部追加值
1
APPEND key value
  1. 获取字符串长度
1
STRLEN key
  1. 同时获得/设置多个键值
1
2
MGET key [key ...]
MSET key value [key value ...]
  1. 设置位的值操作(键值一个字节8位,当超过键值二进制位长度时,会自动将中间的二进制位设置为0,设置一个不存在的键的二进制的位的值时,会自动将前面的位赋值为0。利用位操作可以非常紧促的存储bool值,而且SETBIT和GETBIT的时间复杂度都是O(1))
1
2
3
4
SETBIT key offset value


BITOP operation destkey key [key ...]
  1. 获得位的值操作(当超过二进制位的长度时,默认为0)
1
GETBIT key offset
  1. 获得键值中二进制位的值是1的个数
1
BITCOUNT key [start] [end]
  1. 对多个键值进行位运算(运算结果存储在destkey中)
1
BITOP AND/OR/XOR/NOT destkey key [key ...]
  1. 指定键的第一个位值是0或1的位置(返回的结果的偏移量是从头开始算起的,与begin和end的起始字节无关,若不设置结束字节且键值的所有二进制位都是1,则当查询值为0的二进制偏移量时,返回的是键值长度的下一个字位的偏移量,因为redis会认为键值长度之后的二进制位都是0。)
1
2
BITPOS key 0/1 			 // 指定键值第一个二进制为0/1的偏移量
BITPOS key 0/1 begin end // begin和end分别是查询查询指定键值begin字节到end字节之间出现的第一个值为0/1的二进制位的偏移量

注意事项

Redis的底层是由C语言实现的,但是redis中的字符串和C语言的字符串还是有很多区别。

C语言的字符串后面多了个'\0',做为字符串终止符,长度为N的字符串,会用长度为N+1的字符数组来表示。有如下缺点:

  1. C语言字符串并没有记录自身长度。
  2. 需要根据空字符’\0’判断字符串是否结束。

引起的问题如下:

  1. 获取字符串长度复杂度高。
  2. 拼接字符串容易出现缓冲区溢出,要通过内存重分配来扩展底层字符数组空间大小内存重分配是个十分耗时的操作。
  3. 不能存储二进制数据,如果数据本身就包含空字符串’\0’,而代码会认为是字符串终止;像图片、音频、视频、压缩文件这样的二进制数据是会包含空字符串’\0’的,所以数据会出现问题。

Redis中的存储字符串并不是C语言那么简单,而是使用了SDS结构,并且可以存储包含了’\0’的二进制,简单的结构定义如下:

1
2
3
4
5
6
struct SDS
{
	int free = 3;
	int len = 5;
	char* buf = new char[n];
};
  1. 引入了两个变量free和len属性来夺取字符串长度,时间复杂度为O(1)。

  2. 采用了空间预分配和惰性空间释放。当对SDS进行空间扩展时,会分配额外未使用空间。如果SDS修改之后,SDS的长度(len属性的值)小于1MB,那么就会分配和len属性同样大的未使用空间,也就是free属性会和len属性相同,相当于将原数组长度二倍。如果SDS修改之后,SDS的长度(len属性的值)大于或等于1MB,那么就会分配固定的1MB未使用空间。当缩短操作时,想要删掉N个字符后,free就增加N,不做内存重分配操作,空间先给留着后面能继续用。这两种操作都是为了减少内存重分配操作。

  3. C字符串不能存储二进制数据的原因是只能根据 ‘\0’ 来判断数据是否结束,不能保证其完整性,但因为 SDS 的 len 属性,无论你数据里有多少 ‘\0’ 都没关系,我是根据 len 属性值来判断数据长度的,必定是完整的,所以 SDS 可以安全地存储二进制数据。

  4. SDS在字符串后面加了’\0’,保持和C字符串一样的特性,在一定的情况下可以直接用C语言的函数,避免了不必要的函数重写。

散列类型(hash)

redis本来就是采用字典结构以键值对的形式存储数据的,而散列类型的键对应的值也是一种字典结构,相当于字典结构下的字典结构了,其存储了字段 field 和字段值 value 的映射。字段值只能是字符串,不能是其他类型,可以最大包含 2的32次方-1个字段。一般用来表示一个对象中的各种属性值。比如:汽车:001->(颜色->白色;名字->奥迪;价格->100万)。对比关系数据库,redis更加灵活,在关系数据库中,需要建二位表,所有的记录都有相同的属性,无法单独为某条记录增减属性。redis散列类型不存在这个问题,每个键对应的字段可以自由增减而不影响其他键。

常用命令

  1. 赋值(不区分插入和更新操作,当执行插入操作返回1,当执行更新操作返回0,当redis键不存在会自动创建,不能使用操作字符串键的命令操作散列键,会提示错误)
1
2
HSET key field value	//给字段赋值
HMSET key field value [field value ...]	//同时设置多个字段值
  1. 取值
1
2
HGET key field value	//获取字段的值
HMGET key field [field ...]	//同时获取多个字段的值
  1. 取所有字段和字段值
1
HGETALL key
  1. 判断字段是否存在(存在返回1,不存在返回0)
1
HEXISTS key field
  1. 仅当字段不存在时赋值(如果字段已存在,不执行任何操作)
1
HSETNX key field value
  1. 字段值增加指定数字(散列没有HINCR自增命令,也没有HDECRBY命令)
1
HINCRBY key field increment
  1. 删除字段(返回被删除字段个数)
1
HDEL key field [field ...]
  1. 只获取字段名或字段值
1
2
HKEYS key
HVALS key
  1. 获得字段数量
1
HLEN key

列表类型(list)

redis键对应的值的类型可以时列表list类型,列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。

列表内部使用双向链表实现,向列表两端添加元素的时间复杂度为O(1),获取越进两端的元素越块。但是通过索引访问元素比较慢。一般用于特定场景下,可以快速的完成关系数据库难以完成的场景,可以作为队列或栈使用,一个列表类型的键值最多能容纳2的32次方-1个元素。

常用命令

  1. 向列表两端增加元素
1
2
LPUSH key value [value ...]	//向列表左端增加元素,返回增加元素后列表的长度
RPUSH key value [value ...] //向列表右端增加元素,返回增加元素后列表的长度
  1. 从列表两端弹出元素
1
2
LPOP key	//从列表左端弹出元素,第一步将列表左端元素从列表移除,第二步返回被移除的元素值
RPOP key	//从列表右端弹出元素
  1. 获取列表中元素的个数
1
LLEN key
  1. 获得列表片段(返回索引从start到end之间的所有元素,索引从0开始,包含end的元素,当为负数时,表示用右边开始,-1表示右边第一个参数,显然 0 -1 可以获取列表中所有元素。当start索引位置比stop的索引位置靠后,则会返回空列表。当stop大于实际索引范围,则会返回到列表最右边元素)
1
LRANGE key start stop
  1. 删除列表中指定的值(删除列表中前count个值为value的元素,返回实际删除元素的个数,count>0从左边开始删除,count<0从右边开始删除,count=0删除所有值为value的元素)
1
LREM key count value
  1. 获得指定索引元素值(可当数组使用,index是负数表示从右边开始计算,最右边是-1)
1
LINDEX key index
  1. 设置指定索引元素值
1
LSET key index value
  1. 只保留列表指定片段(删除指定索引范围之外的所有元素,使用和LRANGE类似,LTRIM和LPUSH命令一起使用可用来限制类表中元素的数量)
1
ITRIM key start end
  1. 向列表中插入元素(在列表中从左到右查找值为pivot的元素,根据第二个参数是BEFORE还是AFTER决定将value插入到该元素前面还是后面)
1
LINSERT key BEFORE|AFTER pivot value
  1. 将元素从一个列表转到另一个列表(顾名思义先从src执行RPOP一个元素,LPUSH这个元素到dest中,整个过程是原子的,当src和dest相同时,会将队尾元素移动到队首)
1
RPOPLPUSH src dest

集合类型(set)

集合中,每个元素都是不同的,而且没有顺序,一个集合类型的键值可以存储2的32次方-1个字符串,集合类型常用操作是向集合中加入或删除元素、判断某个元素是否存在等。集合使用值为空的hashtable实现,这些操作的时间复杂度都是O(1),最方便的是多个集合之间还可以进行并集、交集、差集运算。

常用命令

  1. 增加集合元素(向set中增加一个元素,如果元素不存在则创建,如果元素存在就忽略,返回成功加入元素的数量)
1
SADD key member [member ...]
  1. 删除集合元素
1
SREM key member [member ...]
  1. 获得集合中所有元素
1
SMEMBERS key
  1. 判断元素是否在集合中(时间复杂度O(1),存在返回1,不存在返回0)
1
SISMEMBER key member
  1. 集合运算(支持同时传入多个键)
1
2
3
4
5
6
7
8
SDIFF key [key ...] //执行差集运算 A-B表示所有属于A且不属于B的元素构成的集合 {1,2,3} - {2,3,4} = {1}
SDIFFSTORE dest key [key ...] //执行差集运算,但将结果存储到dest中

SINTER key [key ...] //执行交集运算 A∩B表示属于A且属于B的元素构成的集合 {1,2,3} ∩ {2,3,4} = {2,3}
SINTERSTORE dest [key ...] //执行交集运算,但将结果存储到dest中

SUNION key [key ...] //执行并集运算 A∪表示所有属于A或属于B构成元素的集合 {1,2,3} ∪ {2,3,4} = {1,2,3,4}
SUNIONSTORE dest [key ...] //执行并集运算,但将结果存储到dest中
  1. 获得集合中元素的个数
1
SCARD key
  1. 随机获得集合中的元素(可以传递参数count获得多个元素,count为正数时,会最多获得count个不重复的元素,当count值大于集合中的元素个数,则返回元素中全部元素。count为负数时,会从集合中获得|count|个元素,元素可能相同。首先会从桶中随机选择一个非空桶,再从桶中所有元素随机选择一个元素,当桶中元素数量越少时,被选中的可能性越大)
1
SRANDMEMBER key [count]
  1. 从集合中弹出一个元素(由于集合是无序的,所以SPOP会从集合中选择一个元素弹出)
1
SPOP key

有序集合类型(sorted set)

在集合的基础上,有序集合为集合中的每个元素都关联了一个分数,使得我们不仅可以完成插入,删除,和判断元素是否存在等操作,还能够获得分数最高(最低)的前N个元素、获得指定范围内的元素等与分数有关的操作,虽然集合中每个元素都是不同的,但是分数可以相同。有序集合类型是使用hash表和跳跃表实现的,所以即使读取中间部分的数据也很快。有序集合中可以更改分数调整元素的位置。有序集合是五种数据结构类型中最高级的类型。

常用命令

  1. 增加元素(score对应元素的分数,如果元素已存在,会使用新的分数替换原有分数,分数不仅可以是整数,还可以是双精度浮点数,+inf/-inf表示正无穷和负无穷)
1
ZADD key score member [score member]
  1. 获得元素分数
1
ZSCORE key member
  1. 获得排名在某个范围的元素列表(返回索引从start到stop之间的所有元素,包含两端元素,索引从0开始,负数-1代表最后一个元素,如果同时获得元素的分数的话,可以加上WITHSCORES,ZRANGE事件复杂度是O(log(n)+m),n为有序集合基数,m为返回的元素的个数。如果两个元素分数相同,redis会按照ASCII字典顺序进行排列,如果中文,会使用UTF-8编码顺序排列)
1
2
ZRANGE key start stop [WITHSCORES]	//按照元素分数,从小到大顺序
ZREVRANGE key start stop [WITHSCORES]	//按照元素分数,从大到小顺序
  1. 获得指定分数范围的元素(返回分数在min到max之间的元素,如果希望分数范围不包括端点值,在分数前加上"(“符号,min和max还支持无穷大+inf/-inf表示正无穷和负无穷。获得元素列表基础上向后偏移offset个元素,并且只获取前count个元素。)
1
2
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
  1. 增加某个元素的分数(返回是更改后的分数)
1
ZINCRBY key increment menber
  1. 获得集合中元素数量
1
ZCARD key
  1. 获得指定分数范围内的元素个数(min和max的特性与ZRANGEBYSCORE一样)
1
ZCOUNT key min max
  1. 删除一个或多个元素(成功删除的元素个数)
1
ZREM key member [member ...]
  1. 按照排名范围删除元素(元素分数从小到大)
1
ZREMRANGEBYRANK key start stop
  1. 按照分数范围删除元素(min和max的特性与ZRANGEBYSCORE一样,返回删除元素的数量)
1
ZREMRANGEBYSCORE key min max
  1. 获得元素的排名
1
2
ZRANK key member		//元素分数从小到大的顺序
ZREVRANK key member		//元素分数从大到小的顺序
  1. 计算有序集合的交集/并集(计算多个有序集合的交集并将结果存在dest键中,返回dest键中的元素个数。dest中的元素个数是由AGGREGATE参数决定的,当为SUM时,dest键中元素的分数是每个集合中该元素分数的和,当为MIN时,dest键中元素分数是每个集合中该元素的最小值,当为MAX时,dest键中元素分数是每个集合中该元素的最大值。还能通过WEIGHTS参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该权重。)
1
2
ZINTERSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] //交集
ZUNIONSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] //并集

Redis事务

事务是一组命令的集合,也是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。

事务的首先由 MULTI 命令开始,告诉Redis后面的命令是一个事务,然后使用 EXEC 命令告诉Redis将等待执行的事务队列中的所有命令按照发送顺序依次执行,返回值就是这些命令的返回值组成的列表,顺序和命令顺序相同。除此之外,Redis保证了一个事务内的命令依次执行而不被其他命令插入。

如果在命令 EXEC 之前客户端断线了,会清空事务队列,事务中的所有命令都不执行。但一旦发送了 EXEC 命令,所有的命令都会被执行,即使短线也没关系。事务也是在 EXEC 之后执行的所有命令的。

事务的错误处理:

  1. 语法错误。如果事务的多个命令中某个命令语法出错,只要有一个命令有语法错误,执行 EXEC 命令后 Redis 就会直接返回错误,连语法正确的命令也不会执行。
  2. 运行错误。语法都是正确的,但是运行时才出现的错误,比如使用散列类型的命令操作集合类型的键,因为语法检查阶段无法发现,会被 Redis 接受并执行。即使一条命令运行时出现了错误,事务中的其他命令依然会继续执行。开发者必须在开发层面上规划好键名规范使用。

Redis 事务没有关系数据库中的回滚(RollBack)功能。正因为如此 Redis 在事务上可以保持简洁和快速。

Watch命令

watch命令可以监控一个或多个键,一旦有一个键被修改,之后的事务就不会执行。watch 不能保证其他客户端不修改这一值。执行 EXEC 命令后,会取消对所有键的监控。如果没执行 EXEC 但想取消对键的监控,可以使用 UNWATCH 命令来取消监控,保证其他事务的执行不受影响。

比如:

1
2
3
4
5
6
SET key 1
WATCH key
SET key 2
MULTI
set key 3
EXEC

上面在WATCH前,执行修改了key值,所以最后事务中的 set key 3 没有执行,EXEC 后返回空结果。

过期时间

使用 EXPIRE 命令设置一个键的过期时间,到时间后会自动删除它。返回1时,表示设置成功。如果再次使用 EXPIRE 命令会重新设置键的过期时间。对其键值进行操作的命令均不会影响键的过期时间(INCT、LPUSH、HSET、ZREM)。

1
EXPIRE key seconds

其中 seconds 表示键的过期时间,单位是秒,表示在多少秒后自动删除该键。

获得一个键的剩余被删除时间:

1
TTL key

当 key 不存在时,TTL 命令会返回 -2。当没有为键设置过期时间时,TTL 返回值为 -1.

取消键的到期时间:

1
PERSIST foo

除此之外,使用 SET 或 GETSET 命令为键赋值时也会同时清除键的到期时间。

如果想要更精准的过期时间,可以使用 PEXPIRE 命令,以毫秒为单位。

注意:如果使用 WATCH 命令监测了一个拥有过期时间的键,该键时间到期自动删除,并不会被 WATCH 命令认为该键被改变。

EXPIREAT 和 PEXPIREAT 使用 UNIX 时间作为第二个参数表示键的过期时刻。

1
2
EXPIREAT key unixtimestampsec
PEXPIREAT key unixtimestampmsec

缓存策略

可以让 Redis按照一定的规则淘汰不需要的缓存键,具体方法是修改配置文件的 maxmemory 参数,限制 Redis 最大可用内存大小,单位是字节,当超过这个限制时,Redis会依据 maxmemory-policy 参数指定的策略来删除不需要的键直到 Redis 占用的内存小于指定内存。

maxmemory-policy 支持的规则如下,其中 LRU 即最近最少使用的键在未来一段时间内不会被用到,当需要空间时,这些键是可以被删除的。

规则 说明
volatile-lru 使用LRU算法删除一个键(只对设置了过期时间的键)
allkeys-lru 使用LRU算法删除一个键
volatile-random 随机删除一个键(只对设置了过期时间的键)
allkeys-random 随机删除一个键
volatile-ttl 删除过期时间最近的一个键
noeviction 不删除键,只返回错误

当设置为 allkeys-lru 时,一旦 Redis 占用的内存超过了限制值,就会不断的删除内存中最近最少使用的键,知道内存小于限制值。

排序

Redis自带的有序集合常见的使用场景是大数据排序,如游戏的玩家排行榜等。很少会获得有序集合中的全部数据。同样 Redis 任务开发者在做完交集、并集后不需要直接获得全部结果,而是将结果存到新的键中以便后续处理,这解释为什么有序集合只有 ZINTERSTORE 和 ZUNIONSTORE 命令,而没有 ZINTER 和 ZUNION 命令。

Redis 提供了 SORT 命令对键的值进行排序。可以使用的类型包括:列表类型、集合类型、有序集合类型的键。可以完成与关系数据库中的连接查询相似的任务。

1
SORT key

普通排序是使用双精度浮点数进行排序的,如果要按字段顺序排列非数字元素,需要加 ALPLA 参数:

1
SORT key ALPHA

默认从小到大排序,更改为从大到小排序:

1
SORT key DESC

LIMIT 是指定范围结果参数:

1
SORT key LIMIT m n

BY参数

很多情况下,我们想根据键值对应的某个属性进行排序,比如散列类型中的time属性,可以使用 BY 关键字。

1
BY 参考键

参考键可以是字符串类型键或散列类型的的某个字段(键名->字段名)。如果提供了 BY 参数,SORT 命令将不再依据元素本身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个*并获得其值,然后依据该值对元素进行排序。

比如:

1
SORT tag:ruby:posts BY post:*->time

将会读取 post:2、post:6、post:12、post:26 几个散列键中的 time 字段的值并依次决定 tag:ruby:posts 键中各个文章ID的顺序。

处理散列类型以外,参考键还可以是字符串类型,比如:

1
2
3
4
5
6
7
8
LPUSH sortbylist 2 1 3
SET itemscore:1 50
SET itemscore:2 100
SET itemscore:3 -10
SORT sortbylist BY itemscore:* DESC
"2"
"1"
"3"

使用 BY 但是参考键名不包含 * 时,SORT 命令将不会执行排序操作,因为 Redis 认为无意义。但在不需要排序但需要借助 SORT 命令获得与元素相关联的数据时,常量键名很有用。

如果几个元素的参考键值相同,则 SORT 命令会比较元素本身的值来决定元素的顺序。

当某个元素的参考键不存在时,会默认参考键的值为 0,会大于一些参考键是负数值。

* 只能在 -> 符号的前面才会认为是参考键,如果在后面会被当成字段名本身而不会作为占位符被元素的值替换。

GET参数

GET参数不影响排序,作用是使 SORT 命令返回的结果不再是元素自身的值,而是 GET 参数中指定的键值。

GET参数的规则和 BY 参数一样,GET 参数也支持字符串类型和散列类型的键,并使用 * 作为占位符。

例如:要实现排序后直接返回ID对应的文章标题:

1
SORT tag:ruby:posts BY post:*->time DESC GET post:*->title

一个 SORT 命令中可以有多个 GET 参数(但 BY 参数只能有一个)。

1
SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time

如果还需要返回文章ID,可以使用 GET #,用来返回元素本身。

1
SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET #

STORE参数

默认情况下,SORT 直接返回排序结果,如果希望保存排序结果,可以使用 STORE 参数。

例如:将结果保存在 sort.result 键中:

1
SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # sort.result

保存后的键的类型为列表类型,如果键已存在会覆盖它。加上 SOTRE 参数后 SORT 返回值为结果的个数。

STORE 参数常用来结合 EXPIRE 命令缓存排序结果。

性能优化

SORT 命令比较复杂,使用不好很容易性能瓶颈。时间复杂度是O(n+mlog(m)),n表示排序的列表的元素个数,m表示返回的个数。

当n较大时,性能较低,而且在排序前会建立一个长度为 n 的容器来存储待排序元素,虽然是一个临时的过程,但同时进行较多的大数据量排序将会严重影响性能。

注意以下几点:

  1. 尽可能减少待排序键中元素的数量(使N尽可能小)。

  2. 使用LIMIT参数只获取需要的数据(使M尽可能小)。

  3. 如果排序的数据量较大,可以使用 STORE 参数将结果缓存。

支付宝
微信
0%