二. Redis 的安装及通用命令
1. Ubuntu 安装 Redis
(1) 切换到 root 用户:
su root
(2) 搜索 Redis 软件包
apt search redis
(3) 安装 Redis
apt install redis
(4) 查看 Redis
netstat -anp | grep redis
(5) 切换到 Redis 目录下
cd /etc/redis/
(6) 修改 Redis 配置文件:
vim redis.conf
(7) 重启 Redis 服务器:
service redis-server restart
(8) 查看 Redis 服务器状态:
service redis-server status
(9) 使用 Redis 客户端 (自动连到 Redis 服务器):
redis-cli
(10) 验证是否服务器正常工作:
ping
如果返回 pong, 则表明服务器正常工作.
[!NOTE]
- [补充知识]
- 退出 Redis 客户端: Ctrl + D
- Redis 默认端口: 6379
- Redis 的配置文件: redis.conf (里面包含了 Redis 的相关配置内容)
- vim 模式下:
i #修改 :w #保存当前文件 :q #退出vim :wq #保存并退出
2. Redis 客户端
Redis 也是一个 客户端-服务器结构 的程序.
Redis 的客户端和服务器可以在同一主机上, 也可以在不同主机上.
Redis 的客户端可以有一个, 也可以有多个:
Redis 命令行客户端
本机客户端:
redis-cli
其他主机的客户端:
redis-cli -h 127.0.0.2 -p 6379
# -h:其他主机的ip ; -p:其他主机的端口号
!! 一定要学会使用官方文档 !!
3. 通用命令
[!CAUTION]
注意: Redis 在命令方面不区分大小写, 但是在 key-value 方面 严格区分大小写.
必须要先进入 redis-cli 客户端程序, 才能使用 Redis 命令
Redis 中的命令不区分大小写.
Redis 支持很多种数据结构. 整体上来说, Redis 是键值对结构: key 固定就是字符串类型, 但value 会有多种数据类型 (字符串, 哈希表, 列表, 集合, 有序集合 …)
操作不同的数据结构会有不同的命令~
3.1 get 和 set
(1) get
根据 key 取到相应的 value 值.
get key1
# key1 是某个键值对的键.
[!NOTE]
get 一个不存在的 key, 会得到 (nil) --> nil 和 null 的意思是一样的.
(2) set
把 key 和 value 存储进 Redis.
set key value
# 其中, key和value都是字符串
# 使用时,不需要加""
3.2 Redis 全局命令
全局命令,就是能够搭配任意一个数据结构来使用的命令
(1) keys
返回所有满足样式 (pattern) 的 key.
- pattern 就是包含特殊符号的字符串, 作用是描述目标字符串长什么样.
pattern 样式:
① ?匹配 1个 任意字符.
eg: h?llo 匹配 hello, hallo, hxllo …
② *
匹配 0个到多个 任意字符.
eg: hllo 匹配 hllo 和 heeeeello …*
③ [abcde] 匹配固定选项, 只能匹配 a, b, c, d, e ; 别的不行.
eg: h [ae] llo 只能匹配 hello 和 hallo.
④ [^e]
排除e, 只有e匹配不了, 其他的都能匹配.
eg: h [^e]
llo 不能匹配 hello, 但可以匹配 hallo, hbllo …
⑤ [a - b] 匹配 a - b 这个范围内的字符, 包括两侧边界.
eg: h [a-b] llo 匹配 hallo 和 hbllo.
[!WARNING]
注意: keys 命令的时间复杂度是 O(n) (因为要遍历所有的 key).
所以生产环境中, 一般会禁止使用 keys 命令. 尤其是 keys * (查询 Redis 中所有的 key).
如果 keys 使用不当, 可能会导致 Redis 阻塞.
[!NOTE]
未来在工作中会涉及到的几个环境:
(1) 办公环境: 入职公司之后, 公司给发个笔记本电脑 (一般 8C16G512G, (配置较低) )
(2) 开发环境: 有时就是公司发的电脑, 有时是要用单独的服务器.
(3) 线上环境 (生产环境): 用户能够直接访问到的环境 (一旦生产环境出现问题, 一定会对用户的使用产生影响).
(2) exists
用于判断某个或某些 key 是否存在, 返回 key 存在的个数.
eg:
exists key1; #判断key1是否存在
exists key2 key3; #判断key2和key3是否存在
时间复杂度: O(1) (因为键值对是用哈希表存储的).
Redis 的很多命令都是支持一次完成多个操作的. 因为每次命令都要从客户端经过网络发往服务器, 所以将多次命令 (也就是网络请求和响应) 合并成一次能够提高性能. (网络开销小了)
(3) del
用于删除指定的 key, 一次可以删除一个或者多个, 返回删掉的 key 的个数.
eg:
del key1; #删除key1
del key2 key3; #删除key2和key3
时间复杂度: O(1) (因为键值对是用哈希表存储的).
(3) expire
给指定的 key 设置过期时间 (指定key 的存活时间超过这个值, 就会被自动删除), 返回值是1或0 (1表示成功, 0表示失败).
时间单位是 秒(s)
注: 要设置过期时间的 key 必须已经存在.
时间复杂度: O(1) (因为键值对是用哈希表存储的).
典型应用场景: 基于 Redis 实现分布式锁 (其实就是写了一个特殊的 key-value)
[补充]
: pexpire --> 也是用于设置 key 的过期时间, 但是单位是毫秒.
eg:
expire hello 3 #给hello设置过期时间为3s
pexpire hello1 50 #给hello1设置过期时间为50ms
(4) ttl
即: “time - to - live”
查看当前 key 的过期时间还剩多少, 返回值: 正常情况 --> 还剩的过期时间 ; 异常情况 --> -1表示没有关联过期时间, -2表示key不存在.
时间单位是 秒(s)
时间复杂度: O(1) (因为键值对是用哈希表存储的).
eg:
ttl hello #查看hello的过期时间还剩多少(秒)
(5) type
返回当前 key 对应的 value 的数据类型.
时间复杂度: O(1) (因为键值对是用哈希表存储的).
eg:
type key1 #返回key1对应value 的数据类型
[经典面试题: Redis 中 key 的过期策略是怎么实现的?]
一个 Redis 中可能同时存在很多很多 key. 这些 key 中可能有很大一部分都有过期时间. 此时, Redis 服务器如何知道哪些key已经过期要被删除, 哪些key还没过期?
直接遍历所有的 key? --> 显然是行不通的. 因为这样做效率非常低, 要消耗很多网络资源, 很可能会导致 Redis 阻塞.
Redis 的整体策略是: 定期删除 + 惰性删除
(1) 定期删除: 每次抽取一部分, 进行验证过期时间. (为什么只抽一部分? --> 要保证这个抽取检查的过程足够快!! 否则可能阻塞)
[!NOTE]
为啥这里对于定期删除的时间, 有明确的要求呢? --> 因为redis是单线程的程序, 如果扫描过期 key 消耗的时间太多了, 就可能导致正常处理请求命令就被阻塞住 (产生类似于执行 keys * 这样的效果).
(2) 惰性删除: 假设某个key已经到过期时间了, 但是暂时还没删它, key 仍然存在. 紧接着, 后面的一次访问, 正好用到了这个key, 此时就会触发 Redis 服务器删除 key 的操作, 同时再返回一个nil.
[经典面试题: 定时器的实现]
定时器: 在某个时间到达之后, 执行指定的任务.
(1) 基于 优先级队列/堆 实现
正常的队列是先进先出, 优先级队列则是按照指定的优先级出队 (这里的优先级是自定义的. 在 Redis 过期 key 的场景中, 过期时间越早, 就是优先级越高).
现在假定有很多 key 设置了过期时间. 就可以把这些 key 加入到一个优先级队列中, 指定优先级规则是过期时间早的, 先出队. 那么, 队首元素 就是最早的要过期的任务!!
此时定时器中只需要分配一个线程, 让这个线程去检查队首元素, 看它是否过期即可. 如果队首元素还没过期, 后续元素一定没过期.
[!NOTE]
注意: 在扫描线程检查队首元素过期时间的时候, 也不能检查的太频繁 --> 太频繁的话可能会出现忙等的问题. 此时做法就是可以根据当前时刻和队首元素的过期时间, 设置一个等待时间. 当时间差不多到了, 系统再去唤醒醒这个线程.
万一在线程休眠的时候来了一个新任务咋办? --> 在添加新任务的时候唤醒一下线程, 看看是否有要执行的任务.
(2) 基于 时间轮 实现
把时间划分成很多小段 (划分粒度看具体需求)
每个小段上都挂着一个链表, 链表的每个节点都代表一个要执行的任务.
假设需要添加一个 key, 这个 key 在 300ms 之后过期. 此时这个指针, 就会每隔固定的间隔 (这里是 100ms) 每次走到一个格子, 就会把这个格子上链表的任务尝试执行一下: 如果时间没到, 就跳过; 如果时间到了, 就执行.
[!NOTE]
对于时间轮来说,每个格子是多少时间,一共多少个格子,都是需要根据实际场景,灵活调配的~~