【Redis初阶】常见数据类型
目录
一、Redis常见数据类型
1.1 前言
1.1.1 基本全局命令
1.1.1.1 SET
1.1.1.2 GET
1.1.1.3 KEYS
1.1.1.4 EXISTS
1.1.1.5 DEL
1.1.1.6 EXPIRE
1.1.1.7 TTL
1.1.1.8 Redis的key的过期策略是怎么实现的?(一个经典的面试题)
1.1.1.9 定时器的实现原理
1.1.1.10 TYPE
1.1.1.11 全局命令小结
1.1.2 数据类型和编码方式
1.1.3 单线程架构
一、Redis常见数据类型
1.1 前言
1.1.1 基本全局命令
1.1.1.1 SET
SET把key和value存进去。
1.1.1.2 GET
GET
根据key来取value
1.1.1.3 KEYS
KEYS返回所有满⾜样式(pattern)的 key。⽀持如下统配样式。
- h?llo匹配hello,hallo和hxllo
- h*llo匹配hllo和heeeello
- h[ae]llo匹配hello和hallo但不匹配hillo
- h[^e]llo匹配hallo,hbllo, ... 但不匹配hello
- h[a-b]llo匹配hallo和hbllo
KEYS pattern
这些匹配规则不用特意去背。
注意:
keys命令的时间复杂度是O(N)
所以在生产环境上面一般都会禁止使用keys命令,尤其是keys *(查询redis中所有的key!!!)
生成环境上面key可能会非常多!!而redis是一个单线程的服务器,执行keys * 的事件非常长,就使redis服务器被阻塞了,无法给其它客户端提供服务!!
1.1.1.4 EXISTS
EXISTS判断某个 key 是否存在,或者多个是否存在。EXISTS key [key ...]时间复杂度:O(1)返回值:key 存在的个数。
示例:
redis> SET key1 "Hello" "OK" redis> EXISTS key1 (integer) 1 redis> EXISTS nosuchkey (integer) 0 redis> SET key2 "World" "OK" redis> EXISTS key1 key2 nosuchkey (integer) 2图解:
1.1.1.5 DEL
DEL删除指定的 key或者多个。DEL key [key ...]时间复杂度:O(1)返回值:删除掉的 key 的个数。
redis> SET key1 "Hello""OK"redis> SET key2 "World""OK"redis> DEL key1 key2 key3(integer) 2
1.1.1.6 EXPIRE
EXPIRE为指定的 key 添加秒级的过期时间(Time To Live TTL)EXPIRE key seconds时间复杂度:O(1)返回值:1 表⽰设置成功。0 表示设置失败。
示例:
redis> SET mykey "Hello" "OK" redis> EXPIRE mykey 10 (integer) 1 redis> TTL mykey (integer) 101.1.1.7 TTL
TTL:用来查看当前key的过期时间还剩下多少。
语法:TTL key时间复杂度:O(1)返回值:剩余过期时间。-1 表⽰没有关联过期时间,-2 表⽰ key 不存在。
1.1.1.8 Redis的key的过期策略是怎么实现的?(一个经典的面试题)
一个redis中可能同时存在很多很多key。这些key中有很大可能都有过期时间。此时,redis服务器咋知道哪些key已经过期要被删除,哪些key还没过期??
如果直接遍历所有的key,显然是行不通的,,效率非常低。
redis整体的策略是:
1.定期删除
每次抽取一部分,进行验证过期时间,保证这个抽取的过程,足够快!!(有点像数学概率里面的抽样检测)
那么为啥对于时间有着明确的要求呢?
因为redis是一个单线程的程序。
主要的任务(处理每个命令的任务,刚才扫描过期key。。。。)
如果扫描过期key消耗的时间太多了,就可能导致正常处理请求命令被阻塞了。(产生了类似于keys *这样的效果)
2. 惰性删除
假设这个key已经到了过期时间了,但是暂时还没有删他,key还存在。
紧接着,后面有一次访问,正好用到了这个key,于是这次访问就会让redis服务器触发删除key的操作,同时再返回一个nil
图解:
1.1.1.9 定时器的实现原理
1. 基于优先级队列/堆
正常的队列是先进先出。优先级队列则是按照指定的优先级,先出。
啥叫优先级高?自定义的~~
在redis过期key的场景中,就可以通过“过期时间越早,就是优先级越高”
现在假定有很多key设置了过期时间.
就可以把这些key加入到一个优先级队列中,指定优先级规则是过期时间早的,先出队列。
队首元素,就是最早的要过期的key!!
此时定时器中只要分配一个线程,让这个线程去检查对首元素,看是否过期即可!!
如果对手元素还么有过期,后续元素一定没过期!
此时扫描线程不需要遍历所有key只盯住这一个队首元素即可!!
另外在扫描线程检查队首元素过期时间的时候,也不能检查的太频繁
此时做法就是可以根据当前时刻和队首元素的过期时间,设置一个等待时间
当一个时间差不多了,系统再唤醒这个线程。
此时扫描线程不需要高频扫描队首元素,把cpu的开销也节省下来了。
万一再线程休眠的时候,来了一个新任务要执行。
可以再新任务添加的时候唤醒一下刚才的线程~~重新检查一下队首的元素,再根据时间差距重新调整阻塞时间即可。
2.基于时间轮实现的定时器
把时间分成很多小段(具体怎么划分,看实际需求)
当设定的过期时间特别长,就如上面的例子,可能小段没有那么多,但是还是要继续往后面数,比如上面就八段,但是还是要数九、十、十一、一直数到三十,在后面加上该任务。
此处我们一定要注意!!
redis并没有采取上面的方案!!但是我们需要了解这两种方法,都是属于高效的定时器实现方式,很多场景都有可能会用到。
在redis源码里面有一个比较核心的机制,是事件循环。
1.1.1.10 TYPE
TYPE返回 key 对应的数据类型。TYPE key时间复杂度:O(1)返回值:none,string,list,set,zset,hashandstream.。
示例:
redis> SET key1 "value" "OK" redis> LPUSH key2 "value" (integer) 1 redis> SADD key3 "value" (integer) 1 redis> TYPE key1 "string" redis> TYPE key2 "list" redis> TYPE key3 "set"1.1.1.11 全局命令小结
图解:
1.1.2 数据类型和编码方式
数据结构 | 内部编码 |
string | raw |
string | int |
string | embstr |
hash | hashtable |
hash | ziplist |
list | linkedlist |
list | ziplist |
set | hashtable |
set | intset |
zset | skiplist |
zset | ziplist |
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> lpush mylist a b c (integer) 3 127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding mylist "quicklist"redis会自动根据当前实际情况选择内部的编码方式,自动适应的。
1.1.3 单线程架构
127.0.0.1:6379> set hello world
127.0.0.1:6379> incr counter
127.0.0.1:6379> incr counter
redis能够使用单线程模型很好的工作,原因主要在于redis的核心业务逻辑,都是短平快的
不太消耗cpu资源也就不太吃多核了!!!
弊端!!!
redis必须要特别小心,某个操作占用时间特别长,就会阻塞其它命令的执行!!!
a.纯内存访问。Redis 将所有数据放在内存中,内存的响应时⻓⼤约为 100 纳秒,这是 Redis 达到每秒万级别访问的重要基础。b.⾮阻塞 IO。Redis 使⽤ epoll 作为 I/O 多路复⽤技术的实现,再加上 Redis ⾃⾝的事件处理模型将 epoll 中的连接、读写、关闭都转换为事件,不在⽹络 I/O 上浪费过多的时间,如下图。c.单线程避免了线程切换和竞态产⽣的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同⼀份共享数据时带来的切换和等待消耗。
举个栗子:
方源,商心慈,谢含沫今晚要出去吃饭,
方源要吃方正小吃,商心慈要吃黑土小吃,谢含沫要吃人族蛊师小吃。
有三种解决方法。
一:
先买方正小吃,然后等待。
再买黑土小吃,然后等待。
再买人族蛊师小吃,等待。
各等各的。这是效率最低的方案。
二:
三个人各自去买各自的小吃。效率大大提升了,但是系统开销大了。
三:
商心慈一个人去,先买方正小吃,在等的过程中,去买黑土小吃,等的过程中去买人族蛊师小吃,这三样东西,哪个先做好了,对应的老板就可以喊一嗓子。(这就是epoll事件通知/回调机制)此时一个线程同时做三件事情,能高效的完成的前提是,这三件事的交互都不太频繁,大部分都在等!!!
如果这三件事都是交互频繁的,还是老老实实多搞几个线程靠谱~~一个线程容易忙不过来。