
《Redis开发与运维》阅读笔记:键管理之遍历键、数据库管理
发布日期:2021-05-07 15:15:44
浏览次数:26
分类:原创文章
本文共 4637 字,大约阅读时间需要 15 分钟。
目录
遍历键
- Redis提供了两个命令遍历所有的键,分别是keys和scan
全量遍历键:keys pattern
- 支持pattern匹配
- 例如向一个空的Redis插入4个字符串类型的键值对。
127.0.0.1:6379> dbsize(integer) 0127.0.0.1:6379> mset hello world redis best jedis best hill highOK
- 如果要获取所有的键,可以使用keys pattern命令:
127.0.0.1:6379> keys *1) "hill"2) "jedis"3) "redis"4) "hello"
- 上面为了遍历所有的键,pattern直接使用星号,这是因为pattern使用的是glob风格的通配符:
*代表匹配任意字符 · 代表匹配一个字符 [] 代表匹配部分字符例如 [1 , 3] 代表匹配 1 , 3 , [1-10] 代表匹配 1 到 10 的任意数字。\x 用来做转义,例如要匹配星号、问号需要进行转义。
- 下面操作匹配以j,r开头,紧跟edis字符串的所有键:
127.0.0.1:6379> keys [j,r]edis1) "jedis"2) "redis"
- 例如下面操作会匹配到hello和hill这两个键:
127.0.0.1:6379> keys hll*1) "hill"2) "hello"
- 当需要遍历所有键时(例如检测过期或闲置时间、寻找大对象等),keys是一个很有帮助的命令
- 例如想删除所有以video字符串开头的键,可以执行如下操作:
redis-cli keys video* | xargs redis-cli del
- Redis的单线程架构,如果包含大量的键,执行keys命令很可能会造成Redis阻塞,一般建议不要在生产环境下使用keys命令。
- 确实有遍历键的需求该怎么办,可以在以下三种情况使用:
- 在一个不对外提供服务的Redis从节点上执行,这样不会阻塞到客户端的请求,但是会影响到主从复制
- 如果确认键值总数确实比较少,可以执行该命令
- 使用scan命令渐进式的遍历所有键,可以有效防止阻塞。
渐进式遍历
- 从2.8版本后,提供了新命令scan,能有效的解决keys命令存在的问题:
- 和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题
- 每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。
- Redis存储键值对实际使用的是hashtable的数据结构,其简化模型如所示
- 那么每次执行scan,可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。scan的使用方法如下:
scan cursor [match pattern] [count number]
- 参数说明:
cursor 是必需参数,实际上 cursor 是一个游标,第一次遍历从 0 开始,每 次 scan 遍历完都会返回当前游标的值,直到游标值为 0 ,表示遍历结束。match pattern 是可选参数,它的作用的是做模式的匹配,这点和keys 的 模式匹配很像。count number 是可选参数,它的作用是表明每次要遍历的键个数,默认 值是 10 ,此参数可以适当增大。
- 现有一个Redis有26个键(英文26个字母),现在要遍历所有的键,使用scan命令效果的操作如下。
- 第一次执行scan0,返回结果分为两个部分:
- 第一个部分6就是下次scan需要的cursor
- 第二个部分是10个键:
127.0.0.1:6379> scan 01) "6"2) 1) "w"2) "i"3) "e"4) "x"5) "j"6) "q"7) "y"8) "u"9) "b"10) "o"
- 使用新的cursor="6",执行scan6:
127.0.0.1:6379> scan 61) "11"2) 1) "h"2) "n"3) "m"4) "t"5) "c"6) "d"7) "g"8) "p"9) "z"10) "a"
- 这次得到的cursor="11",继续执行scan11得到结果cursor变为0,说明所有的键已经被遍历过了:
127.0.0.1:6379> scan 111) "0"2) 1) "s"2) "f"3) "r"4) "v"5) "k"6) "l"
- 除了scan以外还提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似
- 下面以sscan为例子进行说明,当前集合有两种类型的元素
- 例如分别以old:user和new:user开头,先需要将old:user开头的元素全部删除,可以参考如下伪代码:
String key = "myset";// 定义patternString 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; }}
- 如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:
- 新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键
数据库管理
- Redis提供了几个面向Redis数据库的操作,它们分别是dbsize、select、flushdb/flushall命令
切换数据库:select dbIndex
- 与关系型数据库用字符来区分不同数据库名不同,Redis只是用数字作为多个数据库的实现。Redis默认配置中是有16个数据库:
databases 16
- 假设databases=16,select0操作将切换到第一个数据库,select15选择最后一个数据库,但是0号数据库和15号数据库之间的数据没有任何关联,甚至可以存在相同的键:
127.0.0.1:6379> set hello world #默认进到0号数据库OK127.0.0.1:6379> get hello"world"127.0.0.1:6379> select 15 #切换到15号数据库OK127.0.0.1:6379[15]> get hello #因为15号数据库和0号数据库是隔离的,所以get hello为空(nil)
- 如下图更加生动地表现出上述操作过程,当使用redis-cli-h{ip}-p{port}连接Redis时,默认使用的就是0号数据库,当选择其他数据库时,会有[index]的前缀标识,其中index就是数据库的索引下标。
- 废弃一个实例Redis多个数据库功能的三点原因:
- Redis是单线程的。如果使用多个数据库,那么这些数据库仍然是使用一个CPU,彼此之间还是会受到影响的。
- 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难
- 假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定位问题非常的困难。
- 部分Redis的客户端根本就不支持这种方式。即使支持,在开发的时候来回切换数字形式的数据库,很容易弄乱。
- 如果要使用多个数据库功能,可以在一台机器上部署多个Redis实例,彼此用端口来做区分
- 因为现代计算机或者服务器通常是有多个CPU的。这样既保证了业务之间不会受到影响,又合理地使用了CPU资源。
flushdb/flushall
- flushdb只清除当前数据库,flushall会清除所有数据库。
- 例如当前0号数据库有四个键值对、1号数据库有三个键值对:
127.0.0.1:6379> dbsize(integer) 4127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> dbsize(integer) 3
- 如果在0号数据库执行flushdb,1号数据库的数据依然还在:
127.0.0.1:6379> flushdbOK127.0.0.1:6379> dbsize(integer) 0127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> dbsize(integer) 3
- 在任意数据库执行flushall会将所有数据库清除:
127.0.0.1:6379> flushallOK127.0.0.1:6379> dbsize(integer) 0127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> dbsize(integer) 0
- 注意事项:
- flushdb/flushall命令会将所有数据清除,一旦误操作后果不堪设想,rename-command(后面介绍)配置规避这个问题,以及如何在误操作后快速恢复数据。
- 如果当前数据库键值数量比较多,flushdb/flushall存在阻塞Redis的可能性。
API的理解和使用总结
- Redis提供5种数据结构,每种数据结构都有多种内部编码实现。
- 纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。
- 由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。
- 批量操作(例如mget、mset、hmset等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。
- 了解每个命令的时间复杂度在开发中至关重要,例如在使用keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考虑数据规模对于Redis的影响。
- persist命令可以删除任意类型键的过期时间,但是set命令也会删除字符串类型键的过期时间,这在开发时容易被忽视。
- move、dump+restore、migrate是Redis发展过程中三种迁移键的方式,其中move命令基本废弃,migrate命令用原子性的方式实现了dump+restore,并且支持批量操作,是Redis Cluster实现水平扩容的重要工具。
- scan命令可以解决keys命令可能带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。
【注】:参考《Redis开发与运维》
发表评论
最新留言
关注你微信了!
[***.104.42.241]2025年03月25日 21时21分46秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Mac OS X 下 su 命令提示 sorry 的解决方法
2019-03-04
vue-router 缓存路由组件对象
2019-03-04
移动端 触摸事件和mousedown、mouseup、click事件之间的关系
2019-03-04
js中事件捕获和事件冒泡(事件流)
2019-03-04
js的各种数据类型判断(in、hasOwnProperty)
2019-03-04
严格模式、混杂模式与怪异模式
2019-03-04
一篇文章带你搞定 Java 中字符流的基本操作(Write / Read)
2019-03-04
HTML 和 CSS 简单实现注册页面
2019-03-04
(Java)让枚举实现一个接口
2019-03-04
XML 解析学习
2019-03-04
验证码的简单实现
2019-03-04
解决 vscode 窗口故障
2019-03-04
JSP 入门学习
2019-03-04
JSP,EL 和 JSTL 一篇文章就够了
2019-03-04
(延迟初始化)Lazy 初始化
2019-03-04
(Java 剑指 offer)二维数组中的查找
2019-03-04
(SpringMVC)springMVC.xml 和 web.xml
2019-03-04
Oracle 学习一篇文章就够了(珍藏版)
2019-03-04
一篇文章带你搞定 Oracle 的体系结构
2019-03-04
Oracle 单行函数
2019-03-04