Redis学习其一
文章目录
- 1.NoSQL概述
- 1.1概述
- 1.2Nosql的四大分类
- 2.Redis入门
- 2.1概述
- 2.2基础知识
- 2.2.1基础命令/语法
- 2.2.2Redis为什么单线程还这么快
- 2.3性能测试
- 3.五大数据类型
- 3.1Redis-key
- 3.2String(字符串)
- 3.3List(列表)
- 3.4Set(集合)
- 3.5Hash(哈希)
- 3.6Zset(有序集合)
- 4.三种特殊数据类型
- 4.0知识补充
- 4.0.1geohash
- 4.0.2HyperLogLog的误差原因
- 4.0.3bitmap的底层实现原理
- 4.1Geospatial(地理位置)
- 4.2Hyperloglog(基数统计)
- 4.3BitMaps(位图)
1.NoSQL概述
1.1概述
-
什么是 NoSQL:非关系型数据库统称,数据存储格式灵活,不依赖固定表结构,适应海量数据、高并发等场景,包含键值、文档、列族、图等类型数据库 。
-
NoSQL 特点:
-
方便扩展(数据之间没有关系,很好扩展!)
-
大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!
细粒度缓存是指缓存操作可以精确到更小的数据单元。不像一些粗粒度缓存,比如把整个页面、一大段业务数据作为缓存单元。Redis 能对单条记录、单个对象、某个字段等进行缓存 。比如电商系统中,可单独缓存某个商品的详细信息,而不是缓存整个商品列表。
-
数据类型是多样型的!(不需要事先设计数据库,随取随用)
-
-
为什么使用 NoSQL:应对传统关系型数据库在大规模数据存储(如日志、物联网数据)、高并发访问(如秒杀、直播弹幕)、数据结构灵活多变(如社交动态)、分布式部署等场景的局限性,补充关系型数据库,与之一同构建混合架构满足多样需求 。
1.2Nosql的四大分类
-
键值(Key-Value)数据库
- 特点:以键值对形式存储数据,键是唯一标识,值为任意数据(字符串、二进制等),操作简单高效。
- 适用场景:缓存、会话存储、计数器等简单数据。
- 代表产品:MemCache、Redis(支持更复杂结构)、Riak。
-
文档(Document)数据库
-
特点:数据以文档(如 JSON、BSON)为单位存储,文档内可包含嵌套结构,支持按文档内容查询。
-
适用场景:内容管理系统(CMS)、电商商品信息(属性多样且易变)、日志存储。
-
代表产品:MongoDB、CouchDB、RavenDB。
MongoDB(掌握):MongoDB是RDBMS和NoSQL的中间产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
-
-
列族(Column-Family)数据库
- 特点:数据按列族(Column Family)组织,列族内包含多个列,适合查询某一列族的所有数据,而非单条记录。
- 适用场景:海量数据存储与分析(如日志分析、用户行为统计)。
- 代表产品:HBase、Cassandra、Amazon DynamoDB。
-
图(Graph)数据库
- 特点:以节点(Node)和边(Edge)存储数据,专注于处理实体间的复杂关系(如社交网络中的 “好友关系”、知识图谱中的关联)。
- 适用场景:社交网络、推荐系统、路径分析。
- 代表产品:Neo4j、JanusGraph、OrientDB。
2.Redis入门
2.1概述
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务。
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能该干什么?
- 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
- 高效率、用于高速缓冲
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(eg:浏览量)
特性
-
多样的数据类型(像字符串、哈希、列表等常用类型,让它能适配不同业务)
-
持久化(RDB 和 AOF 两种方式保障数据不丢)
-
集群
-
事务
2.2基础知识
2.2.1基础命令/语法
-
redis默认有16个数据库。默认使用的第0个;16个数据库为:DB 0~DB 15
默认使用DB 0 ,可以使用select n
切换到DB n,dbsize
可以查看当前数据库的大小,与key数量相关。 -
不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
-
基础命令
-
将键值对移动到指定数据库。
move 键名 n
-
判断键是否存在。
EXISTS 键名
-
删除键值对。
del 键名。返回的是删除个数。
-
设置键值对的过期时间。
EXPIRE 键名 num。单位是秒
-
查看key的过期剩余时间。
关于
TTL
命令Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:
- 当前key没有设置过期时间,所以会返回-1.
- 当前key有设置过期时间,而且key已经过期,所以会返回-2.
- 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
ttl 键名
-
keys *
:查看当前数据库中所有的key。flushdb
:清空当前数据库中的键值对。flushall
:清空所有数据库的键值对。
-
2.2.2Redis为什么单线程还这么快
Redis是单线程的,Redis是基于内存操作的。
所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。
那么为什么Redis的速度如此快呢,性能这么高呢?QPS达到10W+
QPS 即 Queries-per-second,是一个衡量系统性能的重要指标,意为 **每秒查询率** ,表示系统在一秒内能够处理的查询请求数量 。QPS 的计算方法相对简单,通常是通过统计一段时间内的查询请求总数,再除以这段时间的秒数来得到。例如,在 10 秒内系统总共处理了 1000 个查询请求,那么 QPS = 1000 ÷ 10 = 100 。
Redis为什么单线程还这么快?
- 误区1:高性能的服务器一定是多线程的?
- 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
2.3性能测试
Redis 性能测试是通过同时执行多个命令实现的。**redis-benchmark:**Redis官方提供的性能测试工具,参数选项如下:
进入redis的安装目录,输入相关命令即可
简单测试:
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
3.五大数据类型
3.1Redis-key
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
下面学习的命令:
-
exists key
:判断键是否存在 -
del key
:删除键值对 -
move key db
:将键值对移动到指定数据库 -
expire key second
:设置键值对的过期时间 -
type key
:查看value的数据类型 -
关于重命名
RENAME
和RENAMENX
RENAME key newkey
修改 key 的名称RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
特性 | RENAME | RENAMENX |
---|---|---|
覆盖已存在键 | ✅ 强制覆盖 | ❌ 仅在新键不存在时执行 |
返回值 | 始终成功(除非源键不存在) | 成功返回 1 ,失败返回 0 |
应用场景 | 需要确保重命名完成时 | 需要避免覆盖现有数据时 |
3.2String(字符串)
命令 | 描述 | 示例 |
---|---|---|
APPEND key value | 向指定的key的value后追加字符串 | 127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world” |
DECR/INCR key | 将指定key的value数值进行-1/+1(仅对于数字) | 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20 |
INCRBY/DECRBY key n | 按指定的步长对数值进行加减 | 127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15 |
INCRBYFLOAT key n | 为数值加上浮点型数值 | 127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2” |
STRLEN key | 获取key保存值的字符串长度 | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11 |
GETRANGE key start end | 按起止位置获取字符串(闭区间,起止位置都取) | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl” |
SETRANGE key offset value | 用指定的value 替换key中 offset开始的值 | 127.0.0.1:6379> SETRANGE msg 2 hello (integer) 7 127.0.0.1:6379> get msg “tehello” |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 | 127.0.0.1:6379> GETSET msg test “hello world” |
SETNX key value | 仅当key不存在时进行set | 127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1 |
SETEX key seconds value | set 键值对并设置过期时间 | 127.0.0.1:6379> setex name 10 root OK 127.0.0.1:6379> get name (nil) |
MSET key1 value1 [key2 value2..] | 批量set键值对 | 127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK |
MSETNX key1 value1 [key2 value2..] | 批量设置键值对,仅当参数中所有的key都不存在时执行 | 127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0 |
MGET key1 [key2..] | 批量获取多个key保存的值 | 127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3” |
PSETEX key milliseconds value | 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间, |
如何存储对象呢
对象set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象 值为 json字符来保存一个对象!这里的key是一个巧妙的设计: user:{id}:{filed},为此设计在Redis中是完全OK了!
String类似的使用场景:value除了是字符串还可以是数字,用途举例:
- 计数器
- 统计多单位的数量:uid:123666:follow 0
- 粉丝数
- 对象存储缓存
INCRBYFLOAT的精度问题
>127.0.0.1:6379[2]> set aage 2.0
OK
>127.0.0.1:6379[2]> incrbyfloat aage 5.3
"7.29999999999999982"
浮点数精度问题是计算机底层二进制表示的局限性导致的,并非 Redis 的 bug。这是所有编程语言和系统(包括 Python、Java、JavaScript 等)都会存在的共性问题。计算机使用二进制存储和处理数据,但部分十进制小数无法用二进制精确表示。
3.3List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
可以经过规则定义将其变为队列、栈、双端队列等
命令 | 描述 |
---|---|
LPUSH/RPUSH key value1[value2..] | 从左边/右边向列表中PUSH值(一个或者多个)。 |
LRANGE key start end | 获取list 起止元素==(索引从左往右 递增)== |
LPUSHX/RPUSHX key value | 向已存在的列名中push值(一个或者多个) |
`LINSERT key BEFORE | AFTER pivot value` |
LLEN key | 查看列表长度 |
LINDEX key index | 通过索引获取列表元素 |
LSET key index value | 通过索引为元素设值 |
LPOP/RPOP key | 从最左边/最右边移除值 并返回 |
RPOPLPUSH source destination | 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 |
LTRIM key start end | 通过下标截取指定范围内的列表 |
LREM key count value | 《下面详解》 |
BLPOP/BRPOP key1[key2] timout | 移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
BRPOPLPUSH source destination timeout | 和RPOPLPUSH 功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
LREM key count value
- 功能:从列表
key
中删除 最多count
个值等于value
的元素。 - 参数
count
:控制删除的方向和数量count > 0
:从列表头部开始,正向删除最多count
个值为value
的元素。count < 0
:从列表尾部开始,反向删除最多|count|
个值为value
的元素。count = 0
:删除所有值为value
的元素(不考虑方向)。
127.0.0.1:6379[2]> lrange list1 0 91) "1"2) "1"3) "1"4) "1"5) "4"6) "3"7) "2"8) "1"9) "d"
10) "b"
127.0.0.1:6379[2]> lrem list1 3 1
(integer) 3
127.0.0.1:6379[2]> lrange list1 0 6
1) "1"
2) "4"
3) "3"
4) "2"
5) "1"
6) "d"
7) "b"
BLPOP/BRPOP key1[key2] timout示例
--------------客户端1----------
127.0.0.1:6379[2]> blpop list3 20
(nil)
(20.06s)
127.0.0.1:6379[2]> blpop list3 20
1) "list3"
2) "2"
(7.92s)
--------------客户端2----------
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> lpush list3 2
(integer) 1
3.4Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令 | 描述 |
---|---|
SADD key member1[member2..] | 向集合中无序增加一个/多个成员 |
SCARD key | 获取集合的成员数 |
SMEMBERS key | 返回集合中所有的成员 |
SISMEMBER key member | 查询member元素是否是集合的成员,结果是无序的 |
SRANDMEMBER key [count] | 随机返回集合中count个成员,count缺省值为1 |
SPOP key [count] | 随机移除并返回集合中count个成员,count缺省值为1 |
SMOVE source destination member | 将source集合的成员member移动到destination集合 |
SREM key member1[member2..] | 移除集合中一个/多个成员 |
SDIFF key1[key2..] | 返回所有集合的差集 key1- key2 - … |
SDIFFSTORE destination key1[key2..] | 在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢! |
SINTER key1 [key2..] | 返回所有集合的交集 |
SINTERSTORE destination key1[key2..] | 在SINTER的基础上,存储结果到集合中。覆盖 |
SUNION key1 [key2..] | 返回所有集合的并集 |
SUNIONSTORE destination key1 [key2..] | 在SUNION的基础上,存储结果到及和张。覆盖 |
SSCAN KEY cursor [MATCH pattern] [COUNT count] | 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分 |
SRANDMEMBER key [count]
# 随机返回 1 个元素
SRANDMEMBER fruits # 可能返回 "apple" 或其他元素# 随机返回 2 个不重复元素
SRANDMEMBER fruits 2 # 可能返回 ["cherry", "date"]# 随机返回 3 个元素(允许重复)这里的重复是指抽样重复,而非元素重复
SRANDMEMBER fruits -3 # 可能返回 ["apple", "apple", "elderberry"]#set1一共3个元素,则只会返回3个
127.0.0.1:6379[2]> srandmember set1 5
1) "1"
2) "2"
3) "3"
SSCAN KEY cursor [MATCH pattern] [COUNT count]
- 功能:迭代集合
key
中的元素,每次返回部分结果。 - 参数
cursor
:游标值,首次调用时为0
,后续使用上次返回的游标继续迭代。MATCH pattern
(可选):只返回与模式匹配的元素(支持 glob 模式,如user*
)。COUNT
(可选):每次迭代返回的近似元素数量(默认值为10
)。
假设集合有 100 个元素:
bash
# 第一次调用
SSCAN set1 0 COUNT 5
1) "234" # 新游标,非0表示迭代未结束
2) 1) "1"2) "2"3) "3"4) "4"5) "5"
# 第二次调用(使用新游标234)
SSCAN set1 234 COUNT 5
1) "0" # 游标为0,迭代结束
2) 1) "6"2) "7"... # 剩余元素
3.5Hash(哈希)
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。
命令 | 描述 |
---|---|
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0 |
HMSET key field1 value1 [field2 value2..] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
HGET key field value | 获取存储在哈希表中指定字段的值 |
HMGET key field1 [field2..] | 获取所有给定字段的值 |
HGETALL key | 获取在哈希表key 的所有字段和值 |
HKEYS key | 获取哈希表key中所有的字段 |
HLEN key | 获取哈希表中字段的数量 |
HVALS key | 获取哈希表中所有值 |
HDEL key field1 [field2..] | 删除哈希表key中一个/多个field字段 |
HINCRBY key field n | 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 |
HINCRBYFLOAT key field n | 为哈希表 key 中的指定字段的浮点数值加上增量 n。 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对。 |
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!
3.6Zset(有序集合)
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
命令 | 描述 |
---|---|
ZADD key score member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间score的成员数 |
ZINCRBY key n member | 有序集合中对指定成员的分数加上增量 n |
ZSCORE key member | 返回有序集中,成员的分数值 |
ZRANK key member | 根据排序(按分数score从小到大的顺序)返回有序集合中指定成员的索引 |
ZRANGE key start end | 用于按分数(score)从小到大的顺序获取指定范围内的元素。 |
ZRANGEBYLEX key min max | 通过字典区间返回有序集合的成员 |
ZRANGEBYSCORE key min max | 通过分数返回有序集合指定区间内的成员==-inf 和 +inf分别表示最小最大值,只支持开区间()== |
ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
ZREM key member1 [member2..] | 移除有序集合中一个/多个成员 |
ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 |
ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE key start end | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
ZREVRANGEBYSCORRE key max min | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
ZREVRANGEBYLEX key max min | 返回有序集中指定字典区间内的成员,按字典顺序倒序 |
ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZINTERSTORE destination numkeys key1 [key2 ..] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score |
ZUNIONSTORE destination numkeys key1 [key2..] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
ZSCAN key cursor [MATCH pattern\] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
ZRANGEBYLEX key min max
- 功能:返回有序集合
key
中,字符串值在[min, max]
范围内的元素(按字典序排列)。 - 参数
min
和max
:
字符串边界值,指定范围的最小值和最大值。必须使用特殊前缀(见下文)。LIMIT offset count
(可选):
用于分页,跳过offset
个元素后返回count
个元素。
边界值规则
min
和 max
必须以特殊前缀开头,用于指定边界类型:
[
:包含该值(闭区间)。
例如:[apple
表示包含"apple"
。(
:不包含该值(开区间)。
例如:(apple
表示不包含"apple"
。-
:负无穷(表示最小值)。
例如:-
等价于[-∞
。+
:正无穷(表示最大值)。
例如:+
等价于[+∞
。
如果zset中元素的 分数(score)不同,ZRANGEBYLEX
的行为会变得不可预测,甚至可能返回空结果。该命令的设计前提是:所有元素的分数必须相同。
ZADD fruits 0 "apple" 0 "banana" 0 "cherry" 0 "date" 0 "elderberry"
#获取所有元素(按字典序)
ZRANGEBYLEX fruits - +
1) "apple"
2) "banana"
3) "cherry"
4) "date"
5) "elderberry"ZRANGEBYLEX fruits [b (c # 包含 "b" 开头,不包含 "c" 开头
1) "banana"ZRANGEBYLEX fruits [apple [date
1) "apple"
2) "banana"
3) "cherry"
4) "date"ZRANGEBYLEX fruits - + LIMIT 1 2 # 跳过1个元素,取2个
1) "banana"
2) "cherry"
4.三种特殊数据类型
4.0知识补充
4.0.1geohash
GeoHash 是一种将二维地理坐标(经度、纬度)编码为一维字符串的算法。
以北京天安门(116.4039, 39.9151)为例:
- 经度编码:116.4039 → 二进制串
1101001011
- 纬度编码:39.9151 → 二进制串
1011100011
- 合并:交替合并为
11100111010000111111
- Base32 转换:每 5 位一组 →
wx4g0
特点
-
前缀相似性
-
相近的地理位置通常具有相同的前缀,前缀越长精度越高。
-
例如:
wx4g0
(天安门)和wx4g1
(附近区域)
-
-
精度控制
-
字符串长度决定精度,每增加一位精度约提高 10 倍。
-
长度 5:约 5km × 5km
-
长度 8:约 19m × 19m
-
-
高效查询:可通过前缀快速筛选附近区域,适合数据库索引。
-
边界问题:相邻区域可能编码差异大(如网格边界两侧)。
4.0.2HyperLogLog的误差原因
HyperLogLog 之所以存在误差,源于其核心设计思路 ——基于概率统计的近似计数算法,而非通过存储所有元素实现精确计数。这种误差是为了换取 “极低内存占用” 而做出的权衡,其本质与算法的数学原理直接相关。
核心原理:用 “随机事件的概率” 估算基数
HyperLogLog 的目标是统计一个集合的基数(即不重复元素的数量,如 “独立访客数 UV”),但它不存储任何实际元素,而是通过记录随机哈希值的分布特征来反推基数。具体来说:
- 哈希映射
首先,将集合中的每个元素通过哈希函数(如 MurmurHash)转换为一个固定长度的随机二进制串(例如 64 位)。
哈希的特性保证:不同元素的哈希值近似 “随机且均匀分布”,且相同元素的哈希值唯一。 - 统计 “前导零” 的最大长度
对于每个哈希后的二进制串,统计其开头连续的 0 的个数(称为 “前导零长度”)。例如:- 二进制串
1010...
的前导零长度为 0(第一个位是 1); - 二进制串
0010...
的前导零长度为 2(前两位是 0)。
算法会记录所有元素中最大的前导零长度(记为max_zero
)。
- 二进制串
- 通过概率公式反推基数
根据概率论中的 “伯努利试验” 模型:- 对于均匀分布的随机二进制串,“前导零长度为 k” 的概率是
1/(2^(k+1))
(即连续 k 个 0 后接 1 的概率)。 - 当观测到最大前导零长度为
max_zero
时,可通过公式基数 ≈ 常数 × 2^max_zero
估算集合的总基数(实际公式更复杂,涉及多个桶的平均和偏差修正)。
- 对于均匀分布的随机二进制串,“前导零长度为 k” 的概率是
总结:误差是 “空间换精度” 的必然结果。
HyperLogLog 的误差本质是用概率统计的 “近似值” 替代精确计数,从而实现 “用固定 12KB 内存统计任意大小集合基数” 的惊人效率。这种误差是可控的(约 1%),对于无需绝对精确的场景(如 UV 统计、访问量估算)完全可接受,而这正是它的设计目标 —— 在 “精度” 和 “资源消耗” 之间找到最优平衡。
4.0.3bitmap的底层实现原理
Redis 中的 Bitmaps(位图)底层是通过字符串(String)数据结构实现的,本质上是一串连续的二进制位(bit)序列,依托字符串的动态扩容机制和高效存储特性,实现了对位级数据的紧凑存储和快速操作。
底层核心实现
-
基于字符串的二进制存储
- Redis 的字符串底层以字节数组(
char[]
)形式存储数据,每个字节包含 8 个二进制位。一个char占一个字节 - 位图的每个 “位(bit)” 直接映射到字符串字节数组中的某一位,例如:
- 位图的偏移量
offset=0
对应字符串第 0 个字节的第 0 位(最低位); - 偏移量
offset=8
对应第 1 个字节的第 0 位,以此类推。
- 位图的偏移量
这种映射关系让位图无需额外的数据结构,直接复用字符串的存储逻辑,节省了内存开销。
- Redis 的字符串底层以字节数组(
-
动态扩容机制
- 当使用
SETBIT
命令设置某个偏移量的位时,如果该偏移量超出当前字符串的长度,Redis 会自动扩容字符串:- 计算所需的总字节数(
offset // 8 + 1
); - 新增的字节会被初始化为 0(即所有位默认值为 0)。
- 计算所需的总字节数(
- 例如:设置
offset=100
时,需要100//8 + 1 = 13
个字节,字符串会自动扩容到 13 字节,前 12 字节未设置的位均为 0。
- 当使用
局限性
- 偏移量限制:虽然 Redis 支持的最大偏移量为
2^63 - 1
,但过大的偏移量(如10^18
)会导致字符串扩容到巨大尺寸,消耗大量内存; - 非稀疏存储:即使只设置了一个大偏移量的位(如
offset=1000000
),Redis 仍会为前面的 1000000 位分配内存(全部为 0),因此适合存储密集型数据(大部分位有值),不适合稀疏数据。
4.1Geospatial(地理位置)
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
- 坐标有效性:Redis 会自动验证经纬度的有效性,超出范围的坐标会导致命令失败。
- 精度:Redis 使用 52 位有符号整数存储坐标,精度约为 50 厘米。
- 重复添加:如果
member
已存在,新坐标会覆盖旧坐标。 - 底层实现:
GEOADD
实际将数据存储为有序集合(Sorted Set)。
有效经纬度
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
关于GEORADIUS的参数
通过
georadius
就可以完成 附近的人功能
命令 | 描述 |
---|---|
geoadd key longitud(经度) latitude(纬度) member [..] | 将具体经纬度的坐标存入一个有序集合 |
geopos key member [member..] | 获取集合中的一个/多个成员坐标 |
geodist key member1 member2 [unit] | 返回两个给定位置之间的距离。默认以米作为单位。 |
`georadius key longitude latitude radius m | km |
GEORADIUSBYMEMBER key member radius... | 功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。 |
geohash key member1 [member2..] | 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。 |
geoadd key longitud(经度) latitude(纬度) member […]
- key:存储地理空间数据的键名。
- longitude:地理位置的经度(有效范围:-180 到 180 度)。
- latitude:地理位置的纬度(有效范围:-85.05112878 到 85.05112878 度)。
- member:该地理位置的名称或标识。
# 添加单个位置:北京天安门
GEOADD cities 116.4039 39.9151 "tiananmen"# 添加多个位置:上海和广州
GEOADD cities 121.4737 31.2304 "shanghai" 113.2649 23.1291 "guangzhou"
GEORADIUS key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- WITHCOORD:返回成员的经纬度。
- WITHDIST:返回成员到中心点的距离。
- WITHHASH:返回成员的 geohash 整数编码。
- COUNT count:限制返回结果数量。
- ASC|DESC:按距离升序 / 降序排列。
- STORE key:将结果存储为有序集合(仅存储成员)。
- STOREDIST key:将结果存储为有序集合(成员分数为距离)。
# 添加测试数据
GEOADD cities 116.4039 39.9151 "tiananmen" 121.4737 31.2304 "shanghai" 113.2649 23.1291 "guangzhou"# 查询天安门1000公里内的城市(返回距离和坐标)
GEORADIUS cities 116.4039 39.9151 1000 km WITHCOORD WITHDIST# 查询距离最近的2个城市
GEORADIUS cities 116.4039 39.9151 2000 km COUNT 2 ASC
4.2Hyperloglog(基数统计)
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
其底层使用string数据类型
什么是基数?
数据集中不重复的元素的个数。
应用场景:
网页的访问量(UV):一个用户多次访问,也只能算作一个人。
传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。
如果允许容错,那么一定可以使用Hyperloglog !
如果不允许容错,就使用set或者自己的数据类型即可 !
命令 | 描述 |
---|---|
PFADD key element1 [elememt2..] | 添加指定元素到 HyperLogLog 中 |
PFCOUNT key [key] | 返回给定 HyperLogLog 的基数估算值。 |
PFMERGE destkey sourcekey [sourcekey..] | 将多个 HyperLogLog 合并为一个 HyperLogLog |
4.3BitMaps(位图)
使用位存储,信息状态只有 0 和 1
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
应用场景
签到统计、状态统计
命令 | 描述 |
---|---|
setbit key offset value | 为指定key的offset位设置值。偏移量(offset)是从 0 开始的。 |
getbit key offset | 获取offset位的值 |
bitcount key [start end] | 统计字符串被设置为1的bit数,也可以指定统计范围按字节 |
bitop operration destkey key[key..] | 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 |
BITPOS key bit [start] [end] | 返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位 |
------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1 不设置默认 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0
(integer) 0-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4----------------bitop-------------
# 周一:用户1和用户3访问
SETBIT visitors:monday 1 1
SETBIT visitors:monday 3 1# 周二:用户1和用户2访问
SETBIT visitors:tuesday 1 1
SETBIT visitors:tuesday 2 1# 计算两天都访问的用户(交集)
BITOP AND visitors:both_days visitors:monday visitors:tuesday# 结果:visitors:both_days 中只有偏移量1为1(用户1两天都访问了)
GETBIT visitors:both_days 1 # 返回 1
GETBIT visitors:both_days 2 # 返回 0
GETBIT visitors:both_days 3 # 返回 0------------bitpos-----------
# 设置位图:第0位=0,第1位=1,第2位=0,第3位=1
SETBIT bitmap 1 1
SETBIT bitmap 3 1# 查找第一个值为 1 的位的偏移量
BITPOS bitmap 1 # 返回 1(第1位是第一个值为 1 的位)# 设置一个较长的位图(前8位:01010000,后8位:00001000)
SETBIT long_bitmap 1 1
SETBIT long_bitmap 3 1
SETBIT long_bitmap 13 1# 仅在第2个字节(偏移量8-15)中查找值为 1 的位
BITPOS long_bitmap 1 1 1 # 返回 13(第2个字节中的第5位)
bitop operration destkey key[key…]
- operation位运算类型,支持以下四种:
AND
:按位与(所有位都为 1 时结果为 1,否则为 0)OR
:按位或(任何一位为 1 时结果为 1,否则为 0)XOR
:按位异或(两位不同时结果为 1,否则为 0)NOT
:按位取反(单操作数,0 变 1,1 变 0)