Redis的bitmap使用不当,我内存爆了
背景
最近发现Redis的内存持续暴涨, 涨的有点吓人,机器都快扛不住了,不得不进行Redis内存可视化分析,发现大量的String类型的大key
经分析,最近上线了页面UV的统计,那目前如何做的呢?
- 通过访客的IP地址来标识和追踪访客。当一个访问者首次访问网站时,服务器会记录其IP地址,并将其计算为一个UV。随后,如果同一IP地址再次访问网站,服务器将不会将其计算为一个UV。
- 将IP地址转换为整数,用位图(Bitmap)进行存储IP,实现UV的统计
这方案看上去没啥问题,也达到了去重的效果,统计也比较精确,内存占用率也低(bitmap优势就是内存占用率低),那为什么实际内存占用的这么夸张呢?我接着继续分析。
IP4
IP4介绍
目前的全球因特网所采用的协议族是TCP/IP协议族。IP是TCP/IP协议族中网络层的协议,是TCP/IP协议族的核心协议。IP协议定义了一种地址编码,称为IP地址,它是网络中网络段、网络设备接口、主机的编码,它并不是一种物理地址,而是逻辑地址,即地址是可以被分配、并且非固定、可修改的。
IPv4,是互联网协议(Internet Protocol,IP)的第四版,也是第一个被广泛使用,构成现今互联网技术的基石的协议。1981年 Jon Postel 在RFC791中定义了IP,IP可以运行在各种各样的底层网络上,比如端对端的串行数据链路、卫星链路等等。局域网中最常用的是以太网。
IPv4的下一个版本就是IPv6,IPv6正处在不断发展和完善的过程中,它在不久的将来将取代目前被广泛使用的IPv4。
ip4构成
IP地址有是一个32位的二进制数逻辑地址。因此,除了全0,拥有2的32次方-1个地址。全0地址用来表示一个无效的,未知的,或者不可用的目标。
为了方便使用,把这32位二进制数分成八位一组,被称为八位组(octet)。每个八位组书写时用点分十进制的格式标识。每个八位组取值为0000000011111111(二进制数),使用十进制数表示则值为0255。
二进制与十进制的转化非常简单,用二进制数的每一位乘以2的N次方,N是相应的位,从低位到高位以0次方开始,将二进制是1的每位结果相加得到的就是相应的十进制数。
IP地址分类
IP地址(0.0.0.0——255.255.255.254)分类:
A类:
0.0.0.0—127.255.255.255 (其中私有:10.0.0.0—10.255.255.255,保留:0.0.0.0,127.0.0.0—127.255.255.255)
B类:
128.0.0.1—191.255.255.254(其中私有:172.16.0.0—172.31.255.255,保留:169.254.0.0-169.254.255.255,191.255.255.255是广播地址,不能分配)
C类:
192.0.0.1—223.255.255.254(其中:私有:192.168.0.0—192.168.255.255)
D类:
224.0.0.1—239.255.255.254
E类:
240.0.0.1—255.255.255.254
什么是公网IP(外网IP)
公网IP就是除了保留IP地址以外的IP地址,可以与Internet上的其他计算机随意互相访问。我们通常所说的IP地址,其实就是指的公网IP。互联网上的每台计算机都有一个独立的IP地址,该IP地址唯一确定互联网上的一台计算机。
IP如何转为整数
把一个IPv4地址的每段可以看成是一个0-255的整数,先把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成一个长整数。
以10.0.3.193这个IP地址为例:
每段数字 | 相对应的二进制数 |
---|---|
10 | 00001010 |
0 | 00000000 |
3 | 00000011 |
193 | 11000001 |
组合起来即为:00001010 00000000 00000011 11000001,转换为十进制数就是:167773121,所以10.0.3.193这个IPv4地址转换为Int数字就是167773121。
得到数字 167773121,作为bitmap 的偏移量
BitMap
BitMap可以看下如何统计百万用户在线状态-bitmap这篇文章,有详细的介绍,这里就简单分析下:
BitMap 原本的含义是用一个比特位来映射某个元素的状态。由于一个比特位只能表示 0 和 1 两种状态,所以 BitMap 能映射的状态有限,但是使用比特位的优势是能大量的节省内存空间。
在 Redis 中,可以把 Bitmaps 想象成一个以比特位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。
位图不是实际的数据类型,而是在 String 类型上定义的一组面向位的操作,将其视为位向量。由于字符串是二进制安全 blob,其最大长度为 512 MB,因此它们适合设置最多 2^32 个不同位。
例子: 10.0.3.193 ****这个IP访问了页面page1
10.0.3.193 转换为数字167773121,167773121作为bitmap 的偏移量,值设置为1
setbit uv:page1 167773121 1
# 统计
内存分析
页面page1,第一次被10.0.3.193 访问,进行记录,偏移量是167773121
1Byte(Byte 字节) = 8Bit
167773121/8/1024/1024=20MB
一次就分配了20mb的内存空间,前面的空间就造成了浪费,使用都是后面的位
如果IP是224开头,比如:224.1.2.1,转为数字3758162433
3758162433/8/1024/1024=448MB
一次就分配448mb,这样的统计页面如果有上万个,我们的资源根本没法承受,想想都可怕
如何优化呢?分段统计
分段统计
IPv4地址是一个32位的二进制数,每8位作为一段,分为四段进行储存,比如:10.255.1.12分割,如图:
# 第一段
setbit uv:page1:seg1 10 1
# 第二段
setbit uv:page1:seg2 255 1
# 第三段
setbit uv:page1:seg3 1 1
# 第四段
setbit uv:page1:seg4 12 1
最大偏移量值是255位,四段占用内存:4*255/8/1024=0.12kb
假设10w个页面进行统计,10000*0.12kb=121mb ,最大内存也只占用121mb。统计的页面越多,效果也是明显。不过这里有个问题,都分段了,那如果统计这个页面的uv呢,没分段之前,我们可以
bitcount uv:page1
分段之后,
# 第一段
bitcount uv:page1:seg1
# 第二段
bitcount uv:page1:seg2
# 第三段
bitcount uv:page1:seg3
# 第四段
bitcount uv:page1:seg4
统计分段后的四个key,然后相加吗,明显不对,那怎么办呢?
# 第一段
setbit uv:page1:seg1 10 1
# 第二段
setbit uv:page1:seg2 255 1
# 第三段
setbit uv:page1:seg3 1 1
# 第四段
setbit uv:page1:seg4 12 1
# 记录UV,上面四个只要有一个返回0,说明是一个新的IP,那就加1
INCR uv:page1#统计uv
get uv:page1
使用Jedis客户端代码实现
public static void main(String[] args) {Jedis jedis = new Jedis("10.1.250.157", 6379);jedis.auth("google00");jedis.del("ip");//添加四个IP统计uv,有一个是重复的,访问页面page1List<String> ipList = new ArrayList<>();ipList.add("10.1.255.10");ipList.add("255.1.255.10");ipList.add("10.1.195.10");ipList.add("10.1.255.10");for (String ip : ipList) {String[] ips = ip.split("\.");boolean seg1 = jedis.setbit("uv:page1:seg1",Long.valueOf(ips[0]).longValue(),true);boolean seg2 = jedis.setbit("uv:page1:seg2",Long.valueOf(ips[1]).longValue(),true);boolean seg3 = jedis.setbit("uv:page1:seg3",Long.valueOf(ips[2]).longValue(),true);boolean seg4 = jedis.setbit("uv:page1:seg4",Long.valueOf(ips[3]).longValue(),true);if (seg1&&seg2&&seg3&seg4){System.out.println(ip+"已访问过");}else {jedis.incr("uv:page1");}}String uv = jedis.get("uv:page1");System.out.println("页面page1的UV为:"+uv);}
结果:
10.1.255.10已访问过
页面page1的UV为:3
小结
bitmap最大的优势是节约内存空间,但是在使用的时候,需要根据实际的场景分析,上面的例子,就是没考虑偏移量的浪费。好多时候,理论跟实际差距还是有的,多实践。
相关文章:

Redis的bitmap使用不当,我内存爆了
背景 最近发现Redis的内存持续暴涨, 涨的有点吓人,机器都快扛不住了,不得不进行Redis内存可视化分析,发现大量的String类型的大key 经分析,最近上线了页面UV的统计,那目前如何做的呢? 通过访…...
基于python的新闻爬虫
咱们这个任务啊,就是要从一个指定的网站上,抓取新闻内容,然后把它们整整齐齐地保存到本地。具体来说,就是要去光明网的板块里,瞅瞅里面的新闻,把它们一条条地保存下来。 首先,咱得有个网址&…...
C#基础题
值类型和引用类型之间的区别是什么? 值类型在内存中存储实际值,而引用类型存储对对象的引用。值类型在栈上分配内存,而引用类型在堆上分配内存。值类型是不可变的,而引用类型是可变的。值类型的大小是固定的,而引用类型…...

AI大语言模型学习笔记之三:协同深度学习的黑魔法 - GPU与Transformer模型
Transformer模型的崛起标志着人类在自然语言处理(NLP)和其他序列建模任务中取得了显著的突破性进展,而这一成就离不开GPU(图形处理单元)在深度学习中的高效率协同计算和处理。 Transformer模型是由Vaswani等人在2017年…...

c++阶梯之auto关键字与范围for
auto关键字(c11) 1. auto关键字的诞生背景 随着程序的逐渐复杂,程序代码中用到的类型也越来越复杂。譬如: 类型难以拼写;含义不明确容易出错。 比如下面一段代码: #include <string> #include &…...

第八篇:node模版引擎Handlebars及他的高级用法(动态参数)
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📘 引言: …...

css3 属性 backface-visibility 的实践应用
backface-visibility 是一个用于控制元素在面对屏幕不同方向时的可见性的CSS3特性。它有两个可能的值: visible:当元素不面向屏幕(即背面朝向用户)时,元素的内容是可以被看到的。hidden:当元素不面向屏幕…...

嵌入式学习第十七天
C语言小项目: 制作俄罗斯方块小游戏(全部) 主函数部分 #include <stdio.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <stdlib.h> #include <time.h> #include "b…...

使用Python的Turtle模块简单绘制烟花效果
import turtle import random# 初始化屏幕 screen turtle.Screen() screen.bgcolor("black") screen.title("烟花模拟")# 创建一个Turtle来绘制烟花 firework turtle.Turtle() firework.hideturtle() firework.speed(0) # 设置绘图速度为最快# 绘制烟花…...

数学建模-退火算法和遗传算法
退火算法和遗传算法 一.退火算法 退火算法Matlab程序如下: [W]xlsread(D:100个目标经度纬度);>> x[W(:,1)];>> y[W(:,2)];>> w[x y];;d1[70, 40];>> w[d1;w;d1]ww*pi/180;%角度化成弧度dzeros(102);%距离矩阵初始化for i1:101…...

Qt开源版 vs 商业版 详细比较!!!!
简单整理Qt开源版与商业版有哪些差别,仅供参考。 简单对比 开源版商业版许可证大部分采用对商业使用不友好的LGPLv3具备商业许可证保护代码专有许可证相关大部分模块使用LGPLv3和部分模块使用GPL组成仅第三方开源组件使用Qt的其他许可证Qt模块功能支持支持技术支持…...

华为云CodeArts Snap荣获信通院优秀大模型案例及两项荣誉证书
2024年1月25日,中国人工智能产业发展联盟智能化软件工程工作组(AI for Software Engineering,下文简称AI4SE)在京召开首届“AI4SE创新巡航”活动。在活动上,华为云大模型辅助系统测试代码生成荣获“2023AI4SE银弹优秀案…...
小程序的应用、页面、组件生命周期(超全版)
小程序生命周期 应用的生命周期 onLaunch: 初始化小程序完成时触发,且全局只触发一次; onShow: 小程序初始化完成(启动)或从后台切换到前台显示时触发; onHide: 小程序从前台切换到后台隐藏时触发(如切换…...

TCP四次握手
TCP 协议在关闭连接时,需要进行四次挥手的过程,主要是为了确保客户端和服务器都能正确地关闭连接。 # 执行流程 四次挥手的具体流程如下: 客户端发送 FIN 包:客户端发送一个 FIN 包,其中 FIN 标识位为 1,…...

EBC金融英国CEO:高波动性周期下,如何寻找市场的稳定性?
利率主导的市场,将在2024年延续。目前,固收市场对于降息的定价,正通过利率传导至不同资产中。尽管市场迫切利用通胀去佐证降息,但各国央行仍囿于通胀目标的政策桎梏。政策和市场预期的博弈将继续牵动市场脉搏,引发价格…...

C++ Web 编程
什么是 CGI? 公共网关接口(CGI),是一套标准,定义了信息是如何在 Web 服务器和客户端脚本之间进行交换的。CGI 规范目前是由 NCSA 维护的,NCSA 定义 CGI 如下:公共网关接口(CGI&…...

docker笔记整理
Docker 安装 添加yum源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 安装docker yum -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin 启动docker systemctl start docker 查看docker状态 s…...

什么是git,怎样下载安装?
简介: 应用场景: 应用场景:团队企业开发 作用: 安装: 1.网址:Git - Downloads 很卡很慢 2.可以选择镜像网站下载(推荐) CNPM Binaries Mirror...
Camille-学习笔记-测试流程和测试设计
## 测试用例学习路线 startmindmap * 测试用例 ** 黑盒测试方法论 *** 等价类 *** 边界值 *** 因果图 *** 判定表 *** 场景法 *** 基于模型的测试 ** 白盒测试方法论 ** 测试用例基础概念 ** 测试用例设计 ** 面试测试用例设计 ** 常用测试策略与测试手段 endmindmap **测试用…...

【Python笔记-设计模式】建造者模式
一、说明 又称生成器,是一种创建型设计模式,使其能够分步骤创建复杂对象。允许使用相同的创建代码生成不同类型和形式的对象。 (一) 解决问题 对象的创建问题:当一个对象的构建过程复杂,且部分构建过程相互独立时,可…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...