Redis中的所有数据结构均不支持数据类型的嵌套。比如,集合类型的每个元素都只能是字符串,而不能是另一个集合或散列表(Hash)。
字符串(String)
SET - 插入数据
SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX] [GET]
- Time complexity: O(1)
Set key
to hold the string value
. If key
already holds a value, it is overwritten, regardless of its type.
如果当前 key 已经存在 value,则用新 value 覆盖原有的value值。
一旦设置成功(无论当前有没有发生覆盖),则返回OK;如果没设置成功,返回 nil (比如 options中包含了 nx
,这时,只有当当前 key 不存在 value时,才会真正去设置value,即无覆盖发生。否则,就会返回 nil)。
Return Value
- Simple string reply: OK if SET was executed correctly.
- Null reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met.
Options
The SET command supports a set of options that modify its behavior:
-
EX
seconds – Set the specified expire time, in seconds. -
PX
milliseconds – Set the specified expire time, in milliseconds. -
NX
– Only set the key if it does not already exist.127.0.0.1:6379> set a cc OK 127.0.0.1:6379> set a cccc OK 127.0.0.1:6379> set a ddd NX (nil)
-
XX
– Only set the key if it already exist. -
KEEPTTL
– Retain the time to live associated with the key. -
GET
– Return the old value stored at key, or nil when key did not exist.
GET - 读数据
- Time complexity: O(1)
Get the value of key
. If the key does not exist the special value nil
is returned. An error is returned if the value stored at key
is not a string, because GET only handles string values.
即,如果当前key没有value值,则返回null
GETSET key value
- Time complexity: O(1)
将键 key
的值设为 value
, 并返回键 key
在被设置之前的旧值。
返回值
返回给定键 key
的旧值。
如果键 key
没有旧值, 也即是说, 键 key
在被设置之前并不存在, 那么命令返回 nil
。
当键 key
存在但不是字符串类型时, 命令返回一个错误。
APPEND - 数据追加
- 如果当前key的value有值则附加到原有string后面,如果没有则写入。返回值为追加后的数据长度
DEL - 数据删除
- Time complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).
Removes the specified keys. A key is ignored if it does not exist.
Return value
Integer reply: The number of keys that were removed.
127.0.0.1:6379> set name wei
OK
127.0.0.1:6379> get name
"wei"
127.0.0.1:6379> append name isgreat
(integer) 10
127.0.0.1:6379> get name
"weiisgreat"
127.0.0.1:6379> del wei
(integer) 0
127.0.0.1:6379> get wei
(nil)
散列类型(Hash)
Redis是采用字典结构以键值对的形式存储数据的,而散列类型(hash)的键值也是一种字典结构,其存储了字典(field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型(换句话说,散列类型不能嵌套其他的数据类型)。
HSET
命令用来给字段赋值,而HGET
用来获得字段的值。
127.0.0.1:6379> HSET car price 500
(integer) 0
127.0.0.1:6379> HSET car name BMW
(integer) 1
127.0.0.1:6379> HGET car name
"BMW"
HSET
命令的方便之处在于不区分插入和更新操作,这意味着修改数据时不用事先判断字段是否存在来决定要执行的是插入操作(update)还是更新操作(insert)。当执行的是插入操作时(即之前字段不存在)HSET
命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET
命令会返回0。更进一步,当键本身不存在时,HSET
命令还会自动建立它。
HGET - 获取某个值
Time complexity: O(1)
Returns the value associated with field
in the hash stored at key
.
127.0.0.1:6379> HGET car name
"BMW"
Return value
Bulk string reply: the value associated with field
, or nil
when field
is not present in the hash or key
does not exist.
Examples
redis> HSET myhash field1 "foo"
(integer) 1
redis> HGET myhash field1
"foo"
redis> HGET myhash field2
(nil)
redis>
HSET - 写入某个值
Time complexity: O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.
127.0.0.1:6379> HSET car price 500
(integer) 1
Sets field
in the hash stored at key
to value
. If key
does not exist, a new key holding a hash is created. If field
already exists in the hash, it is overwritten.
As of Redis 4.0.0, HSET is variadic and allows for multiple field
/value
pairs.
Return value
Integer reply: The number of fields that were added.
Example
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HGET myhash field1
"Hello"
redis>
HMGET key field [field …] - 同时读取多个值
Time complexity: O(N) where N is the number of fields being requested.
Returns the values associated with the specified fields
in the hash stored at key
.
For every field
that does not exist in the hash, a nil
value is returned. Because non-existing keys are treated as empty hashes, running HMGET against a non-existing key
will return a list of nil
values.
Return value
Array reply: list of values associated with the given fields, in the same order as they are requested.
Examples
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HMGET myhash field1 field2 nofield
1) "Hello"
2) "World"
3) (nil)
redis>
[deprecated] HMSET - 同时写入多个值
Time complexity: O(N) where N is the number of fields being set.
Sets the specified fields to their respective values in the hash stored at key
. This command overwrites any specified fields already existing in the hash. If key
does not exist, a new key holding a hash is created.
As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code.
127.0.0.1:6379> HMSET key field value [field value ...]
同时将多个field - value(域-值)对写入到哈希表key中。此命令会覆盖哈希表中已存在的域。如果key不存在(哈希表不存在),一个空哈希表被创建并执行hmset操作。
Return value
Examples
redis> HMSET myhash field1 "Hello" field2 "World"
"OK"
redis> HGET myhash field1
"Hello"
redis> HGET myhash field2
"World"
redis>
HEXISTS key field - 查看指定 field 是否存在
Time complexity: O(1)
Returns if field
is an existing field in the hash stored at key
.
127.0.0.1:6379> HEXISTS key field
查看指定哈希表key中,指定域field是否存在。
Return value
Integer reply, specifically:
1
if the hash containsfield
.0
if the hash does not containfield
, orkey
does not exist.
Examples
redis> HSET myhash field1 "foo"
(integer) 1
redis> HEXISTS myhash field1
(integer) 1
redis> HEXISTS myhash field2
(integer) 0
redis>
HKEYS key - 获取所有 field
127.0.0.1:6379> HKEYS key
获得指定哈希表中key所有的field。
列表(List)
- 列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素, 或者获得列表的某一个片段。
- Redis中的List数据结构是链表结构,这意味这无论数据量多大,头尾操作数据还是很快的,list的容量是2的32次方减1个元素,即4294967295个元素数量
- 更具体的来说,列表类型内部使用**双向链表(double linked list)**实现,因此向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的)。
- 不过使用链表的代价是通过索引访问元素比较慢,设想在iPad mini发售当天有1000 个人在三里屯的苹果店排队等候购买,这时苹果公司宣布为了感谢大家的排队支持,决定奖励排在第486位的顾客一部免费的iPad mini。为了找到这第486位顾客,工作人员不得不从队首一个一个地数到第486个人。但同时,无论队伍多长,新来的人想加入队伍的话直接排到队尾就好了,和队伍里有多少人没有任何关系。这种情景与列表类型的 特性很相似。
LPUSH - 增加元素
LPUSH
命令用来向列表左边增加元素,返回值表示增加元素后的列表长度。
127.0.0.1:6379> LPUSH numbers 1
(integer) 1
# LPUSH命令还支持同时增加多个元素,此时,LPUSH会先向列表左边加入“2”,然后再加入“3”
127.0.0.1:6379> LPUSH numbers 2 3
(integer) 3
若想向列表右边增加元素,可使用RPUSH
命令,其用法与LPUSH
命令一致。
127.0.0.1:6379> RPUSH numbers 0 -1
(integer) 5
LPOP/RPOP - 从列表两端弹出元素
有进有出,LPOP
命令可以从列表左边弹出一个元素。LPOP
命令执行两步操作:
- 第一步是将列表左边的元素从列表中移除
- 第二步是返回被移除的元素值。例如,从numbers 列表左边弹出一个元素(也就是"3"):
127.0.0.1:6379> LPOP numbers
"3"
127.0.0.1:6379> RPOP numbers
"-1"
集合(Set)
集合的概念高中的数学课就学习过。在集合中的每个元素都是不同的,且没有顺序。 一个集合类型(set)键可以存储至多232 -1个(相信这个数字对大家来说已经很熟悉了)字符串。
集合类型的常用操作是向集合中加入或刪除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是0(1)。谅方便的是多个集合类型键之间还可以进行并集、交集和差集运算。
SADD - 增加元素
- Time complexity: O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.
Add the specified members to the set stored at key
. Specified members that are already a member of this set are ignored. If key
does not exist, a new set is created before adding the specified members.
An error is returned when the value stored at key
is not a set.
SADD命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素己经存在于集合中就会忽略这个元素。本命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。例如:
127.0.0.1:6379> SADD letters a
(integer) 1
127.0.0.1:6379> SADD letters a b c
(integer) 2
127.0.0.1:6379>
第二条SADD命令的返回值为2,是因为元素“a”已经存在,所以实际上 SADD letters a b c
命令只加入了两个元素。
Return value
Integer reply: the number of elements that were added to the set, not including all the elements already present into the set.
SREM - 删除元素
SREM命令用来从集合中删除一个或多个元素,并返回删除成功的个数,例如:
127.0.0.1:6379> SREM letters c d
(integer) 1
由于元素“d”在集合中不存在,所以只删除了一个元素,返回值为1。
SMEMBERS - 获得集合中的所有元素
- Time complexity: O(N) where N is the set cardinality.
SMEMBERS命令会返回集合中的所有元素,例如:
127.0.0.1:6379> SMEMBERS letters
1) "b"
2) "a"
In this case, the time complexity of this operation is O(n) (O(2)), since letters
contains two elements.
Returns all the members of the set value stored at key
.
This has the same effect as running SINTER with one argument key
.
Return value
Array reply: all elements of the set.
SISMEMBER - 判断元素是否在集合中
判断一个元素是否在集合中是一个时间复杂度为0(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时SISMEMBER命令返回1,当值不存在或键不存在时返回0,例如:
127.0.0.1:6379> SISMEMBER letters a
(integer) 1
127.0.0.1:6379> SISMEMBER letters b
(integer) 1
127.0.0.1:6379> SISMEMBER letters d
(integer) 0
SSCAN -
有序集合(Sorted Set)
有序集合类型(sorted set)的特点从它的名字中就可以猜到,它与上一节介绍的集合类型的区别就是“有序”二字。
在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。
有序集合类型在某些方面和列表类型有些相似。
- 二者都是有序的。
- 二者都可以获得某一范围的元素。
但是二者有着很大的区别,这使得它们的应用场景也是不同的。
- 列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后, 访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问 中间元素的应用。
- 有序集合类型是使用散列表和跳跃表(Skip Hst)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log_2N) )。
- 列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。
- 有序集合要比列表类型更耗费内存。
ZADD - 增加元素
127.0.0.1:6379> ZADD key score member [score member ...]
ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素己经存在则会新的分数替换原打的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。
假设我们用有序集合模拟计分板,现在要记录Tom、Peter和David三名运动员的分数 (分别是89分、67分和100分):
127.0.0.1:6379> ZADD scoreboard 89 Tom 67 Peter 100 David
(integer) 3
这时我们发现Peter的分数录入有误,实际的分数应该是76分,可以用ZADD命令修改Peter的分数:
127.0.0.1:6379> ZADD scoreboard 76 Peter
(integer) 0
分数不仅可以是整数,还支持双精度浮点数:
127.0.0.1:6379> ZADD tastboard 17E+307 a
(integer) 1
127.0.0.1:6379> ZADD testboard 1.5 b
(integer) 1
ZSCORE - 获得元素的分数
127.0.0.1:6379> ZSCORE key member
127.0.0.1:6379> ZSCORE scoreboard Tom
"89"
ZRANGE - 获得排名在某个范围的元素列表
127.0.0.1:6379> ZRANGE key start stop [WITHSCORES]
127.0.0.1:6379> ZREVRANGE key start stop [WITHSCORES]
ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。ZRANGE命令与LRANGE命令十分相似,如索引都足从0开始, 负数代表从后向前査找(-1表示最后一个元素)。就像这样:
127.0.0.1:6379> ZRANGE scoreboard 0 2
1) "Peter"
2) "Tom"
3) "David"
127.0.0.1:6379> ZRANGE scoreboard 0 1
1) "Peter"
2) "Tom"
127.0.0.1:6379> ZRANGE scoreboard 1 -1
1) "Tom"
2) "David"
ZRANGEBYSCORE - 获得指定分数范围的元素
127.0.0.1:6379> ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGEBYSCORE命令参数虽然多,但是都很好理解。该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)的元素:
redis> ZRANGEBYSCORE scoreboard 80 100
-
“Tom”
-
“David”
如果希望分数范围不包含端点值,可以在分数前加上“(”符号。例如,希望返回”80 分到100分的数据,可以含80分,但不包含100分,则稍微修改一下上面的命令即可:
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 80 (100
1) "Tom"
min和max还支持无穷大,同ZADD命令一样,-inf和+ inf分别表示负无穷和正无穷。比如你希望得到所有分数高于80分(不包含80分)的人的名单,但你却不知道最高分是多少(虽然有些背离现实,但是为了叙述方便,这里假设可以获得的分数是无上限的), 这时就可以用上+inf 了:
127.0.0.1:6379> ZRANGEBYSCORE scoraboard (80 +inf
1) "Torn”
2) "David”
WITHSCORES参数的用法与ZRANGE命令一样,不再赘述。
incremental iteration 操作
Background
有时候需要从 Redis 实例成千上万的 key 中找出特定前缀的 key 列表来手动处理数据,可能是修改它的值,也可能是删除 key。这里就有一个问题,如何从海量的 key 中找出满足特定前缀的 key 列表来?
Redis 提供了一个简单暴力的指令 keys 用来列出所有满足特定正则字符串规则的 key。
!redis-cli keys key67*
1) "key6764"
2) "key6738"
3) "key6774"
4) "key673"
5) "key6710"
6) "key6759"
7) "key6715"
8) "key6746"
9) "key6796"
这个指令使用非常简单,提供一个简单的正则字符串即可,但是有很明显的两个缺点。
-
没有 offset、limit 参数,一次性吐出所有满足条件的 key,万一实例中有几百 w 个 key 满足条件,
当你看到满屏的字符串刷的没有尽头时,你就知道难受了。
-
keys 算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,
所有读写 Redis 的其它的指令都会被延后甚至会超时报错,
因为 Redis 是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续。
-
建议生产环境屏蔽keys命令
Redis 为了解决这个问题,它在 2.8 版本中加入了指令——scan。
scan 相比 keys 具备有以下特点:
- 复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程;
- 提供 limit 参数,可以控制每次返回结果的最大条数,limit 只是对增量式迭代命令的一种提示(hint),返回的结果可多可少;
- 同 keys 一样,它也提供模式匹配功能;
- 服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;
- 返回的结果可能会有重复,需要客户端去重复,这点非常重要;
- 遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的;
- 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零
Usage
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
SCAN is a cursor based iterator. This means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call.
An iteration starts when the cursor is set to 0, and terminates when the cursor returned by the server is 0. The following is an example of SCAN iteration:
127.0.0.1:6379> scan 0 count 5
1) "131072"
2) 1) "key:{tag}:000004481225"
2) "key:{tag}:000000042693"
3) "key:{tag}:000005393921"
4) "key:{tag}:000004360397"
5) "key:{tag}:000002615937"
127.0.0.1:6379> scan 0 count 6
1) "1179648"
2) 1) "key:{tag}:000004481225"
2) "key:{tag}:000000042693"
3) "key:{tag}:000005393921"
4) "key:{tag}:000004360397"
5) "key:{tag}:000002615937"
6) "key:{tag}:000004677871"
127.0.0.1:6379> scan 0
1) "1966080"
2) 1) "key:{tag}:000004481225"
2) "key:{tag}:000000042693"
3) "key:{tag}:000005393921"
4) "key:{tag}:000004360397"
5) "key:{tag}:000002615937"
6) "key:{tag}:000004677871"
7) "myhash:{tag}:000000067032"
8) "key:{tag}:000000062496"
9) "key:{tag}:000009606011"
10) "key:{tag}:000007564046"
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
In the example above, the first call uses zero as a cursor, to start the iteration. The second call uses the cursor returned by the previous call as the first element of the reply, that is, 17.
As you can see the SCAN return value is an array of two values: the first value is the new cursor to use in the next call, the second value is an array of elements.
Since in the second call the returned cursor is 0, the server signaled to the caller that the iteration finished, and the collection was completely explored. Starting an iteration with a cursor value of 0, and calling SCAN until the returned cursor is 0 again is called a full iteration.
Misc
Key 操作
# 删除所有key
127.0.0.1:6379> flushall
# 列出所有key
127.0.0.1:6379> key *
#列出满足特定正则的key
127.0.0.1:6379> key ...
Reference
- https://redis.io/commands
- 《Redis入门指南》
- 《Redis设计与实现》
- 缓存穿透,缓存击穿,缓存雪崩解决方案分析 - https://blog.csdn.net/zeb_perfect/article/details/54135506
- Redis架构之防雪崩设计:网站不宕机背后的兵法 - https://mp.weixin.qq.com/s/TBCEwLVAXdsTszRVpXhVug?
- 为什么说Redis是单线程的以及Redis为什么这么快! - https://blog.csdn.net/xlgen157387/article/details/79470556
- Redis 网络架构及单线程模型 - http://blog.jobbole.com/100079/
- 这可能是目前最全的Redis高可用技术解决方案总结 - https://mp.weixin.qq.com/s/r1ig-jO13YxbqrofJzmkfw
- Redis -https://cyc2018.github.io/CS-Notes/#/notes/Redis?id=%e5%85%ad%e3%80%81%e9%94%ae%e7%9a%84%e8%bf%87%e6%9c%9f%e6%97%b6%e9%97%b4