文章目录
- [2. Redis 常见数据类型](#2. Redis 常见数据类型)
-
- [2.1 基础知识](#2.1 基础知识)
-
- [2.1.1 基本命令](#2.1.1 基本命令)
-
- [2.1.1.1 SET](#2.1.1.1 SET)
- [2.1.1.2 GET](#2.1.1.2 GET)
- [2.1.2 基本全局命令](#2.1.2 基本全局命令)
-
- [2.1.2.1 KEYS](#2.1.2.1 KEYS)
- [2.1.2.2 EXISTS](#2.1.2.2 EXISTS)
- [2.1.2.3 DEL](#2.1.2.3 DEL)
- [2.1.2.4 EXPIRE](#2.1.2.4 EXPIRE)
- [2.1.2.5 TTL](#2.1.2.5 TTL)
- [2.1.2.6 TYPE](#2.1.2.6 TYPE)
2. Redis 常见数据类型
Redis提供了5种数据结构,理解每种数据结构的特点对于Redis开发运维非常重要,同时掌握每种数据结构的常见命令,会在使用Redis的时候做到游刃有余。本章内容如下:
- 预备知识:几个全局(
generic)命令,数据结构和内部编码,单线程模式机制分析。5种数据结构的特点、命令使用、应用场景示例。- 键遍历、数据库管理。
redis客户端有很多种形态:
- 命令行客户端。
redis-cli- 图形化界面客户端。桌面程序,
web程序(有可能连不上redis,因为中间会经历很多跳板机,壁垒机,权限校验)- 基于
redis的api自行开发客户端(工作中最主要的形态)类似于MySQL的C语言API和JDBC。
2.1 基础知识
在正式介绍
5种数据结构之前,了解一下Redis的一些全局命令、数据结构和内部编码、单线程命令处理机制是十分必要的,它们能为后面内容的学习打下一个良好的基础。主要体现在两个方面:
Redis的命令有上百个,如果纯靠死记硬背比较困难,但是如果理解Redis的一些机制,会发现这些命令有很强的通用性。Redis不是万金油,有些数据结构和命令必须在特定场景下使用,一旦使用不当可能对Redis本身或者应用本身造成致命伤害。
2.1.1 基本命令
2.1.1.1 SET
设置
key以保存字符串值。如果key已经保存了一个值,则不管其类型如何,它都将被覆盖。在
SET操作成功时,先前与该键关联的任何存活时间都将被丢弃。
语法:
mysql
SET key value [NX | XX | IFEQ ifeq-value | IFNE ifne-value |
IFDEQ ifdeq-digest | IFDNE ifdne-digest] [GET] [EX seconds |
PX milliseconds | EXAT unix-time-seconds |
PXAT unix-time-milliseconds | KEEPTTL]
命令有效版本:
1.0.0之后时间复杂度:
O(N)返回值:无
实例:
mysql
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> set "key3" value2
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get value2
(nil)
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> get key3
"value2"
127.0.0.1:6379>
2.1.1.2 GET
获取
key的值。如果键不存在,则返回nil。和null/NULL是一个意思。如果存储在
key处的值不是字符串,则返回错误,因为GET只处理字符串值。
语法:
mysql
GET key
命令有效版本:
1.0.0之后时间复杂度:
O(N)返回值:
key的值。
实例:
mysql
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> set "key3" value2
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get value2
(nil)
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> get key3
"value2"
127.0.0.1:6379>
2.1.2 基本全局命令
Redis有5种数据结构,但它们都是键值对种的值,对于键来说有一些通用的命令。
redis中的命令不区分大小写。
2.1.2.1 KEYS
返回所有满足样式(
pattern)的key。支持如下统配样式:
h?llo匹配hello,hallo和hxlloh*llo匹配hllo和heeeelloh[ae]llo匹配hello和hallo但不匹配hilloh[^e]llo匹配hallo,hbllo, ... 但不匹配helloh[a-b]llo匹配hallo和hbllo(匹配a到b范围内的字符)上述规则这些不用刻意去背,有个印象就行了。要用的时候去查一下就行。
注意:
keys的时间复杂度是O(N),所以在一些生产环境上会禁止使用keys命令,尤其是keys *。因为生产环境上的
key可能会非常多,而redis是一个单线程服务器,执行keys *的时间非常长,就会使得redis服务器被阻塞,无法给其他客户提供服务。这样的后果可能是灾难性的。因为
redis经常会做缓存,挡在MySQL前面,替MySQL负重前行。万一redis被一个keys *阻塞住了,其他的查询redis操作就超时了。这些请求就会直接查数据库,突然一堆请求过来,MySQL措手不及就可能挂掉。整个系统基本就挂了,如果没有及时发现的话,年终奖就飞走啦。(银手镯应该不至于)
语法:
mysql
KEYS pattern
命令有效版本:
1.0.0之后时间复杂度:
O(N)返回值:匹配
pattern的所有key。
示例:
mysql
127.0.0.1:6379> MSET firstname Jack lastname Stuntman age 35keys *
OK
127.0.0.1:6379> KEYS *name*
1) "firstname"
2) "lastname"
127.0.0.1:6379> KEYS a??
1) "age"
127.0.0.1:6379> KEYS *
1) "firstname"
2) "lastname"
3) "backup3"
4) "age"
127.0.0.1:6379>
2.1.2.2 EXISTS
判断某个
key是否存在。语法:
mysql
EXISTS key [key ...]
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
key存在的个数。
实例:
mysql
127.0.0.1:6379> SET key1 "Hello"
OK
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> exists nosuchkey
(integer) 0
127.0.0.1:6379> set key2 "World"
OK
127.0.0.1:6379> exists key1 key2 nosuchkey
(integer) 2
127.0.0.1:6379> exists key1 key2
(integer) 2
127.0.0.1:6379>
2.1.2.3 DEL
删除指定的
key。语法:
mysql
DEL key [key ...]
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:删除掉的
key的个数。不过不小心删一两个倒是没事,一不小心全删掉或者删掉大半,就会有很大的影响了。请求都打给
MySQL就可能把MySQL搞挂掉。如果把
redis作为数据库,误删的影响就大了。如果把
redis作为消息队列,误删的影响需要具体情况具体分析。
示例:
mysql
127.0.0.1:6379> SET key1 "Hello"
"OK"
127.0.0.1:6379> SET key2 "World"
"OK"
127.0.0.1:6379> DEL key1 key2 key3
(integer) 2
127.0.0.1:6379>
2.1.2.4 EXPIRE
为指定的
key添加秒级的过期时间(Time To Live TTL)语法:
mysql
EXPIRE key seconds
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
1表示设置成功。0表示设置失败。
其实很多业务都有时间限制。例如:
手机验证码几分钟内有效
外卖优惠卷几号后过期
基于
redis实现分布式锁,为了避免不能正确解锁的情况(比如解锁的数据丢了),通常都会在加锁的时候设置一下过期时间。其实所谓的
redis实现分布式锁,就是给redis里面写一个特殊的key value。
示例:
mysql
127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> get mykey
"Hello"
127.0.0.1:6379> expire mykey 10
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) 7
127.0.0.1:6379> get mykey
"Hello"
127.0.0.1:6379> ttl mykey
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379>
2.1.2.5 TTL
获取指定
key的过期时间,秒级。语法:
mysql
TTL key
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:剩余过期时间。
-1表示没有关联过期时间,-2表示key不存在。
示例:
mysql
127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> get mykey
"Hello"
127.0.0.1:6379> expire mykey 10
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) 7
127.0.0.1:6379> get mykey
"Hello"
127.0.0.1:6379> ttl mykey
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379>
EXPIRE和TTL命令都有对应的支持毫秒为单位的版本:PEXPIRE和PTTL。
键的过期机制:
定期删除:每次抽取一部分,进行验证过期时间。保证这个抽取检查的过程,足够快。因为如果把所有的
key都抽取一边就太耗时了。为啥这里对于定期删除的时间,有明确的要求呢?
因为
redis是单线程的程序。主要的任务是处理每个命令的任务,刚才扫描过期key,如果扫描过期key消耗的时间太多了,就可能导致正常处理请求命令就被阻塞了。(产生了类似于执行keys *这样的效果,外界看起来就像redis挂了一样)惰性删除:假设这个
key已经到过期时间了,但是暂时还没删它,key还存在.紧接着,后面又一次访问,正好用到了这个key,于是这次访问就会让redis服务器触发删除key的操作,同时再返回一个nil。类似于:有一个杂货铺老板,店里有一堆东西,但是不知道哪些过期了。你买东西的时候,他发现过期了,就会丢掉商品不让你买。
下图是定期删除:

注意:
redis中没有采取定时器的方式来实现过期key删除。
虽然没有采取定时器,但是有两种高效的定时器方案可以看一下:(下面两个方案并不在redis中采用)方案一:基于优先级队列/堆
正常的队列是先进先出,优先级队列则是按照指定的优先级,先出。
这个优先级高可以自定义。
例如:在
redis过期key的场景中,就可以通过"过期时间越早,就是优先级越高"现在假定有很多
key设置了过期时间,就可以把这些key加入到一个优先级队列中,指定优先级规则是过期时间早的,先出队列,队首元素,就是最早的要过期的key。比如:
key1:12:00
key2:13:00
key3:14:00
key1先过期,先进队列,key2其次,key3最后此时定时器中只要分配一个线程,让这个线程去检查队首元素,看是否过期即可。如果队首元素还没过期,后续元素一定没过期。此时扫描线程 不需要遍历所有key只盯住这一个队首元素即可。
另外在扫描线程检查队首元素过期时间的时候,也不能检查的太频繁,此时做法就是可以根据当前时刻和队首元素的过期时间,设置一个等待。当时间差不多到了,系统再唤醒这个线程。此时扫描线程不需要高频扫描队首元素,把cpu的开销也节省下来了。
万一在线程休眠的时候,来了一个新的任务,是11:30要执行,可以在新任务添加的时候,唤醒一下刚才的线程~~重新检查一下队首元素,再根据时间差距重新调整阻塞时间即可。
方案二:基于时间轮实现的定时器把时间划分成很多小段。(划分的粒度,看实际需求)

2.1.2.6 TYPE
返回
key对应的值的数据类型。语法:
mysql
TYPE key
命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
none , string , list , set , zset , hash and stream ...
示例:
mysql
127.0.0.1:6379> SET key1 "value"
OK
127.0.0.1:6379> LPUSH key2 "value"
(integer) 1
127.0.0.1:6379> SADD key3 "value"
(integer) 1
127.0.0.1:6379> TYPE key1
string
127.0.0.1:6379> TYPE key2
list
127.0.0.1:6379> TYPE key3
set
127.0.0.1:6379>