Redis系列(十二)scan Info Object等特殊命令集合
前言
在上一篇文章中,介绍了 Redis 的所有命令的基本含义及其用法。但是 Redis 的命令太多,导致上一篇文章只能简单的进行总结,而有一些命令是那么简单的话语总结不了的,因此在这里单独的进行讲解。
当然,这种复杂的命令,不属于线上常用数据结构内部,而是一些监控和 debug 用到的。
目录
SCAN
好吧,这个是线上用的。
对 Redis 的很多操作都是已知 key 而去操作或者查找 value, 那么当我们不知道 key 或者仅仅知道一部分,想找到对应的 key 应该怎么办呢?
keys
命令提供了根据正则来匹配 key 的能力,但是一般线上的 redis 是禁用掉这个 key 的。原因如下:
- keys 直接返回所有的 key, 万一数量太多,我们看不过来。
- 他会遍历所有的 key, 如果 reids 实例中的 key 数量太大,这个遍历的 O(n) 过程可能会导致服务器卡顿,从而影响对线上的服务。
2.8 之后版本的 redis 为我们提供了另一个批量扫描的,可控的遍历方法。也就是SCAN
命令。
它通过批量遍历的方式,来避免卡顿服务器。
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- cursor 这是游标,第一次填写 0 即可,之后每次请求带着上一次返回的游标,直到游标为 0 则遍历完成。
- pattern 模式匹配,同 keys 一样,它也提供了按照正则模式进行匹配的功能。
- count 一个数量,但是注意,这个数量并不是像 mysql 一样保证只返回这么多,而是遍历这么多,至于有多少满足条件的值,就看缘分了。即使 redis 返回了空列表,也不意味着遍历结束了,遍历结束的标志是 cursor=0.
- type 在 6.0 之后的版本,scan 加入了一个新的选项 type, 可以在扫描时只获取某个类型的 key, 但是这个 type 不区分所有的数据类型,比如 geohash, hyperloglog 等数据类型其实内部是使用 sorted set 等其他数据类型实现的,那么 scan 并不能区分它们,会把他们都当做 zset.
redis 127.0.0.1:6379> GEOADD geokey 0 0 value
(integer) 1
redis 127.0.0.1:6379> ZADD zkey 1000 value
(integer) 1
redis 127.0.0.1:6379> TYPE geokey
zset
redis 127.0.0.1:6379> TYPE zkey
zset
redis 127.0.0.1:6379> SCAN 0 TYPE zset
1) "0"
2) 1) "geokey"
2) "zkey"
scan 原理
这么好使的 scan 是怎么实现的呢?
设想一下,如果我们的服务器上的数据是一个不会变化的数组,是不是就简单了好多。我们只需要 从客户端给的游标开始遍历,获取 limit 个后,把当前的的下标作为游标返回给客户端即可。这样不仅简单还天然支持多个平行的 scan, 因为我们的服务时无状态的,状态都在游标中。
但是现实没有这么美好,我们都知道 redis 的 所有 key 存储在一个大字典中,这个字典的实现就是 redis 中的字典。
那么它是一维数组+二维链表。
游标就是一维数组上的槽,count 选项控制的也是遍历多少个槽。
每一次遍历,redis 在遍历 limit 个槽及其链接的链表之后,将结果返回,这也是为什么 scan 不保证每次 scan 返回数据量的原因,因为不是所有的槽上都有链表,而且链表上的元素也是有多有少的。
然而更麻烦的来了,字典是会有扩容以及缩容操作的,扩容及缩容都伴随着 rehash. rehash 会改变元素的槽位,也就是没有办法直接进行顺序遍历,否则就会造成重复遍历或者遗漏。
Redis 使用了高位进位假发来进行遍历,一顿花里胡哨的操作,可以保证遍历的槽位没有重复值。
花里胡哨的操作:本质上是找到扩容之后元素 rehas 的规律,之后通过高位进位假发来规避掉。 距离:我们当前需要遍历下标为二进制
110
的元素,但是不幸发生了 rehash. rehash 之后的元素会落到0110 或 1110
上,这时我们只需要调整指针,从 0110 开始遍历就能保证没有重复了。 想看特别详细的关于这个遍历的解释,可以查看这里:scan 的详细介绍
更麻烦的是,Redis 为了解决 rehash 大字典带来的卡顿,使用了渐进式的 rehash 过程,也就是说,有一段时间内,字典内部是有两张哈希表的。
对于这个情况,redis 会扫描两张表,然后将结果融合之后返回给客户端。
联想
前阵时间,我将这个思路应用到了具体的项目中,算是对 scan 思路的一个具体实践。
场景:我有一个服务 A, 程序里维护了千万级别的一个 list, 服务 B 每次启动前,需要从服务 A 中获取一段时间的列表,这个列表可大可小,可能没有值,也有可能是 600w(峰值).
600w 个对象的大小远远超出了 thrift 限制的 16M, 因此我没有办法一次请求拿到所有的值。
后来借鉴 scan 的实现方式,写了一个 scan 接口。
这个接口就是上面我提到的美好状况下的 scan 场景。服务 A 中的数组基本上是不太改变,只会增加的。同时真的就是一个简单的数组。
因此我只需要用 cursor=0 进行请求,然后向后遍历找到 limit 个符合条件的值,之后返回当前的下标作为下一次的 cursor.
当遍历完成之后,返回一个 cursor=0, 客户端就识趣的不再请求了。600w 数据 30s 也就请求完了,而且不会再受限于数据包大小。
其他 scan
scan 是一系列的命令,除了有用来遍历 key 的 scan 之外,还有遍历集合的sscan
. 遍历字典的hscan
, 遍历有序集合的zscan
等等,他们的实现原理基本上一样,功能也是类似的(其他 scan 没有 type 选项). 因为本质上上面讲的那几个数据类型,底层实现都是用到了字典的,那么就和 scan 没有太大的区别了。
INFO
INFO 命令格式化的输出 Redis 服务端的基本信息及一些统计信息。它的使用方式如下:
INFO [section]
. 其中 section 可以是以下值,代表了不同的模块的相关信息:
- server: Redis Server 的一些基础信息
- clients: 客户端连接相关信息
- memory: 内存使用信息
- persistence: RDB ,AOF 等持久化信息
- stats: 基础统计信息
- replication: 主从 DB 同步信息
- cpu: cpu 占用相关统计
- commandstats: 命令统计相关信息
- cluster: 集群信息
- keyspace: 数据库信息
- all: 返回所有 section 的信息
- default: 返回默认的 section 的信息
返回值是一个 string 的 list, 类似下面这样:
redis> INFO
# Server
redis_version:999.999.999
redis_git_sha1:3c968ff0
redis_git_dirty:0
redis_build_id:51089de051945df4
redis_mode:standalone
os:Linux 4.8.0-1-amd64 x86_64
arch_bits:64
# CPU
used_cpu_sys:2660.77
used_cpu_user:27170.64
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
# Cluster
cluster_enabled:0
# Keyspace
db0:keys=2305145,expires=4,avg_ttl=1221252880772
每个 section 以 #
开头,其他的值都是 field:value
的样子。
由于 action 非常多,而且每个 action 中的属性也非常多,因此这里就不一一介绍了。只挑几个比较有用的属性来看一下。全量的属性可以查看官网 https://redis.io/commands/info.
- redis 的每秒操作数 (operation per second) ```shell redis-cli info stats | grep ops
instantaneous_ops_per_sec:3518
* redis 的客户端连接数
```shell
redis-cli info clients | grep connect
# connected_clients:421
- redis 的内存占用 ```shell redis-cli info memory | grep human
used_memory_rss_human:576.36M
## MONITOR
monitor 命令对应的官网地址:[https://redis.io/commands/monitor](https://redis.io/commands/monitor)
它可以回放对应的 redis 服务器的所有命令,来帮助我们进行 debug.
![2020-01-17-17-18-27](http://img.couplecoders.tech/2020-01-17-17-18-27.png)
这里我启动了两个客户端,左边的客户端用`monitor`命令进行监控,右边的客户端进行正常的操作,可以看到所有的命令都会展示出来。
这个命令经常用于 debug. 比如新写了个功能,有 bug, 你想知道数据有没有被写到 redis. 此时你可以
```shell
redis-cli monitor | grep "your_key"
来进行不断的监听。
但是,注意:monitor 命令是有损耗的.
这里我直接取用了 Redis 官网的数据,在 Redis 服务进行正常服务时,吞吐量如下:
$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 101936.80 requests per second
PING_BULK: 102880.66 requests per second
SET: 95419.85 requests per second
GET: 104275.29 requests per second
INCR: 93283.58 requests per second
当对一个启动了 monitor 的服务器进行压测,吞吐量如下:
$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 58479.53 requests per second
PING_BULK: 59136.61 requests per second
SET: 41823.50 requests per second
GET: 45330.91 requests per second
INCR: 41771.09 requests per second
可以看到,吞吐量下降了 50%还多,如果你继续增加监听的客户端的数量,吞吐量还会继续下降。因此, 对于线上的压力大的 Redis 进行 monitor 操作要慎重
OBJECT
OBJECT 允许我们根据某个 KEY 去查看 redis 对象的内部信息。比如内部编码等。可以用来帮助我们进行 debug, 或者像官网上说的那样,用来做一个缓存的分级删除等。
它的使用格式如下:
OBJECT sub-command key
.
其中sub-command
可以是以下的几种:
- OBJECT REFCOUNT 查看对象的引用计数,主要是用于 debug.
- OBJECT ENCODING 查看对象内部存储的编码方法。
- OBJECT IDLETIME 查看对象的空转时长。`空转时长=当前时间-对象的最后一次访问时间`. 在内存淘汰策略为 LRU 时用这个
- OBJECT FREQ 查看对象的访问频率,在内存淘汰策略为 LFU 时候用到
- OBJECT HELP 帮助文档。
示例:
redis> lpush mylist "Hello World"
(integer) 4
redis> object refcount mylist
(integer) 1
redis> object encoding mylist
"ziplist"
redis> object idletime mylist
(integer) 10
参考文章
《Redis 的设计与实现(第二版)》
- (Head First 设计模式)学习笔记(3) --装饰者模式(StarBuzz咖啡店实例)
- 我的Js代码-按钮按下时判断是否选择了最后一行,给出提示
- (Head First 设计模式)学习笔记(2) --观察者模式(气象站实例)
- Spring Boot使用HandlerInterceptorAdapter和WebMvcConfigurerAdapter实现原始的登录验证
- 一条视频获C+融资 两个域名神助攻
- ExtJs与WCF交互:生成树
- JavaScript大略
- 加点的心得
- Markdown
- 介绍linux下利用编译bash设置root账号共用的权限审计设置
- 分享一例脚本发版和tomcat重启脚本
- 2018年小程序的红利趋势预测,或许你将成为下个富翁
- 分布式监控系统Zabbix-3.0.3-完整安装记录(5)-邮件报警部署
- label自定义的惨痛教训
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法