Redis-2_数据类型
概览
存储结构
数据结构
Redis的存储相当于一个大的全局hash表,这也是Redis为什么查询快的一个原因。hash表有一个hash数组组成,我们称之为hash桶,hash桶中存储entry,entry包含key和value,value的类型包括:string、list、hash、set、zset。
hash冲突
使用hash表意为着就会有hash冲突的情况,Redis采用链式hash的方式解决hash冲突
rehash
为了使 rehash 操作更高效,Redis 默认使用了两个全局哈希表:哈希表 1 和哈希 表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空 间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:
给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
释放哈希表 1 的空间。
第二部涉及到数据拷贝,如果一次性把哈希表1的数据都迁移完,会造成Redis线程阻塞,无法服务其他请求。为了避免这个问题,Redis采用了渐进式rehash的方式。
简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求 时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝 到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。如下图所示:
rehash触发条件
装载因子 = 已存储键值对数量 / 哈希表大小
装载因子≥1,同时,哈希表被允许进行 rehash(redis执行RDB和AOF重写的时候,不允许rehash);
装载因子≥5。
渐进式rehash
当收到新的写/修改请求,会将对应entry复制
没有新得写/修改请求,采用定时任务的方式执行,每次执行时间不超过1ms
数据类型
底层数据类型
sds
SDS是"simple dynamic string"的缩写。 redis中所有场景中出现的字符串,基本都是由SDS来实现的
len:已使用的长度
alloc:已分配的长度
flag:用于存储编码信息和 SDS 类型标志
buf:存放的字符数组
扩容:
为减少修改字符串带来的内存重分配次数,sds采用了“一次管够”的策略:
若修改之后sds长度小于1MB, Redis 采用的是“预分配额外空间”策略,多分配原有len长度的空间
假如修改前sds长度是10k,修改后是20k,则扩展后sds的长度为30k
若修改之后sds长度大于等于1MB(预分配长度),则额外多1MB空间(减少大字符串翻倍扩展浪费内存空间)
假如修改前sds长度是2MB,修改后是5MB,则扩展后sds的长度为6MB
缩容:
如果一个大的sds被显著缩短,新分配的内存确实会比原来小很多,多余的内存部分由内存分配器(jemalloc)管理。内存分配器可能不会立即将这部分内存返回给操作系统,而是保留在其内部池中,以便快速响应后续的内存请求
ziplist
压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同 的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的 偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段 的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查 找,此时的复杂度是 O(N) 。
entry结构:
压缩列表之所以能节省内存,就在于它是用一系列连续的 entry 保存数据。每个 entry 的 元数据包括下面几部分。
prev_len,表示前一个 entry 的长度。prev_len 有两种取值情况:1 字节或 5 字节。 取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数 值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上 一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。
len:表示自身长度,4 字节;
encoding:表示编码方式,1 字节;
content:保存实际数据。
quicklist
quicklist 结合了双向链表和压缩列表(ziplist)的优点,以提高内存效率并保持良好的性能。一个 quicklist 包含多个 quicklist 节点,每个节点内部使用一个 ziplist 来存储实际的元素。
quicklist 访问元素时间复杂度取决于元素在列表中的位置。最坏情况下,需要遍历整个Quicklist的节点链表,然后还可能需要在找到的ziplist中进行线性搜索。因此,最坏的时间复杂度是O(N);但如果是在列表的头部或尾部,则可以近似的看做常数。
hashtable
hashtable的结构类似全局hash表,hash表的时间复杂度为O(1)
intset
intset的结构就是一个int的数组,intset查找采用折半查找的方式,时间复杂度为O(logN)
skiplist
跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位
时间复杂度
各种数据结构查找元素的时间复杂度
编码 | 名称 | 时间复杂度 |
---|---|---|
quicklist | 快速列表 | O(1) ~ O(N) |
ziplist | 压缩列表 | O(N) |
hashtable | 哈希表 | O(1) |
intset | 整数数组 | O(logN), 二分查找 |
skiplist | 跳表 | O(logN) |
基本数据类型
string
实现:
类型 | 条件 | 特点 |
---|---|---|
int | 字符串长度小于等于20的整数 | int有分为两种一种是本身程序启动就已经创建好的,一种是需要手动创建的 |
embstr | 字符串长度小于等于44的字符串 | 1.只分配一次内存空间,因此robj和sds是连续的; 2.只读; 3.Embstr字符串需要修改时,会转成raw,之后一直为raw |
raw | 长度大于44的字符串 | 1.robj和sds非连续; 2.可修改 |
embstr和raw类型底层使用sds
常用指令:
命令 | 描述 |
---|---|
SET key value | 设置指定 key 的值 |
GET key | 获取指定 key 的值 |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value) |
MGET key1 key2.. | 获取所有(一个或多个)给定 key 的值 |
SETEX key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
STRLEN key | 返回 key 所储存的字符串值的长度 |
MSET key value key value | 同时设置一个或多个 key-value 对 |
MSETNX key value key value ... | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 |
INCR key | 将 key 中储存的数字值增一 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值 |
DECR key | 将 key 中储存的数字值减一 |
APPEND key value | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 |
使用场景:
缓存功能:mysql存储,redis做缓存
计数器 / 限流:如点赞次数,视频播放次数 / 基于redis的分布式限流
分布式锁:基于设置唯一key用于分布式锁
list
常用指令:
命令 | 描述 |
---|---|
BLPOP key1 key2 | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BRPOP key1 key2 | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
LINDEX key index | 通过索引获取列表中的元素 |
LLEN key | 获取列表长度 |
LPOP key | 移出并获取列表的第一个元素 |
LPUSH key value1 value2 | 将一个或多个值插入到列表头部 |
LRANGE key start stop | 获取列表指定范围内的元素 |
LREM key count value | 移除列表元素 |
LSET key index value | 通过索引设置列表元素的值 |
RPOP key | 移除列表的最后一个元素,返回值为移除的元素 |
使用场景:
常见数据结构:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
存储列表类型的数据
hash
常用指令:
命令 | 描述 |
---|---|
[HDEL key field1 field2 | 删除一个或多个哈希表字段 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在 |
HGET key field | 获取存储在哈希表中指定字段的值 |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment |
HKEYS key | 获取哈希表中的所有字段 |
HLEN key | 获取哈希表中字段的数量 |
[HMGET key field1 field2 | 获取所有给定字段的值 |
[HMSET key field1 value1 field2 value2 | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value |
HVALS key | 获取哈希表中所有值 |
使用场景:
对象存储:对象的属性和val以hash的方式存储
配置信息:key为配置项名称,val为配置项值
会话缓存:将用户的会话数据以 Hash 形式存储,key 可以是 session ID,field 是会话中的各个属性名,value 是相应的属性值
set
常用指令:
命令 | 描述 |
---|---|
SADD key member1 member2 | 向集合添加一个或多个成员 |
SCARD key | 获取集合的成员数 |
SDIFF key1 key2 | 返回第一个集合与其他集合之间的差异。 |
SINTER key1 key2 | 返回给定所有集合的交集 |
SISMEMBER key member | 判断 member 元素是否是集合 key 的成员 |
SMEMBERS key | 返回集合中的所有成员 |
SPOP key | 移除并返回集合中的一个随机元素 |
SRANDMEMBER key count | 返回集合中一个或多个随机数 |
SREM key member1 member2 | 移除集合中一个或多个成员 |
SUNION key1 key2 | 返回所有给定集合的并集 |
使用场景:
抽奖中随机数:spop/srandmember
标签系统:例如,在博客、社区或者电商网站中,可以为用户或内容打上多个标签,并将这些标签作为 set 存储起来,方便进行分类和搜索
共同好友/关注者:在社交网络应用中,可以使用 Set 来存储用户的共同好友列表或共同关注者列表,通过交集操作(
SINTER
)快速获取两个人之间的共同关系唯一性验证:利用 Set 中不允许重复元素的特性,可以在添加新数据时确保数据的唯一性,例如记录网站独立访客的 IP 地址,或者在游戏中的唯一道具发放等
聚合运算:利用 Redis 提供的 Set 集合间的并集、差集、交集操作,可以轻松地在服务端完成数据合并、过滤等复杂逻辑,提高效率
zset
常用指令:
命令 | 描述 |
---|---|
ZADD key score1 member1 score2 member2 | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
ZRANGE key start stop WITHSCORES | 通过索引区间返回有序集合指定区间内的成员 |
ZRANGEBYSCORE key min max WITHSCORES LIMIT | 通过分数返回有序集合指定区间内的成员 |
ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE key start stop WITHSCORES | 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZSCORE key member | 返回有序集中,成员的分数值 |
使用场景:
排行榜:例如,在游戏、社区、电商等场景中实现用户积分、点赞数、评论数等各种基于数值大小排列的排行榜功能。将用户 ID 作为 member,对应的积分、点赞数或评论数作为 score,通过
ZADD
添加并更新数据,ZRANGE
或ZREVRANGE
获取排名列表。延时任务队列:利用 score 表示未来执行时间的时间戳,将任务添加到 ZSet 中后会自动按照 score 排序。可以通过定时任务定期检查 ZSet 中 score 最小的任务并执行,以此实现延时任务调度
带权重的消息队列:在消息处理系统中,可以根据消息的重要程度或者优先级设置不同的 score,从而实现带有优先级的消息排队和消费。
实时统计:如统计最近一小时内最活跃的用户,可以把用户 ID 加入 ZSet,并以活动次数为 score,这样就可以随时获取当前时段内最活跃用户的列表。
地理位置索引:存储带有地理坐标信息的数据,比如经纬度,将经度和纬度转换成适合排序的分数(如GeoHash),用于查询附近的地点、计算距离等。
限流器:滑动窗口限流算法的一种实现方式就是用 ZSet 存储带有时间戳的请求记录,根据时间窗口内的元素数量来判断是否超过限定阈值。
高级数据类型
geo
geohash:
为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再 把经纬度各自的编码组合成一个最终编码。
常用指令:
命令 | 描述 |
---|---|
GEOADD key longitude latitude member [longitude latitude member ...] | 添加地理位置的坐标 |
GEOPOS key member [member ...] | 获取地理位置的坐标。 |
GEODIST key member1 member2 [m|km|ft|mi] | 计算两个位置之间的距离。 m :米,默认单位。 km :千米。 mi :英里。 ft :英尺。 |
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] | 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合 |
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] | 根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合 |
GEOHASH key member [member ...] | 返回一个或多个位置对象的 geohash 值 |
使用场景:
物联网设备追踪与管理:对于具有GPS功能的物联网设备,可以将它们的位置信息实时存储到Redis GEO中,便于进行跟踪、监控和分析。
附近的人/地点查找:社交应用中的“附近的朋友”或“附近的主播”功能,可以根据用户的实时经纬度信息快速找出一定范围内的其他用户或地点。
配送服务优化:物流行业可以利用 Redis GEO 存储各个配送员、商家或仓库的位置信息,为订单分配最近或最优路线的配送资源。
位置签到系统:在基于位置的移动应用中,实现用户签到功能,并可统计某区域内签到人数、热门地点等。
hyperlog
常用指令:
命令 | 描述 |
---|---|
PFADD key element element ... | 添加指定元素到 HyperLogLog 中。 |
PFCOUNT key key ... | 返回给定 HyperLogLog 的基数估算值。 |
PFMERGE destkey sourcekey sourcekey ... | 将多个 HyperLogLog 合并为一个 HyperLogLog |
使用场景:
网站访问量统计:可以用来估算一天内或一段时间内的不重复访客数量,而不是记录每个访客的详细访问记录。
在线广告点击去重统计:计算某段时间内广告被不同用户点击的次数,用于广告效果分析和费用结算。
大规模数据分析:在数据流处理中,快速估算通过系统的不同元素的数量,例如日志文件中的唯一IP地址、唯一设备ID或者唯一的操作类型。
实时大流量监控:对于高并发系统,实时统计不重复请求来源的数量,用于系统负载分析和异常检测。
bigmap
常用指令:
命令 | 描述 |
---|---|
GETBIT key offset | 对 key 所储存的字符串值,获取指定偏移量上的位(bit) |
SETBIT key offset value | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit) |
使用场景:
用户签到系统:记录每个用户的每日签到情况,只需用一个比特位表示用户是否在某一天签到过,可以极大地节省空间。
活跃用户统计:通过设置或查询对应用户 ID 的比特位,快速统计网站或应用的每日、每周、每月活跃用户数。
在线用户标识:标记用户是否在线,每个比特位代表一个用户,1 表示在线,0 表示离线。
广告曝光计数:追踪广告被不同用户看到的次数,为每个用户分配一个比特位来表示该用户是否看过某个广告。
权限系统:实现精细的权限控制,比如每个功能模块或者操作对应一个比特位,用于标记用户是否拥有特定权限。
布隆过滤器
二值状态的存储
布隆过滤器
布隆过滤器由一个初值都为 0 的 bit 数组和 N 个哈希函数组成,可以用来快速判断某个数 据是否存在。
stream
Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容:
每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。
上图解析:
Consumer Group :消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。
last_delivered_id :游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
pending_ids :消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符)。
常用命令:
消息队列相关命令:
XADD - 添加消息到末尾
XTRIM - 对流进行修剪,限制长度
XDEL - 删除消息
XLEN - 获取流包含的元素数量,即消息长度
XRANGE - 获取消息列表,会自动过滤已经删除的消息
XREVRANGE - 反向获取消息列表,ID 从大到小
XREAD - 以阻塞或非阻塞方式获取消息列表
消费者组相关命令:
XGROUP CREATE - 创建消费者组
XREADGROUP GROUP - 读取消费者组中的消息
XACK - 将消息标记为"已处理"
XGROUP SETID - 为消费者组设置新的最后递送消息ID
XGROUP DELCONSUMER - 删除消费者
XGROUP DESTROY - 删除消费者组
XPENDING - 显示待处理消息的相关信息
XCLAIM - 转移消息的归属权
XINFO - 查看流和消费者组的相关信息;
XINFO GROUPS - 打印消费者组的信息;
XINFO STREAM - 打印流信息
使用场景:
消息队列系统:替代传统的消息队列服务,实现微服务之间的异步通信。Stream 支持消息持久化存储,并提供类似于 Kafka 的 Consumer Group 功能,允许多个消费者独立地消费同一份数据流的不同部分。
扩展数据类型
RedisTimeSeries
常用命令:
创建时间序列:使用 TS.CREATE 命令创建一个新的时间序列。你可以指定一系列的选项,比如保留期限、标签等:
TS.CREATE mytimeseries RETENTION 86400000 LABELS sensor_id 1234 location "New York"
这条命令创建了一个名为 mytimeseries 的时间序列,设置了 24 小时(86400000 毫秒)的数据保留期限,并附加了一些标签信息
添加数据点:使用 TS.ADD 命令向时间序列中添加数据点。如果时间序列不存在,该命令可以自动创建时间序列(前提是未指定任何创建时间序列时的特殊选项):
TS.ADD mytimeseries * 25.4
这条命令向 mytimeseries 时间序列添加了一个值为 25.4 的数据点,* 表示使用服务器当前时间作为时间戳
查询数据点:使用 TS.RANGE 和 TS.REVRANGE 命令查询时间序列中的数据点。这些命令支持指定时间范围,并且可以应用不同的聚合策略:
TS.RANGE mytimeseries 1577836800000 1577840400000 AGGREGATION avg 60000
这个例子查询了时间序列 mytimeseries 在指定时间范围内每分钟(60000 毫秒)的平均值
设置规则进行数据聚合:使用 TS.CREATERULE 命令可以创建规则,自动将聚合数据保存到另一个时间序列中:
TS.CREATERULE mytimeseries aggseries AGGREGATION avg 60000
这条命令创建了一个规则,将 mytimeseries 中每分钟的平均值自动保存到 aggseries 时间序列中
删除数据点和时间序列:使用 TS.DEL 命令删除时间序列中的特定数据点,使用 TS.DELETE 命令删除整个时间序列:
TS.DELETE mytimeseries
这条命令删除了整个 mytimeseries 时间序列。
使用标签进行查询:RedisTimeSeries 支持使用标签进行高效查询
TS.MGET FILTER location="New York"
查询所有位于“New York”的传感器的时间序列
使用场景:
监控与告警:在运维监控系统中记录并查询服务器、数据库、应用程序的性能指标,例如 CPU 负载、内存使用情况、磁盘 I/O 等,并根据这些实时数据设置告警阈值。
物联网(IoT)应用:处理从大量传感器设备发送过来的时间序列数据,如智能电网中的电力消耗数据、智能家居中的环境温湿度数据等。
金融交易分析:存储股票价格、汇率变动、交易量等金融市场数据,支持实时分析、回溯历史数据以及生成图表展示。
游戏数据分析:记录玩家行为数据,如登录时间、在线时长、每日活跃用户数(DAU)等,为运营策略提供依据。
业务运营统计:电子商务网站可以利用 RedisTimeSeries 记录商品浏览次数、购买数量随时间的变化趋势,以优化库存管理和营销策略。
RedisGraph
使用场景:
社交网络分析:在社交网络中,用户之间的关系可以自然地建模为图结构,RedisGraph 可用于存储和查询用户的社交关系、好友推荐、社区发现、路径搜索等。
推荐系统:构建基于物品-用户交互的图模型,进行协同过滤或关联规则挖掘,提供个性化推荐。
知识图谱:适用于构建和查询复杂的关系型数据集,例如语义网、企业知识库、领域本体等,支持实体间多对多的关系查询以及属性路径检索。
应用
统计对比
数据类型 | 聚合统计 | 排序统计 | 二值状态统计 | 基数统计 |
---|---|---|---|---|
set | 支持差集、交集、并集计算 | 不支持 | 不支持 | 精确统计,大数据量时,效率低,内存开销大 |
zset | 支持差集、交集、并集计算 | 支持 | 不支持 | |
hash | 不支持 | 不支持 | 不支持 | |
list | 不支持 | 支持 | 不支持 | 不支持,元素没有去重 |
bigmap | 与、或、异或计算 | 不支持 | 支持,大数据量时,效率高,省内存 | 精确统计,大数据量时,内存开销大于hyperlog |
hyperlog | 不支持 | 不支持 | 不支持 | 概率统计,大数据量时,非常节省内存 |
附录
geohash
对于一个地理位置信息来说,它的经度范围是[-180,180]。GeoHash 编码会把一个经度值 编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作,其中 N 可以自定义。
在进行第一次二分区时,经度范围[-180,180]会被分成两个子区间:[-180,0) 和[0,180] (我称之为左、右分区)。此时,我们可以查看一下要编码的经度值落在了左分区还是右 分区。如果是落在左分区,我们就用 0 表示;如果落在右分区,就用 1 表示。这样一来, 每做完一次二分区,我们就可以得到 1 位编码值。
然后,我们再对经度值所属的分区再做一次二分区,同时再次查看经度值落在了二分区后 的左分区还是右分区,按照刚才的规则再做 1 位编码。当做完 N 次的二分区后,经度值就 可以用一个 N bit 的数来表示了。
举个例子,假设我们要编码的经度值是 116.37,我们用 5 位编码值(也就是 N=5,做 5 次分区)。
我们先做第一次二分区操作,把经度区间[-180,180]分成了左分区[-180,0) 和右分区 [0,180],此时,经度值 116.37 是属于右分区[0,180],所以,我们用 1 表示第一次二分区 后的编码值。
接下来,我们做第二次二分区:把经度值 116.37 所属的[0,180]区间,分成[0,90) 和[90, 180]。此时,经度值 116.37 还是属于右分区[90,180],所以,第二次分区后的编码值仍然 为 1。等到第三次对[90,180]进行二分区,经度值 116.37 落在了分区后的左分区[90, 135) 中,所以,第三次分区后的编码值就是 0。
按照这种方法,做完 5 次分区后,我们把经度值 116.37 定位在[112.5, 123.75]这个区 间,并且得到了经度值的 5 位编码值,即 11010。这个编码过程如下表所示:
对纬度的编码方式,和对经度的一样,只是纬度的范围是[-90,90],下面这张表显示了对 纬度值 39.86 的编码过程。
当一组经纬度值都编完码后,我们再把它们的各自编码值组合在一起,组合的规则是:最 终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位 从 0 开始,奇数位从 1 开始。
我们刚刚计算的经纬度(116.37,39.86)的各自编码值是 11010 和 10111,组合之后, 第 0 位是经度的第 0 位 1,第 1 位是纬度的第 0 位 1,第 2 位是经度的第 1 位 1,第 3 位是纬度的第 1 位 0,以此类推,就能得到最终编码值 1110011101,如下图所示:
用了 GeoHash 编码后,原来无法用一个权重分数表示的一组经纬度(116.37,39.86)就 可以用 1110011101 这一个值来表示,就可以保存为 Sorted Set 的权重分数了。
操作
Pipeline和mget
Redis Pipeline(管道)和mget命令都是Redis为了提高批量操作效率而设计的机制,但它们有着不同的应用场景和特点:
Redis Pipeline(管道):
优势:
减少网络往返延迟:Pipeline可以将多个命令一起发送给Redis服务器,然后一次性接收所有的响应,显著减少了客户端与服务器之间的网络通信次数,从而提升性能。
批量处理:适合进行一系列相关联的操作,如同时对多个键进行多个不同的操作(如设置、获取、删除等)。
使用场景:
批量处理数据时,尤其是执行一系列关联操作时,如批量更新用户信息等。
在高并发场景下,利用Pipeline可以大幅提高系统吞吐量。
Redis mget命令:
优势:
批量获取:mget主要用于批量获取多个键的值,它是一次性发送一个命令,获取多个键的值,相较于单独发送多个get命令,同样减少了网络交互次数。
使用场景:
当需要一次性获取多个键对应的值时,如从数据库中批量获取多个用户的属性信息。
特别适用于读取操作集中且键值相对较小的情况。
区别:
Pipeline功能更强大,它不仅仅局限于mget这类批量获取操作,还可以包括其他任何Redis命令的组合操作。
mget命令仅用于批量获取键值,而Pipeline则可以混合执行任意Redis命令。
Pipeline提供了更为灵活的批处理机制,尤其在处理复杂事务时更具优势。