redis是一种key-value型数据库,它将数据全部以键值对的形式存储在内存中,并且key与value一一对应。这里的key被形象的称之为密钥,redis提供了很多操作这把“密钥”的命令,从而实现了对存储数据的管理
key的特点
key的类型
key 的类型对应着 value 的类型,同样也有五种(string、list、hash、set、zset)。如果 key 指向的是一个字符串类型的值,那么 key 的类型就是字符串。我们可以通过TYPE命令来查看 key 的类型。
也就是说,type命令实际返回的就是当前键的数据结构类型,它们分别是:string、list、hash、set、zset,但这些只是redis对外的数据结构,如下图:
实际上每种数据结构都有自己底层的不止一种的内部编码实现,redis会根据场景选择合适的内部编码。通过object encoding [key] 可以查看内部编码
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"ziplist"
这样设计的好处:
- 可以改进内部编码,对外的数据结构和命名没有影响,这样一旦开发出了更优秀的编码,无需改动外部数据结构和命令
- 多种内部编码可以在不同的场景下发挥各自有时。比如ziplist比较节约内存,但在元素比较多的时候,性能会下降,此时转换为linkedlist。
key的命名规范
key的命名需要遵循如下规则
- key取值不可以太长,否则会影响value的查找效率,并且浪费内存空间
- key取值也不可以过短,否则会使得key可读性变差
在 key 的取值上, Redis 官方建议使用“见名知意”的字符串格式,因为这样便于我们理解 key 的含义。比如要现在存放一个用户的姓名,其信息如下:
我们使用一个 key 来存储用户的名字,key 的设置如下所示:
127.0.0.1:6379> set user:id:01:username XiaoHong
OK
上述示例,自定义了uesr:id:01:username
这个 key,通过 key 不仅可以知道用户的 id,还可以知道这个 key 是用来存储用户名字的。注意,这里的:只是起到分割符的作用,并不是固定的语法格式。
在redis中,我们也可以将一个空字符串设置成key,示例如下:
127.0.0.1:6379> SET "" c.biancheng.net
OK
127.0.0.1:6379> GET ""
"c.biancheng.net"
key的类型并不局限于字符串,在redis中key具有二进制安全的特性,这意味着它可以使用任何二进制序列,但是这种key过于复杂一般不建议采用。
key过期时间
redis允许为key设置一个过期时间,也就是“到点自动删除”。这在实际业务中是非常有用的:
- 一是它可以避免使用频率不高的 key 长期存在,从而占用内存资源;
- 二是控制缓存的失效时间。
Redis 会把每个设置了过期时间的 key 存放到一个独立的字典中,并且会定时遍历这个字典来删除到期的 key。
除了定时遍历之外,它还会使用“惰性策略”来删除过期的key。所谓“惰性策略”指的是当客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除。
redis使用两种相结合的方法去处理过去的key。
与Key相关的命令
和 key 相关的命令,它的语法格式如下所示:
redis 127.0.0.1:6379> COMMAND KEY_NAME
- COMMAND:表示 key 的命令;
- KEY_NAME:表示 key 的名字。
下表对常用的 Redis 键命令做了简单的总结:
命令 | 说明 |
---|---|
DEL key | 若键存在的情况下,该命令用于删除键 |
DUMP key | 用于序列化给定key,并返回被序列化的值 |
EXISTS key | 用于检查键是否存在,如果存在则返回1,否则返回0 |
EXPIRE key | 设置 key 的过期时间,以秒为单位。 |
EXPIREAT key | 该命令与 EXPIRE 相似,用于为 key 设置过期时间,不同在于,它的时间参数值采用的是时间戳格式。 |
PEXPIRE key | 设置 key 的过期,以毫秒为单位。 |
PEXPIREAT key | 与 PEXPIRE 相似,用于为 key 设置过期时间,采用以毫秒为单位的时间戳格式。 |
KEYS pattern | 此命令用于查找与指定 pattern 匹配的 key。 |
MOVE key db | 将当前数据库中的 key 移动至指定的数据库中(默认存储为 0 库,可选 1-15中的任意库)。 |
PERSIST key | 该命令用于删除 key 的过期时间,然后 key 将一直存在,不会过期。 |
PTTL key | 用于检查 key 还剩多长时间过期,以毫秒为单位。 |
TTL key | 用于检查 key 还剩多长时间过期,以秒为单位。 |
RANDOM KEY | 从当前数据库中随机返回一个 key。 |
RENAME key newkey | 修改 key 的名称。 |
RENAMENX key newkey | 如果新键名不重复,则将 key 修改为 newkey。 |
SCAN cursor | |
TYPE key | 该命令用于获取 value 的数据类型。 |
Type:键的类型
key 的类型对应着 value 的类型,同样也有五种(string、list、hash、set、zset)。如果 key 指向的是一个字符串类型的值,那么 key 的类型就是字符串。我们可以通过TYPE命令来查看 key 的类型
语法
redis 127.0.0.1:6379> TYPE KEY_NAME
可用版本
>= 1.0.0
返回值
返回 key 的数据类型,数据类型有:
- none (key不存在)
- string (字符串)
- list (列表)
- set (集合)
- zset (有序集)
- hash (哈希表)
实例
# 字符串
redis> SET weather "sunny"
OK
redis> TYPE weather
string
# 列表
redis> LPUSH book_list "programming in scala"
(integer) 1
redis> TYPE book_list
list
# 集合
redis> SADD pat "dog"
(integer) 1
redis> TYPE pat
set
KEYS:查看键
作用
Keys 命令用于查找所有符合给定模式 pattern 的 key
- KEYS * 匹配数据库中所有 key (
*
匹配任意字符) - KEYS h*llo 匹配 hllo 和 heeeeello 等 (
*
匹配任意字符) - KEYS h?llo 匹配 hello , hallo 和 hxllo 等 (
?
匹配一个字符) - KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo (
[]
匹配任意字符,比如[1, 3]表示匹配1和3,[1-10表示匹配1到0的任意数字])
语法
redis 127.0.0.1:6379> KEYS PATTERN
可用版本
> = 1.0.0
返回值
- 符合给定模式的 key 列表 (Array)。
实例
redis 127.0.0.1:6379> SET runoob1 redis
OK
redis 127.0.0.1:6379> SET runoob2 mysql
OK
redis 127.0.0.1:6379> SET runoob3 mongodb
OK
查找以 runoob 为开头的 key:
redis 127.0.0.1:6379> KEYS runoob*
1) "runoob3"
2) "runoob1"
3) "runoob2"
keys * 会返回当前库中所有的键。
redis 127.0.0.1:6379> KEYS *
1) "runoob3"
2) "runoob1"
3) "runoob2"
若要删除所有以某个字符开头的键,可以执行如下操作:
# 利用Linux的管道删除
./redis-cli keys h* | xargs ./redis-cli del
使用建议
生成环境中不建议使用keys,如果确实有遍历键的需求怎么办?
- 在一个不对外提供服务的redis从节点上执行,这样不会阻塞到客户端的请求,但是会影响到主从复制
- 如果确认键比较少,可以执行该命令
- 使用scan命令渐进式的遍历键,可以有效防止阻塞
scan
redis从2.8版本后,提供了一个新的命令scan,它能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度都是O(1),但是真正要实现keys的功能,需要执行多次scan
redis存储键值对实际使用的是hashtable的数据结构。每次执行scan,可以想象成只扫描一个字段中的一部分键,直到将字典中所有键遍历完毕
语法
SCAN 命令的语法格式如下:
SCAN cursor [MATCH pattern] [COUNT count]
参数说明:
- cursor:是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
- match pattern:是可选参数,他的作用是做模式的匹配,这点和keys的模式匹配很想。
- count number:是可选参数,它的作用是表明每次要遍历的键个数,默认10
实例
例如当前Redis数据库中有26个键(26个英文字母),现在我们要分3次遍历完数据库中的26个键
- 第一次执行"scan 0",返回两部分:
- 第一部分6,代表下次scan需要的cursor
- 第二部分是返回的11个键
127.0.0.1:6379> scan 0
1) "6"
2) 1) "w"
2) "i"
3) "e"
4) "x"
5) "j"
6) "q"
7) "y"
8) "u"
9) "b"
10) "o"
- 第二次执行“scan 6”,因为上一次返回6,所以从“cursor=6”开始扫描,结果如下所示
127.0.0.1:6379> scan 6
1) "11"
2) 1) "h"
2) "n"
3) "m"
4) "t"
5) "c"
6) "d"
7) "g"
8) "p"
9) "z"
10) "a"
- 第二次执行“scan 11”,因为上一次返回11,所以从“cursor=11”开始扫描,结果如下所示
127.0.0.1:6379> scan 11
1) "0"
2) 1) "s"
2) "f"
3) "r"
4) "v"
5) "k"
6) "l"
- 上图中SCAN命令返回0,代表所有的键已经遍历完,所以遍历结束
扩展
- 除了scan之外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令。例如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似
- 下面以sscan为例子进行说明, 当前集合有两种类型的元素, 例如分别以old: user和new: user开头, 先需要将old: user开头的元素全部删除, 可以参考如下伪代码:
String key = "myset";
// 定义pattern
String pattern = "old:user*";
// 游标每次从0开始
String cursor = "0";
while (true) {
// 获取扫描结果
ScanResult scanResult = redis.sscan(key, cursor, pattern);
List elements = scanResult.getResult();
if (elements != null && elements.size() > 0) {
// 批量删除
redis.srem(key, elements);
}/
/ 获取新的游标
cursor = scanResult.getStringCursor();
// 如果游标为0表示遍历结束
if ("0".equals(cursor)) {
break;
}
}
问题
- 渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无效,如果在scan的过程中如果有键的变化(增删改),那么遍历效果可能会遇到下面问题
- 新增的键可能没有遍历到,遍历出现重复的键等兴趣
- 也就是说scan并不能保证完整的遍历出所有的键
Dbsize :键总数
- Dbsize 命令用于返回当前数据库的 key 的数量。
语法
redis 127.0.0.1:6379> DBSIZE
可用版本
>= 1.0.0
返回值
- 当前数据库的 key 的数量。
实例
redis 127.0.0.1:6379> DBSIZE
(integer) 5
redis 127.0.0.1:6379> SET new_key "hello_moto" # 增加一个 key 试试
OK
redis 127.0.0.1:6379> DBSIZE
(integer) 6
说明
- dbsize在计算键总数时不会遍历所有键,而是直接获取redis内置的键总数变量,其时间复杂度为O(1)
- 而keys会遍历所有键,时间复杂度为Q(n),当redis保存了大量数据时,线上环境禁止使用keys
检测键是否存在
- EXISTS 命令用于检查给定 key 是否存在。
语法
redis 127.0.0.1:6379> EXISTS KEY_NAME
可用版本
>= 1.0.0
返回值
- 若 key 存在返回 1 ,否则返回 0 。
实例
redis 127.0.0.1:6379> EXISTS runoob-new-key
(integer) 0
现在我们创建一个名为 runoob-new-key 的键并赋值,再使用 EXISTS 命令。
redis 127.0.0.1:6379> set runoob-new-key newkey
OK
redis 127.0.0.1:6379> EXISTS runoob-new-key
(integer) 1
redis 127.0.0.1:6379>
删除键
- DEL 命令用于删除已存在的键(不论数据类型如何都可以删除)。不存在的 key 会被忽略
语法
redis 127.0.0.1:6379> DEL KEY_NAME
可用版本
>= 1.0.0
返回值
- 被删除 key 的数量。
实例
实例一:删除一个键
redis 127.0.0.1:6379> SET w3ckey redis
OK
redis 127.0.0.1:6379> DEL w3ckey
(integer) 1
实例二:删除多个键
dek a b c
键过期
除了expire、ttl命令以外,Redis还提供了expireat、pexpire、pexpireat、pttl、persist等一系列命令
- expire key seconds:键在seconds秒后过期
- expireat key timestamp:键在秒级时间戳timestamp后过期
expire
Expire 命令用于设置 key 的过期时间,key 过期后redis将会自动删除键
语法
redis 127.0.0.1:6379> Expire KEY_NAME TIME_IN_SECONDS
可用版本
>= 1.0.0
返回值
- 设置成功返回 1 。
- 当 key 不存在或者不能为 key 设置过期时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的过期时间)返回 0 。
实例
redis 127.0.0.1:6379> SET runooobkey redis
OK
redis 127.0.0.1:6379> EXPIRE runooobkey 60
(integer) 1
以上实例中我们为键 runooobkey 设置了过期时间为 1 分钟,1分钟后该键会自动删除
expireat:设置过期时间
作用
Redis Expireat 命令用于以 UNIX 时间戳(unix timestamp)格式设置 key 的过期时间。key 过期后将不再可用。
语法
redis 127.0.0.1:6379> Expireat KEY_NAME TIME_IN_UNIX_TIMESTAMP
返回值
- 设置成功返回 1 。
- 当 key 不存在或者不能为 key 设置过期时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的过期时间)返回 0 。
实例
redis 127.0.0.1:6379> SET runoobkey redis
OK
redis 127.0.0.1:6379> EXPIREAT runoobkey 1293840000
(integer) 1
pexpire: 设置过期时间
作用
Redis PEXPIRE 命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。
语法
PEXPIRE key milliseconds
返回值
- 设置成功,返回 1
- key 不存在或设置失败,返回 0
实例
redis> SET mykey "Hello"
"OK"
redis> PEXPIRE mykey 1500
(integer) 1
redis> TTL mykey
(integer) 1
redis> PTTL mykey
(integer) 1498
redis>
pexpireat: 设置过期时间
作用
这个命令和 EXPIREAT 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 EXPIREAT 那样,以秒为单位。
返回值
设置成功返回 1,若 key 不存在或者不能为其设置过期时间,则返回 0。
实例
127.0.0.1:6379> set www.biancheng.net Python
OK
127.0.0.1:6379> PEXPIREAT www.biancheng.net 12000000000
(integer) 1
ttl
以秒为单位返回 key 的**剩余过期时间//。
格式
redis 127.0.0.1:6379> TTL KEY_NAME
可用版本
>= 1.0.0
返回值
有三种返回值
- -2: key 不存:
- -1: key 存在但没有设置剩余生存时间时
- 大于等于0的整数: key 的剩余生存时间(以秒为单位)
注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
实例
# 不存在的 key
redis> FLUSHDB
OK
redis> TTL key
(integer) -2
# key 存在,但没有设置剩余生存时间
redis> SET key value
OK
redis> TTL key
(integer) -1
# 有剩余生存时间的 key
redis> EXPIRE key 10086
(integer) 1
redis> TTL key
(integer) 10084
pttl:查询过期时间
同ttl,只是以毫秒为单位返回 key 的剩余过期时间。
注意
当使用redis相关的过期命令时,需要注意下面几点:
- 如果expire key的键不存在,返回结果为0
127.0.0.1:6379> expire not_exist_key 30
(integer) 0
- 如果过期时间为负值,键会立即被删除,犹如使用del命令一样;
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello -2
(integer) 1
127.0.0.1:6379> get hello
(nil)
- persist命令可以将键的过期时间清除;
127.0.0.1:6379> hset key f1 v1
(integer) 1
127.0.0.1:6379> expire key 50
(integer) 1
127.0.0.1:6379> ttl key
(integer) 46
127.0.0.1:6379> persist key
(integer) 1
127.0.0.1:6379> ttl key
(integer) -1
- 对于字符串类型键,执行set命令会去掉过期时间;
如下是Redis源码中,set命令的函数setKey,可以看到***执行了removeExpire(db,key)函数去掉了过期时间:
下面的例子证实了set会导致过期时间失效,因为ttl变为-1:
127.0.0.1:6379> expire hello 50
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 46
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> ttl hello
(integer) -1
- redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置;
- setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间;
rename:键重命名
作用
用于修改 key 的名称 。
语法
rename old_key_name new_key_name
返回值
- 改名成功时提示 OK ,失败时候返回一个错误。
- 当 old_key_name 和 new_key_name相同,或者 old_key_name 不存在时,返回一个错误。
- 当 new_key_name已经存在时, RENAME 命令将覆盖旧值。
实例
# key 存在且 newkey 不存在
redis> SET message "hello world"
OK
redis> RENAME message greeting
OK
redis> EXISTS message # message 不复存在
(integer) 0
redis> EXISTS greeting # greeting 取而代之
(integer) 1
# 当 key 不存在时,返回错误
redis> RENAME fake_key never_exists
(error) ERR no such key
# newkey 已存在时, RENAME 会覆盖旧 newkey
redis> SET pc "lenovo"
OK
redis> SET personal_computer "dell"
OK
redis> RENAME pc personal_computer
OK
redis> GET pc
(nil)
redis:1> GET personal_computer # 原来的值 dell 被覆盖了
"lenovo"
Renamenx
作用
在新的 key 不存在时修改 key 的名称 。
语法
rename old_key_name new_key_name
返回值
- 修改成功时,返回 1 。
- 如果 new_key_name已经存在,返回 0 。
实例
# newkey 不存在,改名成功
redis> SET player "MPlyaer"
OK
redis> EXISTS best_player
(integer) 0
redis> RENAMENX player best_player
(integer) 1
# newkey存在时,失败
redis> SET animal "bear"
OK
redis> SET favorite_animal "butterfly"
OK
redis> RENAMENX animal favorite_animal
(integer) 0
redis> get animal
"bear"
redis> get favorite_animal
"butterfly"
使用重命名时注意:
- ·由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视。
- 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同
randomkey:随机返回一个键
作用
从当前数据库中随机返回一个 key
语法
redis 127.0.0.1:6379> RANDOMKEY
返回值
- 当数据库不为空时,返回一个 key 。
- 当数据库为空时,返回 nil (windows 系统返回 null)。
实例
# 数据库不为空
redis> MSET fruit "apple" drink "beer" food "cookies" # 设置多个 key
OK
redis> RANDOMKEY
"fruit"
redis> RANDOMKEY
"food"
redis> KEYS * # 查看数据库内所有key,证明 RANDOMKEY 并不删除 key
1) "food"
2) "drink"
3) "fruit"
# 数据库为空
redis> FLUSHDB # 删除当前数据库所有 key
OK
redis> RANDOMKEY
(nil)
dump:序列化值
作用
- 该命令用于将键对应的值做序列化处理
语法
dump key_name
返回值
- 如果 key 不存在,那么返回 nil 。
- 否则,返回序列化之后的值。
实例
127.0.0.1:6379> SET num 12
OK
127.0.0.1:6379> DUMP num
"\x00\xc0\x0c\t\x00\xec\xd8\xa9\x9d\b\x82\xdfd"
127.0.0.1:6379> get num
"12"
redis> DUMP not-exists-key
(nil)