Redis 键空间迭代 Scan

news2024/12/25 9:34:46

引言

在平时线上Redis维护工作中,有时候需要从Redis实例成千上万的key中找出特定前缀的key列表来手动处理数据,可能是修改他的值,也可能是删除key。

Redis提供了一个简单暴力的指令keys用来列出所有满足特定正则字符串规则的key。

127.0.0.1:6379> set codehole1 a 
OK
127.0.0.1:6379> set codehole2 b 
OK
127.0.0.1:6379> set codehole3 c 
OK
127.0.0.1:6379> set code1hole a 
OK
127.0.0.1:6379> set code2hole b 
OK
127.0.0.1:6379> set code3hole b 
OK
127.0.0.1:6379> keys *
1) "codehole1"
2) "code3hole"
3) "codehole3"
4) "code2hole"
5) "codehole2"
6) "code1hole"
127.0.0.1:6379> keys codehole* 
1) "codehole1"
2) "codehole3"
3) "codehole2"
127.0.0.1:6379> keys code*hole 
1) "code3hole"
2) "code2hole"
3) "code1hole"

这个指令使用很简单,提供一个简单的正则字符串即可,但是有明显的两个缺点

  • 没有offset、limit参数,一次性吐出所有满足条件的key,万一实例中有几百w个key满足条件
  • keys算法是遍历算法,复杂度为O(n), 如果实例中有千万级以上的key,这个指令就会导致Redis服务卡顿,所有读写Redis的其他指令都会被延后甚至会超时报错。因为Redis是单线程程序,顺序执行所有指令,其他指令必须等到当前的keys指令执行完后才可以继续。

Redis为了解决这个问题,在2.8版本中引入了Scan指令,Scan相比keys具有以下特点:

  • 复杂度也是O(n),但是他是通过游标分步进行的,不会阻塞线程
  • 提供limit参数,可以控制每次返回结果的最大条数,limit只是一个hint(优化提示),返回的结果可多可少。
  • 同keys一样,他也可以提供模式匹配功能。
  • 服务器不需要为游标保存状态,游标唯一状态就是scan返回给客户端的游标整数
  • 返回的结果可能会有重复,需要客户端去重
  • 遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的。
  • 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零。

Scan基础使用

向Redis里插入10000条测试数据

import redis
client = redis.StrictRedis() 
for i in range(10000):
  client.set("key%d" % i, i)

目标找出以key99开头key列表。
scan参数提供了三个参数,分别是**cursor整数值;key的正则模式;遍历的limit hint**。第一次遍历时,cursor值为0,然后将返回结果中第一个整数值作为下一次遍历的cursor。一直遍历到返回的cursor值为0时结束。

127.0.0.1:6379> scan 0 match key99* count 1000 
1) "13976"
2) 1) "key9911" 
   2) "key9974" 
   3) "key9994"
   。。。。。。。
127.0.0.1:6379> scan 13976 match key99* count 1000 
。。。。
127.0.0.1:6379> scan 11687 match key99* count 1000 
1) "0" #返回的游标为0,表示遍历结束   

注意:虽然每次提供的limit是1000,但是返回的结果只有10个左右,因为这个limit不是限定返回结果的数量,而是限定服务器单次遍历的字典槽位数量(约等于)如果将limit设置为10,可能会发现返回的结果是空的,但是游标值不为0,意味着遍历还没结束。

字典的结构

在这里插入图片描述
在Redis中所有的key都存储在一个很大的字典中,这个字典的结构和Java中的HashMap一样,是数组+链表结构,第一维数组的大小总是2^n(n>=0),扩容一次数组大小空间加倍,也就是n++.

scan指令返回的游标就是第一维数组的位置索引,我们将这个位置索引称为槽(slot)。如果不考虑字典的扩容缩容,直接按数组下标挨个遍历即可。limit参数表示需要遍历的槽位数,之所以返回的结果可多可少,是因为不是所有的槽位上都会挂接链表,有些槽位可能是空的,还有些槽位挂接的链表上的元素可能有多个。每一次遍历都会将limit数量的槽位上挂接的所有链表元素进行模式匹配过滤后,一次性返回给客户端。

scan遍历顺序

scan的遍历顺序非常特别,他不是从第一维数组的第0位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。

普通加法和高位进位加法的区别
高位进位加法从左边加,进位往右边移动,同普通加法正好相反,但是最终他们都会遍历所有槽位并且没有重复。

假设当前二进制表示为000,则通过高位进位加法后是100(最高位加1,无进位)。在经过高位进位加是010(最高位加1,有进位,向右传递1)

字典扩容

Java中的HashMap有扩容的概念,当loadFactor达到阈值时,需要重新分配一个新的2倍大小的数组,然后将所有的元素全部rehash挂到新的数组下面。rehash就是将元素的hash值对数组长度进行取模运算,因为长度变了,所以每个元素挂接的槽位可能也发生了变化。又因为数组的长度是2^n次方,所以取模运算等价于位与操作。

位与操作(&):对于每一对对应的位,如果两个对应位都是 1,则结果为 1,否则为 0。

假设当前字典的数组长度由8位扩容到16位,那么3号槽位011将会被rehash到3号槽位和11号槽位,也就是说该槽位链表中大约有一半的元素还是3号槽位,其他元素会放到11号槽位,11这个数字的二进制是1011,也就是对3的二进制011增加了一个高位1.
在这里插入图片描述
更抽象一点说,假设开始槽位的二进制数是xxx,那么该槽位中的元素将被rehash到0xxx或者1xxx(xxx+8)中。如果字典长度由16位扩容到32位,那么对于二进制槽位xxxx中的元素将被rehash到0xxxx和1xxxx中。

对比扩容缩容前后的遍历顺序

在这里插入图片描述
观察这张图,发现采用高位进位加法的遍历顺序,rehash后的槽位在遍历顺序上是相邻的。

假设当前即将遍历110这个位置,那么扩容后,当前槽位上的所有元素对应的新槽位是0110,1110,也就是在槽位的二进制数增加一个高位0或者1.这时,我们可以直接从0110这个槽位开始往后继续遍历,0110槽位之前的所有槽位都是已经遍历过的,这样就可以避免扩容后对已经遍历过的槽位进行重复遍历。

在考虑缩容,假设当前即将遍历110这个位置,那么缩容后,当前槽位所有的元素对应的新槽位是10,也就是去掉槽位二进制最高位。这时,我们可以直接从10这个槽位继续向后遍历,10槽位之前的所有槽位都是已经遍历过的。这样就可以避免缩容的重复遍历。不过缩容还是不太一样,他会对图中010这个槽位上的元素进行重复遍历,因为缩容后10槽位的元素是010和110上挂接的元素的融合。

渐进式rehash

Java中的HashMap在扩容时会一次性将旧数组下挂接的元素全部转移到新数组下面。如果HashMap中元素特别多,线程就会出现卡顿现象,Redis为了解决这个问题,采用渐进式rehash。

他会同时保留旧数组和新数组,然后在定时任务中以及后续对hash的指令操作中渐渐的将旧数组中挂接的元素迁移到新数组上。这意味着要操作处于rehash中的字典,需要同时访问新旧两个数组结构,如果在旧数组下面找不到元素,还需要去新数组下面寻找。

scan也需要考虑这个问题,对于rehash中的字典,需要同时扫描新旧槽位,然后将结果融合后返回给客户端。

更多的scan指令

scan指令是一系列指令,除了可以遍历所有的key之外,还可以对指定的容器集合进行遍历。比如zscan遍历zset集合元素,hscan遍历hash字典中的元素、sscan遍历set集合中的元素。

他们的原理同scan都会类似,因为hash底层就是字典,set也是一个特殊的hash(所有的value都指向同一个元素),zset内部也使用了字典来存储所有的元素内容。

大key扫描

有时候会因为业务人员使用不当,在Redis实例中会形成很大的对象,比如一个很大的hash,一个很大的zset这都是经常出现的。这样的对象对Redis的集群数据迁移带来了很大的问题,因为在集群环境下,如果某个key太大,会导致迁移卡顿。另外,在内存分配上,如果一个key太大,那么当他需要扩容时,会一次性申请更大的一块内存,这也会导致卡顿。如果这个大key被删除,内存会一次性回收,卡顿现象再次发生。

平时的业务开发中,要尽量避免大key的产生

如果观察到Redis的内存大起大落,这极有可能是因为大key导致的,这时候就需要定位出具体是哪个key,进一步定位出具体的业务来源,然后改进相关业务代码设计。

如何定位大key

为了避免对线上Redis带来卡顿,需要用到scan指令,对于扫描出来的每一个key,使用type指令获得key的类型,然后使用相应数据结构的size或者len方法来得到他的大小,对于每一种类型,保留大小的前N名作为扫描结果展示出来。
上面这样的过程需要编写脚本,比较繁琐,可以使用如下指令进行扫描。

redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1

–bigkeys: 这是一个特殊的参数,用于在 Redis 数据库中扫描并报告最大的键。这个功能可以帮助识别可能占用过多内存的键。

-i 0.1: 这个参数设置 redis-cli 命令的采样间隔,单位是秒。这里设置为 0.1 秒,意味着 redis-cli 在执行 --bigkeys 操作时每 0.1 秒采样一次,以减少对 Redis 服务器性能的影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1829049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】实现学生管理系统(完整版)

💕💕💕大家好,这是作业侠系列之C实现学生管理系统,还是那句话,大家不想cv或者cv了跑不起来,三连后都可以来找我要源码,私信或评论留下你的邮箱即可。有任何问题有可以私聊我,大家觉得…

课时154:项目发布_手工发布_手工发布

1.2.3 手工发布 学习目标 这一节,我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 为了合理的演示生产环境的项目代码发布,同时又兼顾实际实验环境的资源,我们这里将 B主机和C主机 用一台VM主机来实现,A主机单…

牛客网刷题 | BC118 N个数之和

目前主要分为三个专栏,后续还会添加: 专栏如下: C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读! 初来乍到,如有错误请指出,感谢! 描述 输入数字N&#xf…

【LeetCode 动态规划】买卖股票的最佳时机问题合集

文章目录 1. 买卖股票的最佳时机含冷冻期 1. 买卖股票的最佳时机含冷冻期 题目链接&#x1f517; &#x1f34e;题目思路&#xff1a; &#x1f34e;题目代码&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int n prices.size();ve…

生产者消费者模型的同步与互斥:C++代码实现

文章目录 一、引言二、生产者消费者模型概述1、基本概念和核心思想2、生产者消费者模型的优点 三、消费者和生产者之间的同步与互斥四、代码实现1、事前准备2、环形队列的实现3、阻塞队列的实现4、两种实现方式的区别 一、引言 在现代计算机系统中&#xff0c;很多任务需要同时…

Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求

测试表现层的代码如何测试 加载测试的专用属性 首先写一个测试 假定我们进行测试的时候要加一些属性 要去修改一些属性 我们可以写一个只在本测试有效的测试 写在配置里 测试 打印输出 我们把配置文件里面的配置注释掉后 我们同样可以启动 package com.example.demo;impo…

专业是软件工程,现在好迷茫,感觉什么都没有学到,该怎么办?

学习软件工程可能会遇到迷茫和困惑的时期&#xff0c;这很正常&#xff0c;尤其是在学习初期。这里有一些建议&#xff0c;或许可以帮助你找到方向&#xff1a; 明确目标&#xff1a;思考你学习软件工程的目的是什么&#xff0c;是为了将来从事软件开发工作&#xff0c;还是对编…

LabVIEW与C#的区别及重新开发自动测试程序的可行性分析

LabVIEW和C#是两种广泛使用的编程语言&#xff0c;各自有不同的应用领域和特点。本文将详细比较LabVIEW与C#在自动测试程序开发中的区别&#xff0c;并分析将已完成的LabVIEW自动测试程序重新用C#开发的合理性。本文帮助评估这种转换的必要性和潜在影响。 LabVIEW与C#的区别 开…

Windows环境利用 OpenCV 中 CascadeClassifier 分类器识别人眼 c++

Windows环境中配置OpenCV 关于在Windows环境中配置opencv的说明&#xff0c;具体可以参考&#xff1a;VS2022 配置OpenCV开发环境详细教程。 CascadeClassifier 分类器 CascadeClassifier 是 OpenCV 库中的一个类&#xff0c;它用于实现一种快速的物体检测算法&#xff0c;称…

LSTM模型预测时间序列

长短期记忆模型(Long Short-Term Memory, LSTM)&#xff0c;是一种特殊的循环神经网络&#xff0c;能够学习长期依赖性。长短期记忆模型在各种各样的问题上表现非常出色&#xff0c;现在被广泛使用&#xff0c;例如&#xff0c;文本生成、机器翻译、语音识别、时序数据预测、生…

Matlab电话按键拨号器设计

前言 这篇文章是目前最详细的 Matlab 电话按键拨号器设计开源教程。如果您在做课程设计或实验时需要参考本文章&#xff0c;请注意避免与他人重复&#xff0c;小心撞车。博主做这个也是因为实验所需&#xff0c;我在这方面只是初学者&#xff0c;但实际上&#xff0c;从完全不…

Python | 中心极限定理介绍及实现

统计学是数据科学项目的重要组成部分。每当我们想从数据集的样本中对数据集的总体进行任何推断&#xff0c;从数据集中收集信息&#xff0c;或者对数据集的参数进行任何假设时&#xff0c;我们都会使用统计工具。 中心极限定理 定义&#xff1a;中心极限定理&#xff0c;通俗…

【Liunx】基础开发工具的使用介绍-- yum / vim / gcc / gdb / make

前言 本章将介绍Linux环境基础开发工具的安装及使用&#xff0c;在Linux下安装软件&#xff0c;编写代码&#xff0c;调试代码等操作。 目录 1. yum 工具的使用1.1 什么是软件包&#xff1a;1.2 如何下载软件&#xff1a;1.3 配置国内yum源&#xff1a; 2. vim编辑器2.1 vim的安…

NetSuite Saved Search 之 Filter By Summary

在某些业务场景中&#xff0c;用户需要一个TOP X的报表。例如&#xff0c;过去一段时间内&#xff0c;最多数量的事务处理类型。这就需要利用Saved Search中的Filter By Summary功能。 这在Criteria下的Summary页签里可以定义。其作用是对Result中Summary类型的结果进行过滤。也…

【论文速读,找找启发点】2024/6/16

ICME 2023 End-To-End Part-Level Action Parsing With Transformer 类似 DETR&#xff0c;通过 加 query的方式实现 端到端 ELAN: Enhancing Temporal Action Detection with Location Awareness 如何实现位置感知&#xff1f; > 重叠的卷积核&#xff1f; Do we really …

解决MacOS docker 拉取镜像慢的问题

docker官网&#xff1a;https://docker.p2hp.com/get-started/index.html 下载完成之后&#xff0c;拉取镜像速度慢&#xff0c;问题如下&#xff1a; 解决方法 配置阿里源&#xff1a;https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors在docker desktop里面设置…

代码随想录二刷DAY1~3

Day1 704 二分查找&#xff0c;简单 我也有自己写题解的能力了&#xff0c;而且思维很清晰&#xff1a; 找什么就在if里写什么。 class Solution {public: int search(vector<int>& nums, int target) { int l0,rnums.size()-1; while(l<r){ …

基于C++、MFC和Windows套接字实现的简单聊天室程序开发

一、一个简单的聊天室程序 该程序由服务器端和客户端两个项目组成&#xff0c;这两个项目均基于对话框的程序。服务器端项目负责管理客户端的上线、离线状态&#xff0c;以及转发客户端发送的信息。客户端项目则负责向服务器发送信息&#xff0c;并接收来自服务器的信息&#…

不一样的SYSTEM APP(SYSTEM flag和system_prop区别)

1.问题引入 在Android开发中, 1)Framework中PackageManager扫包后,会把app归类为SYSTEM, SYSTEM_EXT, PRIVILEGED 类别. 2)同样的, SeAndroid也会把APP归类程platform_app, system_app, untrusted_app(甚至还有其他,mediaprovider,gmscore_app). flag SYSTEM和system_app我们…

IDEA配置JavaFX

一、下载SDK &#x1f4ce;javafx-sdk-18.zip 二、配置依赖包 三、复制一个javafx代码 import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.VBox; import javafx.scene.shape.Line; import javafx.stage.Stage;public class Java…