第3章 小功能大用处-Bitmaps、HyperLogLog、GEO

1.Bitmaps
1.1数据结构模型
现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,例
如“big”字符串是由3个字节组成,但实际在计算机存储时将其用二进制表
示,“big”分别对应的ASCII码分别是98、105、103,对应的二进制分别是
01100010、01101001和01100111,如下图所示。
在这里插入图片描述
Redis提供了Bitmaps这个“数据结构”可以实现对位的操作。把数据结构加上引号主要因为:

  • Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符
    串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的
    每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量
    在这里插入图片描述
    1.2命令
    1.2.1设置值:setbit key offset value
    时间复杂度:O(1)
    设置键的第offset个位的值(从0算起)
    假设现在有20个用户,userid=0,5,11,15,19的用户对网站进行了访问,那么当前Bitmaps初始化结果如下图所示
    在这里插入图片描述
127.0.0.1:6379> setbit unique:users:2016-04-05 0 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 5 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 11 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 15 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 19 1
(integer) 0

在第一次初始化Bitmaps时,假如偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis的阻塞。
1.2.2.获取值:gitbit key offset//获取键的第offset位的值(从0开始算)
时间复杂度:O(1)
操作获取id=8的用户是否在2016-04-05这天访问过,返回0说明没有访问:

127.0.0.1:6379> getbit unique:users:2016-04-05 8
(integer) 0

由于offset=1000000根本就不存在,所以返回结果也是0:

127.0.0.1:6379> getbit unique:users:2016-04-05 1000000
(integer) 0

1.2.3.获取Bitmaps指定范围值为1的个数:bitcount [start][end]
时间复杂度:O(N)
下面操作计算2016-04-05这天的独立访问用户数量:

127.0.0.1:6379> bitcount unique:users:2016-04-05
(integer) 5

[start]和[end]代表起始和结束字节数,下面操作计算用户id在第1个字节到第3个字节之间的独立访问用户数,对应的用户id是11,15,19。

127.0.0.1:6379> bitcount unique:users:2016-04-05 1 3
(integer) 3

1.2.4Bitmaps间的运算:bitop op destkey key[key…]
时间复杂度:O(N)
bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并
集)、not(非)、xor(异或)操作并将结果保存在destkey中。假设2016-
04-04访问网站的userid=1,2,5,9,如下图所示。
在这里插入图片描述
and(交集)
下面操作计算出2016-04-04和2016-04-03两天都访问过网站的用户数量

127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique: users:2016-04-03
unique:users:2016-04-03
(integer) 2
127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03
(integer) 2

在这里插入图片描述
or(并集)
如果想算出2016-04-04和2016-04-03任意一天都访问过网站的用户数量
(例如月活跃就是类似这种),可以使用or求并集,具体命令如下:

127.0.0.1:6379> bitop or unique:users:or:2016-04-04_03 unique:
users:2016-04-03 unique:users:2016-04-03
(integer) 2
127.0.0.1:6379> bitcount unique:users:or:2016-04-04_03
(integer) 6

not(非)

127.0.0.1:6379> bitop not unique:users:not:2016-04-04 unique:users:2016-04-04
(integer) 2
127.0.0.1:6379> bitcount unique:users:not:2016-04-04
(integer) 12

因为unique:users:2016-04-04共有2字节,取非只取2字节内的。
xor(异或)

127.0.0.1:6379> bitop xor unique:users:xor:2016-04-03_04 unique:users:2016-04-03 unique:users:2016-04-04
(integer) 2
127.0.0.1:6379> bitcount unique:users:xor:2016-04-03_04
(integer) 4

1.2.5计算Bitmaps中第一个值为targetBit的偏移量
bitpos key targetBit [start] [end]
时间复杂度:O(N)
下面操作计算2016-04-04当前访问网站的最小用户id:

127.0.0.1:6379> bitpos unique:users:2016-04-04 1
(integer) 1

除此之外,bitops有两个选项[start]和[end],分别代表起始字节和结束字
节,例如计算第0个字节到第1个字节之间,第一个值为0的偏移量

127.0.0.1:6379> bitpos unique:users:2016-04-04 0 0 1
(integer) 0

1.3Bitmaps分析
假设网站有1亿用户,每天独立访问的用户有5千万,如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表3-3。
在这里插入图片描述
很明显,这种情况下使用Bitmaps能节省很多的内存空间,尤其是随着时间推移节省的内存还是非常可观的。
但Bitmaps并不是万金油,假如该网站每天的独立访问用户很少,例如只有10万(大量的僵尸用户),那么两者的对比如表3-5所示,很显然,这时候使用Bitmaps就不太合适了,因为基本上大部分位都是0。
在这里插入图片描述
2.HyperLogLog
HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。HyperLogLog提供了3个命令:pfadd、pfcount、pfmerge。
例如2016-03-06的访问用户是uuid-1、uuid-2、uuid-3、uuid-4,2016-03-05的访问用户是uuid-4、uuid-5、uuid-6、uuid-7。
在这里插入图片描述
2.1添加
pfadd key element [element …] //pfadd用于向HyperLogLog添加元素,如果添加成功返回1:
时间复杂度:O(1)

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
(integer) 1
127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
(integer) 0
127.0.0.1:6379> pfcount 2016_03_06:unique:ids
(integer) 4

2.2计算独立用户数
pfcount key [key …] //pfcount用于计算一个或多个HyperLogLog的独立总数
时间复杂度:O(1),使用单个键调用时,平均常数时间非常小。O(N),其中N是键的个数,当调用多个键时,常数次数要大得多。

127.0.0.1:6379> pfadd 2016_03_05:unique:ids "uuid-4" "uuid-5" "uuid-6" "uuid-7"
(integer) 1
127.0.0.1:6379> pfcount 2016_03_05:unique:ids 2016_03_06:unique:ids
(integer) 7

2.3合并
pfmerge destkey sourcekey [sourcekey …] //pfmerge求多个HyperLogLog的并集并赋值给destkey
时间复杂度:O(N),合并N个hyperloglog,但是常数时间很高。
例如要计算2016年3月5日和3月6日的访问独立用户数,可以看到最终独立用户数是7:

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
(integer) 1
127.0.0.1:6379> pfadd 2016_03_05:unique:ids "uuid-4" "uuid-5" "uuid-6" "uuid-7"
(integer) 1
127.0.0.1:6379> pfmerge 2016_03_05_06:unique:ids 2016_03_05:unique:ids
2016_03_06:unique:ids
OK
127.0.0.1:6379> pfcount 2016_03_05_06:unique:ids
(integer) 7

2.4.100万个用户放到HyperLogLog和set中的内存对比:
2.4.1.HyperLogLog:
下面使用shell脚本向HyperLogLog插入100万个id,插入前记录一下redis-cli端执行info memory:

127.0.0.1:6379> info memory
# Memory
used_memory:835144
used_memory_human:815.57K
......

在shell窗口执行下面shell命令

...向2016_05_01:unique:ids插入100万个用户,每次插入1000条:
elements=""
key="2016_05_01:unique:ids"
for i in `seq 1 1000000`
227
doelements="${elements} uuid-"${i}if [[ $((i%1000)) == 0 ]];thenredis-cli  -a paassword pfadd ${key} ${elements}elements=""fi
done

当上述代码执行完成后,可以看到内存只增加了15K左右:

127.0.0.1:6379> info memory
# Memory
used_memory:850616
used_memory_human:830.68K
......

但是,同时可以看到pfcount的执行结果并不是100万:

127.0.0.1:6379> pfcount 2016_05_01:unique:ids
(integer) 1009838

2.4.2.set
可以对100万个uuid使用集合类型进行测试,代码如下:

elements=""
key="2016_05_01:unique:ids:set"
for i in `seq 1 1000000`
doelements="${elements} "${i}if [[ $((i%1000)) == 0 ]];thenredis-cli -a password sadd ${key} ${elements}elements=""fi
done

当上述代码执行完成后,可以看到内存使用了84MB:

127.0.0.1:6379> info memory
# Memory
used_memory:88702680
used_memory_human:84.59M
......

但独立用户数为100万:

127.0.0.1:6379> scard 2016_05_01:unique:ids:set
(integer) 1000000

表3-6列出了使用集合类型和HperLogLog统计百万级用户的占用空间对比。
在这里插入图片描述
可以看到,HyperLogLog内存占用量小得惊人,但是用如此小空间来估算如此巨大的数据,必然不是100%的正确,其中一定存在误差率。Redis官方给出的数字是0.81%的失误率。
HyperLogLog内存占用量非常小,但是存在错误率,开发者在进行数据结构选型时只需要确认如下两条即可:

  • 只为了计算独立总数,不需要获取单条数据。
  • 可以容忍一定误差率,毕竟HyperLogLog在内存的占用量上有很大的优势
    2.5GEO
    Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,对于需要实现这些功能的开发者来说是一大福音。
    2.5.1增加地理位置信息
    geoadd key [NX|XX] [CH] longitude latitude member [longitude latitude member …]
  • XX: 只更新已经存在的元素。永远不要添加元素。
  • NX: 不要更新已经存在的元素。总是添加新元素。
  • XX和NX选项互斥。
  • CH: 将返回值从添加的新元素数修改为更改的元素总数(CH是changed的缩写)。更改的元素是添加的新元素和坐标已更新的现有元素。因此,在命令行中指定的具有与过去相同分数的元素不会被计算在内。注意:通常,GEOADD的返回值只计算添加的新元素的数量。
  • longitude、latitude、member分别是该地理位置的经度、纬度、成员,
    时间复杂度:O(log(N)) ,对于添加的每一项,其中N是排序集中元素的个数。
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing 117.12 39.08 tianjin
(integer) 2

2.5.2.获取地理位置信息
geopos key member [member …]
时间复杂度:O(1)

127.0.0.1:6379> geopos cities:locations tianjin
1) 1) "117.12000042200088501"
2) "39.0800000535766543"

2.5.3.获取两个地理位置的距离。
geodist key member1 member2 [m|km|ft|mi] //[米|公里|英里|尺]
时间复杂度:O(1)

127.0.0.1:6379> geodist cities:locations tianjin beijing km
"89.2061"

2.5.4.获取指定位置范围内的地理信息位置集合
georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist][withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist][withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadius和georadiusbymember两个命令的作用是一样的,都是以一个地理位置为中心算出指定半径内的其他地理信息位置,不同的是georadius命令的中心位置给出了具体的经纬度,georadiusbymember只需给出成员即可。其中radiusm|km|ft|mi是必需参数,指定了半径(带单位),这两个命令有很多可选参数,如下所示:

  • withcoord:返回结果中包含经纬度。
  • withdist:返回结果中包含离中心节点位置的距离。
  • withhash:返回结果中包含geohash,有关geohash后面介绍。
  • COUNT count:指定返回结果的数量。
  • asc|desc:返回结果按照离中心节点的距离做升序或者降序。
  • store key:将返回结果的地理位置信息保存到指定键。
  • storedist key:将返回结果离中心节点的距离保存到指定键。
    时间复杂度:O(N+log(M)) N为圆心和半径划定的圆形区域边界框内的元素个数,M为索引内的项数。
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"2) "190.4424"3) 1) "13.36138933897018433"2) "38.11555639549629859"
2) 1) "Catania"2) "56.4413"3) 1) "15.08726745843887329"2) "37.50266842333162032"
127.0.0.1:6379> georadiusbymember cities:locations beijing 150 km
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"

2.5.5.获取geohash
geohash key member [member …]
时间复杂度:O(1)

127.0.0.1:6379> geohash cities:locations beijing
1) "wx4ww02w070"
127.0.0.1:6379> type cities:locations
zset

geohash有如下特点:

  • GEO的数据类型为zset,Redis将所有地理位置信息的geohash存放在zset中。
  • 字符串越长,表示的位置更精确,表3-8给出了字符串长度对应的精度,例如geohash长度为9时,精度在2米左右
    在这里插入图片描述
  • 两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配算法实现相关的命令。
  • geohash编码和经纬度是可以相互转换的。
  • Redis正是使用有序集合并结合geohash的特性实现了GEO的若干命令。
    2.5.6.删除地理位置信息
    zrem key member
    GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除

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

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

相关文章

22.智能指针(下)

标题 五、引用计数智能指针5.1 共享引用计数智能指针共享数据5.2 使用Box定义三个共享链表5.3 使用Rc代替Box5.4 引用计数增加实验 六、RefCell和内部可变性模式6.1 通过RefCell在运行时检查借用规则6.2 内部可变性:不可变值的可变借用1)内部可变性的用例…

论文《Universal Graph Convolutional Networks》笔记

【UGCN】论文提出一个基本问题,即是否不同的网络结构属性应该采用不同的传播机制。通过实验发现,对于完全同配性、完全异配性和随机性的网络,1-hop、2-hop和k-nearest neighbor(kNN)邻居分别更适合作为信息传播的邻域。…

maven的安装以及配置

前言: Maven是一个强大的构建自动化工具,主要用于Java项目。它解决了软件开发中的两个方面: 构建和依赖管理:Maven通过在项目对象模型(POM)文件中指定依赖关系,简化了项目构建和依赖管理的过程…

【笔记】事务隔离级别以及MVCC解决幻读

事务提交可能碰到的问题: (1)脏读:事务1对数据进行修改但还没提交,事务2读取修改后的数据,之后事务1执行错误,回滚了,此时事务2的数据是错误的脏数据。 (2)不…

数学建模系列(2/4):建模入门

目录 引言 1. 如何开始数学建模 1.1 选择和描述问题 1.2 提出基本假设 1.3 确定模型类型 2. 建模的数学基础 2.1 线性代数基础 矩阵运算 线性方程组的解法 2.2 微分方程基础 常微分方程 偏微分方程 2.3 统计与概率基础 描述性统计 概率基础 3. 模型的求解方法 …

将Vite添加到您现有的Web应用程序

Vite(发音为“veet”)是一个新的JavaScript绑定器。它包括电池,几乎不需要任何配置即可使用,并包括大量配置选项。哦——而且速度很快。速度快得令人难以置信。 本文将介绍将现有项目转换为Vite的过程。我们将介绍别名、填充webp…

基于CSDN的Markdown文本编辑器的博客界面优化 | HTML | 文本标签 | 图像标签 | 个人主页引导

🙋大家好!我是毛毛张! 🌈个人首页: 神马都会亿点点的毛毛张 今天毛毛张分享的内容是如何在CSDN的Markdown编辑器中实现上图的效果,如果觉得能帮助到你的话就点击个人主页点点关注吧❗ 文章目录 1.前言2.基础知识3.字…

(南京观海微电子)——DC-DC和LDO的原理及应用区别

LDO: 低压差线性稳压器,故名思意为线性的稳压器,仅能使用在降压应用中,也就是输出电压必需小于输入电压。 优点:稳定性好,负载响应快,输出纹波小。 缺点: 效率低,输入输出的电压…

[C++][设计模式][模板方法]详细讲解

目录 1.动机2.理解1.设计流程对比1.结构化软件设计流程2.面向对象软件设计流程 2.早绑定与晚绑定 3.模式定义4.要点总结5.代码感受1.代码一 -- 结构化1.lib.cpp2.app.cpp 2.代码二 -- 面向对象1.lib.cpp2.app.cpp 1.动机 在软件构建过程中,对于某一项任务&#xff…

Nginx负载均衡之Memcached缓存模块

Nginx 的 ngx_http_memcached_module 模块本身并没有提供缓存功能,它只是一个将用户请求转发到 Memcached 服务器的代理模块。 在以 Memcached 服务器为缓存应用的方案中,Memcached 作为内容缓存的存储服务器,用户通过 URL 为 Memcac…

古文字识别笔记

前置知识 部件:大部分的汉字是由若干组笔画结构拼合而成的,这些相对独立的笔画结构称为「部件」。 部件是大于基本笔画(例如:点、横、撇、捺等)而小于或等同于 偏旁 的结构单位。 例如「测」字有三个部件:…

视觉新纪元:解码LED显示屏的视角、可视角、最佳视角的最终奥秘

在璀璨夺目的LED显示屏世界里,每一个绚烂画面的背后,都离不开三个关键概念:视角、可视角与最佳视角。这些术语不仅是衡量显示效果的重要标尺,也是连接观众与精彩内容的桥梁。让我们一起走进这场视觉盛宴,探索那些让LED…

【C++】——二叉搜索树(详解)

一 二叉搜索树概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: ✨若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 ✨若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 …

Go 与 Java 字符编码选择:UTF-8 与 UTF-16 的较量

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

淦!在外包开发的三年给整废了,备战两个月终拿到Android阿里字节哈啰offer总结,阿里P6+这回稳了!

面试时候就感觉不靠谱,因为面试地点是位于近江附近的望江国际里面的温州银行,面试前网上搜了广电运通的信息,说是国企,所以我就硬着头皮接下 offer 了,没想到面试 Android 结果做的 C,而且也是驻场开发。 …

RocketMQ 和 Kafka 关于消息队列的推拉模式是怎么做的?

引言:在当今的大数据和分布式系统中,消息队列扮演着至关重要的角色,它们作为系统之间通信和数据传输的媒介,为各种场景下的数据流动提供了可靠的基础设施支持。在消息队列的设计中,推拉模式是两种常见的消息传递机制&a…

02 Shell编程之条件语句(补充实验部分)

1、双分支if语句的补充(实验部分) 例如,要编写一个连通性测试脚本,通过位置参数来提供目标主机地址,然后根据ping检测结果给出相应的提示 (能ping通的,回馈一个信息:该服务器是开启…

浔川AI社宣布正式开创“浔川AI助手”——浔川AI社

这是浔川AI社的标志。 2024.6.22晚8点35分宣布,浔川AI社正式开创“浔川AI助手” 全面发展。 据浔川AI社报道称‘“浔川AI助手”内容包含全部,写作、聊天......都有。’ 让我们敬请期待!

【JAVA】精致的五角星

输出的这幅图像中,一颗精致的金色五角星跃然于深红色背景之上,绽放出迷人的光彩。 要绘画这颗五角星,首先要了解五角星的构造和角度问题。我们可以分为内五边形,和外五边形。内五边形从他的中心到每个外点,连接起来&am…

ECharts词云图(案例一)+配置项详解

ECharts词云图(案例一)配置项详解 ECharts 是一款由百度团队开发的基于 JavaScript 的开源可视化图表库,它提供了丰富的图表类型,包括常见的折线图、柱状图、饼图等,以及一些较为特殊的图表,如词云图。从版…