新闻详情

新闻详情

首页 / 资讯中心 / 详情

Redis高级数据结构实战:GEO、BitMap与HyperLogLog的落地

发布时间:2026/9/11 4:28:02来源:尧图网络
Redis高级数据结构实战:GEO、BitMap与HyperLogLog的落地
最近在从头捋“黑马点评”这个项目发现很多人卡在GEO、BitMap、HyperLogLog这三个Redis扩展数据类型上。原因不复杂这三个结构不像String、Hash那样在常规业务里天天用命令少但细节多真到项目里落地时光靠背命令是hold不住的。这篇文章是我基于黑马点评项目实战过程的完整笔记重点讲清楚“什么时候用、底层怎么想、命令怎么拼、踩过哪些坑”尤其是附近商铺、用户签到、UV统计这三个核心场景的完整落地思路给正在刷项目或者准备面试讲项目的人做个参考。我对这个项目的整体评价是真正有含金量的不是CRUD而是这里面对Redis数据结构的选型思考。GEO解决“附近的人/附近的店”BitMap解决“签到打卡”HyperLogLog解决“不精确但极省内存的去重计数”。三个场景各有各的脾气用对了是性能利器用错了就是给自己埋雷。1. 项目里这三个功能为什么非得用Redis1.1 业务场景先梳理清楚黑马点评这个项目本质是一个点评类应用核心逻辑围绕店铺、用户、订单展开。其中三个功能特别典型也是面试官最爱追问的地方第一个是“附近商铺”。用户在首页或者地图页打开后要能看到离自己3公里、5公里范围内的店铺并且按距离从近到远排列。传统做法是查数据库把所有店铺的经纬度拿出来用Haversine公式在应用层算距离再排序。这个方案在小数据量下能跑可一旦店铺数据上万每次请求都要全表扫描一遍光算距离就足够把CPU打满延迟直接不可控。第二个是“用户签到”。点评类App很喜欢做连续签到送积分、送优惠券的活动。签到记录最朴素的设计是一张签到表字段是user_id、sign_date、bonus。用户量一大这张表会膨胀得非常快。你想一下一个用户一年签到200次100万用户就是2亿条记录存储和查询成本都扛不住而且签到这种“标记某一天有没有发生”的语义本质上就是一个布尔数组用关系型数据库表达太浪费。第三个是“页面UV统计”。运营需要知道今天有多少独立用户访问了首页或者某个店铺详情页。这里的“独立”二字是重点同一个用户访问多次只能算一次。常规方案是用Set集合把每个访问用户的ID丢进去最后统计集合大小。这个方案逻辑上没错但内存完全受不了。用户量过百万以后一个Set里塞几百万个用户ID轻松吃掉几十MB甚至上百MB内存就为了统计一个数字怎么看都不划算。1.2 传统方案和Redis方案的本质差异三个场景分别暴露出三种问题计算远距离耗性能、存储明细浪费空间、去重计数内存爆炸。而GEO、BitMap、HyperLogLog这三个结构恰好分别对应这三种问题的最优解。GEO的底层是ZSETscore存的是经纬度编码后的数值底层用跳表实现有序查找可以快速圈定一个圆心范围内的所有点天然支持距离排序。BitMap本质上是一个Bit数组每一位只有0和1两种状态签到你只需要把对应日期的那一位设为1一个用户一整年的签到记录只需要365个bit也就是不到50字节。HyperLogLog则是标准概率数据结构通过哈希和位模式估算基数误差在0.81%以内但无论你塞进去多少数据内存始终保持在12KB左右。这三种结构其实代表了三种典型的思路转变GEO是把距离计算从应用层下推到数据层BitMap是把你根本不需要的“明细”碾压到极致HyperLogLog则是明明白白告诉你“我可以不精确但我极其高效”。理解到这一层再去记命令和调参就顺理成章了。这也是我在项目实战里最大的感受Redis的每一种数据结构都不是平白无故存在的它背后对应着一类通用问题的固定解法。2. GEO实战附近商铺功能完整落地2.1 GEO的底层逻辑要先明白GEO是Redis 3.2版本引入的功能底层数据结构其实就是ZSET只是Redis在上层做了一层经纬度编码。这个编码思路很有意思它用的是类似二分法的GeoHash算法。你可以把地球想象成一个二维平面经度范围是[-180, 180]纬度范围是[-90, 90]。第一次二分把经度切成两半左边记0、右边记1第二次对选中区间再切不断重复。纬度也做同样操作。然后把经度编码和纬度编码交错拼在一起最终得到一个二进制串再把这个二进制串转成十进制分数作为ZSET的score。这个编码方式有几层含义值得记住。第一ZSET天生支持按score排序所以GEO查询按距离远近排序本质上就是ZSET的范围查询。第二编码时经度和纬度的位数可以控制距离精度位数越多精度越高。Redis默认用52位这个精度在几厘米以内对绝大多数业务场景绰绰有余。第三因为底层是ZSET同一个member只能存一个score如果你对同一个店铺重复添加坐标新值会覆盖旧值这个特性有时候是好事方便更新店铺位置但也容易误操作覆盖数据。下面这张表是我整理的GEO核心命令和对应语义方便对照项目使用命令语义项目中使用场景GEOADD key longitude latitude member添加地理位置坐标导入店铺经纬度GEOPOS key member获取一个或多个成员的坐标展示店铺坐标GEODIST key member1 member2 unit计算两个成员之间的距离计算用户与店铺距离GEOSEARCH key FROMMEMBER/LONLAT radius unit按圆心搜索附近成员用户附近3km商铺GEOSEARCHSTORE key1 key2 ...搜索结果存储到另一个key缓存热门搜索结果GEODIST的unit参数支持m、km、mi、ft实际项目中99%用的是km或者m注意别把单位搞混否则算出来的距离会差一千倍。2.2 店铺坐标数据如何初始化黑马点评项目里的店铺数据是提前从数据库导出的每一条都包含店铺ID和经纬度坐标。写入Redis的代码思路比较简单把数据库里的shop列表拿出来循环依次调用GEOADD命令。用RedisTemplate来实现的话核心代码是这样的// 把店铺ID和经纬度写入GEO MapObject, Object locationMap new HashMap(); for (Shop shop : shopList) { String key RedisConstants.SHOP_GEO_KEY shop.getTypeId(); redisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString()); }这里有两个细节我今天必须说一下都是实际开发中容易踩的雷。第一个是key的设计。项目里不建议把所有店铺都丢进同一个GEO key里因为用户查询附近店铺时通常有类型筛选。我做的方案是按店铺类型分成不同的key比如美食类店铺一个key、酒店类一个key。这样查询时只需要查对应类型的GEO集合数据量小、查询快类型过滤天然完成。key的格式是固定前缀加类型ID比如shop:geo:1表示美食类。第二个是经纬度的先后顺序。Redis的GEOADD命令参数顺序是经度在前、纬度在后和很多地图API的返回顺序是反着的。GEOADD key longitude latitude member永远先写经度再写纬度。我最初导入数据时想当然地按纬度在前来写结果所有店铺位置全部偏移跑到海里去了。这个错误特别隐蔽因为命令能执行成功数据也不会报错只有到前端地图上一看才发现全乱了。2.3 附近商铺搜索的实现和排序优化GEO搜索的核心命令在新版本Redis中是GEOSEARCH它比老版本的GEORADIUS用起来更顺手。GEOSEARCH支持从某个成员出发FROMMEMBER或者从某个经纬度坐标出发FROMLONLAT进行范围搜索还支持按距离排序。用户打开首页定位后前端会给后端传两个参数用户的经度longitude和纬度latitude加上查询半径distance。后端拿到参数后调用GEOSEARCH。下面是我在项目中实际使用的Redis命令GEOSEARCH shop:geo:1 FROMLONLAT 116.397128 39.916527 BYRADIUS 3 km ASC这条命令的意思是从经度116.397128、纬度39.916527这个坐标点出发查找半径3公里内的所有店铺按距离从近到远排序返回。在Java中通过Spring Data Redis调用Circle circle new Circle(new Point(userLng, userLat), new Distance(radius, RedisConstants.GEO_DISTANCE_UNIT)); RedisGeoCommands.GeoRadiusCommandArgs args RedisGeoCommands.GeoRadiusCommandArgs .newGeoRadiusArgs() .includeDistance() .sortAscending(); GeoResultsRedisGeoCommands.GeoLocationString results redisTemplate.opsForGeo().radius(shopGeoKey, circle, args);这里有一个很重要的性能细节GEOSEARCH默认返回的结果是不带距离的如果要展示“距您500米”必须加上WITHDIST参数Redis才会额外计算并返回每个成员的距离。在Spring Data Redis中对应includeDistance()方法。如果只需要按距离排序不加这个参数会更快因为省去了距离计算这一步。实际项目中如果前端只需要排序不需要展示具体距离完全可以不加WITHDIST性能差异在高并发下还是能感知到的。2.4 附近商铺的分页问题分页是个大坑。GEO查询不像MySQL的LIMIT那样天然支持分页GEOSEARCH命令没有OFFSET和LIMIT参数。有些资料说可以直接用COUNT参数限制返回条数但COUNT只是限制返回上限并不能做真正的分页。也就是说如果你一页展示10条用户滑到第二页时你没有办法直接告诉Redis“从第11条开始给”。这是老版本GEORADIUS的固有缺陷新版本GEOSEARCH虽然做了很多优化但也没有提供真正的游标分页机制。我实际项目里的方案是一次查询取一个相对较大的范围比如先按5公里半径查出所有结果拿到Java里按距离排序再做内存分页。这听起来可能有点粗暴但实际效果并不差。原因在于GEO查询的数据量有限一个城市5公里内的店铺数量通常控制在几百到几千条全量返回后内存分页性能完全能接受。而且有一个非常大的好处距离排序后的分页数据可以在内存中直接复用同一个用户连续滑动翻页时不需要反复查询Redis。在这个基础上还可以做一层缓存优化。对于热门位置和热门商圈查询结果可以缓存到Redis里设置几分钟的过期时间。不过缓存key要做得聪明一点可以用geohash前缀作为key的一部分比如查询点在城市东区命中同一个geohash前缀的用户可以共享缓存结果命中率能有明显提升。2.5 GEO实战的额外踩坑记录第一过期策略。GEO数据如果存储的是店铺坐标店铺位置很少变化可以长期保存。但如果是存储用户实时位置一定要设置过期时间避免Redis内存被大量历史位置撑爆。Redis的GEO底层是ZSETZSET本身不支持给单个member设置过期时间所以只能对整个key设置过期时间或者在业务层面做定时清理逻辑。第二GEO查询半径设置。黑马点评项目里我一开始把半径设成固定的2公里结果在三四线城市的测试环境中发现很多店铺查不出来。后来把半径改成可配置的前端可以传5公里、10公里后台按城市密度做兜底。做业务功能时固定阈值往往是最容易出问题的设计一定要保证参数可动态调整。第三地理坐标精度问题。前端定位拿到的经纬度通常只有6位小数用来做GEO编码已经足够。但如果对接GPS原始坐标不同坐标系之间的偏移会很大尤其是火星坐标GCJ-02和标准WGS-84坐标系之间差几百米。国内地图服务返回的坐标基本都是火星坐标需要确认和数据库存储的坐标系是否一致如果一个是GCJ-02一个是WGS-84那搜出来的“附近3公里”可能是错误的。3. BitMap实战用户签到功能实现与优化3.1 位图为什么适合做签到功能BitMap不是一种独立的数据结构它依赖于Redis的String类型底层是一个字节数组每个bit都可以独立操作。Setbit命令可以任意指定一个bit位写0或者写1也可以读任意一个bit位的值。签到业务的核心特征是每个用户每一天的签到状态就是一个布尔值要么签了要么没签。一个用户一年最多365个状态位按bit来存储一年只需要46个字节左右。如果用数据库一张表来存一个用户一年会产生365条记录100万用户就有3.65亿条记录。差距是数量级的。我把这两个方案做了一个对比方案存储方式一年100万用户所需存储MySQL签名表每用户每行记录一年3.65亿行记录Redis BitMap每用户一个key按位存储100万 * 46字节 ≈ 44MB注意上面的MySQL方案还有索引开销、回表查询、写入锁竞争而Redis BitMap的写入是O(1)的位操作速度完全不在一个量级。做技术选型时看到这个对比几乎没有犹豫的必要。3.2 签到功能的key设计与写入命令签到key的设计是BitMap实战中的核心决策。我的设计是sign:用户ID:年月。比如sign:10086:202502表示用户10086在2025年2月的签到记录。key中带上月份有两个好处一是当月的签到数据集中在一个key里查询当月签到能力和查询某一天都很快二是key可以按月份设置过期时间自然解决历史数据长期堆积的问题。BitMap的offset从0开始计数所以第1天对应offset 0第2天对应offset 1第20天对应offset 19。月末最后一天的offset是当月天数减1。签到写入命令非常简单SETBIT sign:10086:202502 19 1这条命令表示用户10086在2025年2月20日签到了。Java中同理String key RedisConstants.SIGN_KEY userId : currentMonth; Long offset dayOfMonth - 1L; Boolean result redisTemplate.opsForValue().setBit(key, offset, true);这里有一个细节很容易被忽略setBit的返回值是修改前的旧值不是修改后的新值。如果用户重复签到第二次setBit会返回true表示之前已经签到了。我见过有人用返回值判断签到是否成功的结果逻辑完全写反了。正确的做法是如果返回值是true说明原本就是1属于重复签到如果是false说明原本是0签到成功。3.3 查询签到记录与统计签到次数查询用户某一天是否签过到用GETBIT命令。offset同样是从0开始的当月天数减1GETBIT sign:10086:202502 19返回1就表示当天签到了返回0表示没有。如果想查整个月签到了多少天用BITCOUNT命令BITCOUNT sign:10086:202502BITCOUNT统计的是整个key中bit值为1的数量正好等于当月签到天数。这里不需要额外加参数命令本身就足够直观。Java里用RedisTemplate实现Long signCount redisTemplate.opsForValue().bitCount(key);BITCOUNT的语法支持范围的写法可以统计某个byte区间内的1的个数比如BITCOUNT key 0 3统计前4个字节。但我不推荐用范围计数来做连续签到统计因为字节范围和天数不是整数倍关系要自己处理位偏移很容易算错。3.4 连续签到天数的核心算法连续签到是运营活动里最常见的需求也最考验基本功。需求通常是给定一个用户计算他截至今天连续签到了多少天。我的实现思路分成三步第一步获取从当月1日到当天为止所有bit位。这里用BITFIELD命令比用GETBIT循环查快得多一条命令就能把最长31位的二进制串拉出来BITFIELD sign:10086:202502 GET u20 0这条命令的意思是从offset 0开始以无符号20位数u20的格式读取20个bit。这里的20就是今天在这个月中的天数编号。如果今天是2月20日就读20个bit。如果今天是2月5日就读5个bit。这样拿到的是一个long型整数它的二进制表示从低位到高位正好对应从第1天到今天的签到记录bit位为1表示当天签到为0表示未签到。第二步通过位运算计算连续1的个数。核心思路是不断把数字右移一位同时把最低位的值拿出来判断是否为1直到遇到0为止。Java实现如下Long val ...; // BITFIELD读取到的值 int count 0; while (val 0) { if ((val 1) 1) { count; val 1; } else { break; } }第三步把连续签到天数返回给前端。这个算法的时间复杂度是O(n)n最多31性能毫无压力。我在项目里实际测试过单用户查询耗时在毫秒以内。这里有件事我必须提醒BITFIELD读取的位数和当天日期直接相关。如果今天是3号你最多只能判断3号当天和过去2天的连续性不存在的未来天数不要去读。如果你直接读u31那后面那些不存在的bit位会被当成0处理连续签到天数就永远是0这个坑我踩过排查了很久才意识到是读取位数的问题。3.5 BitMap的额外注意事项单个key的最大bit位数量是2^32-1约42.9亿位也就是约512MB的String对象。做签到业务根本碰不到这个上限但如果有人想用BitMap做更大规模的标记需要注意这个限制。如果想统计一整年的签到次数有两种选择按月存然后用BITCOUNT分别统计12个key再求和或者用一个key存365位。前者的好处是方便按月查询和过期清理后者统计总次数更快。我建议按业务需求来绝大多数点评类项目按月存更合理。BitMap的expire设置要配合定时任务。如果key在月末自动过期月初就要提前创建新key否则用户一签到就创建签到时会有一次额外的key创建开销在高并发下虽然不是大问题但不算最优做法。我习惯在月末最后一天的凌晨通过定时任务预创建下个月的key。4. HyperLogLog实战UV统计功能落地4.1 为什么UV统计不能只用SetUV统计的语义是“统计有多少独立访客”。很多新手第一反应是用Set把每个用户ID都丢进去然后SCARD一眼看集合大小。这样确实精确但代价是集合里每一个用户ID都要占用空间。以100万用户为例每个用户ID平均按20字节算Set底层还需要维护哈希表或跳表的额外开销实际内存占用在30MB到50MB是保守估计。如果再把时间维度拆细比如统计每天的日活UV那内存消耗会再乘上天数。HyperLogLog的思路完全不同。它不保存原始数据只保存一个固定大小的寄存器数组标准实现是16384个寄存器每个寄存器6bit加起来就是12KB左右。无论你塞多少个用户进去内存占用始终是这个数。代价是统计结果是近似值标准误差是0.81%。换句话说真实UV是100万的话统计结果在99.19万到100.81万之间浮动对运营场景来说完全够用。我在项目里做的对比试验很有代表性向Set和HyperLogLog各写入100万个模拟用户IDSet占用内存约38MBHyperLogLog占用12KB相差3000多倍。而统计耗时几乎一样都是毫秒级。运营人员看UV趋势图看的是量级和波动趋势不需要精确到个位数HyperLogLog的误差在这个语义下完全可以接受。4.2 用HyperLogLog实现UV统计的完整代码HyperLogLog的使用比想象中更简单。记录一个用户访问用PFADD命令把用户ID作为参数传进去PFADD uv:shop:1024 user12345统计这个店铺的独立访客数用PFCOUNTPFCOUNT uv:shop:1024多个月的数据合并比如统计一个季度的UV用PFMERGE把三个月的数据合并到另一个key里再统计PFMERGE uv:shop:1024:quarter uv:shop:1024:202501 uv:shop:1024:202502 uv:shop:1024:202503 PFCOUNT uv:shop:1024:quarterJava端的代码更加整洁项目里可以这样封装// 记录访问 redisTemplate.opsForHyperLogLog().add(uv:shop: shopId, userId.toString()); // 统计UV Long uvCount redisTemplate.opsForHyperLogLog().size(uv:shop: shopId);这里有一个值得注意的细节PFADD支持一次性传入多个元素一次调用可以批量添加用户。批量操作的性能优于多次单条操作在高并发日志回放、离线数据导入场景下能省不少网络往返。4.3 HyperLogLog的精度和内存细节HyperLogLog的原理说简单也简单每个元素通过哈希函数算出一个64位的二进制串从高位开始数遇到第一个1停在这个位置把当前寄存器的最大值更新为这个计数。统计时根据所有寄存器里的最大前导零数量用调和平均数估算整体基数。真实实现会比这个复杂但核心思想就是通过哈希值的“均匀随机”特性用少数寄存器估算海量数据的基数。精度方面标准误差是1.04 / 平方根(m)m是寄存器数量16384代入算出来约0.81%。这个误差方向是不确定的可能高估也可能低估但对于绝大多数业务指标的观察比如日活、月活、页面UV完全不影响判断趋势。如果你需要更精确的结果可以在HyperLogLog之上加一层纠偏比如小基数时Redis会自动启用稀疏存储和Linear Counting算法修正这个不用我们操心Redis内部已经处理了。真正需要注意的是HyperLogLog不能像Set一样告诉你某个用户是否访问过。因为它根本没有保存具体元素只保存了哈希统计信息。如果你的业务需求是“判断用户是否首次访问”那不能用HyperLogLog还是得用Set或者BitMap。4.4 项目中的UV统计架构设计我实际的项目里是这样设计UV统计的每个页面或者每个资源设置一个独立的HyperLogLog key。比如首页UV的key是uv:index:{yyyyMMdd}店铺详情页UV的key是uv:shop:{shopId}:{yyyyMMdd}。用户每次访问时把userId加到对应key里。userId一定要取稳定的业务主键不要用前端随机生成的自增ID否则同一个用户换设备就会被当成两个用户。为了降低Redis的写入压力我在应用层加了一层本地缓存同一个用户在一次会话内相同页面的UV统计只发送一次PFADD。这个优化能过滤掉大量重复请求把QPS降一个数量级而远端key的数据准确性不受影响因为重复PFADD同一个用户本来就不会让计数增加只是白费网络开销。另外HyperLogLog的key生命周期管理比BitMap更需要注意。UV统计key如果按天拆运营只需要最近90天到180天的数据建议对key设置TTL或者用定时任务定期清理。旧的key不删除Redis里会有大量12KB的碎片数据日积月累也是不少内存。4.5 HyperLogLog的实战补充建议如果你需要把UV拆成新用户和旧用户需要维护两个HyperLogLog key。判断“是否新用户”可以在添加前用PFCOUNT对比旧值但这不是原子操作并发下可能误判。更好的方案是额外用一个Set或者BitMap存用户是否首次访问的标记。HyperLogLog和BitMap配合可以玩出花活。比如用BitMap判断用户是否签到用HyperLogLog统计整体访问UV两者互不干扰但又能组合成活动分析面板。误差的0.81%是全局性的基数越小误差感知越明显。如果你的UV是几十的级别用HyperLogLog可能显示37但实际是38这种情况会被业务方诟病。数据量小于几千时不如直接用Set等大了再切HyperLogLog。项目里可以做一个简单的阈值路由小基数用精确Set达到几百上千再切HLL这样既保证小数据时准确又保证大数据时内存可控。5. 三种数据结构的选型对照与组合使用思路5.1 核心参数对比把GEO、BitMap、HyperLogLog放在一张表里看整个项目的技术选型逻辑会清晰很多维度GEOBitMapHyperLogLog底层数据结构ZSET跳表String字节数组固定寄存器数组核心应用场景附近的人/附近店铺/距离计算签到/打卡/在线状态/连续标记独立访客数统计/基数统计是否精确精确精确近似0.81%标准误差内存占用取决于元素数量每个元素约数十字节取决于最大偏移位数与元素数量无关固定约12KB查询类型范围查询、排序、距离计算位查询、位统计基数估算是否支持删除单个元素支持ZREM支持SETBIT置0不支持这张表在面试时也特别好用不管面试官问哪个结构你都可以把“底层结构”、“精确性”、“内存模型”三个维度回答得很清楚。5.2 实战中的选型决策方法我在写项目时给自己总结了一套选型判断流程遇到类似场景时可以套用。第一个问题数据之间有没有距离和范围的概念如果有比如按坐标检索附近对象那么直接选GEO不要考虑其他结构。第二个问题数据本质是“某个时刻是否存在”的布尔标记而且维度是有限的比如签到每天的日期、在线状态按分钟切分数据量很大且只需要标记存在与否选BitMap。它存储空间小、写入快、按位统计方便。第三个问题只需要知道“有多少”不需要知道“具体是谁”比如UV、PV去重、活动参与人数统计选HyperLogLog。特别是数据量可能达到百万、千万级时HyperLogLog是唯一能撑住内存的方案。如果遇到不确定的场景先从最朴素的业务语义出发你有没有精确获取用户ID列表的需求有就不考虑HyperLogLog你不关心具体ID只关心量HyperLogLog性价比极高。5.3 三个结构的组合玩法项目做到后期我发现这三个结构单独用都简单组合起来才是效率最大化。举一个实际例子运营后台想同时展示“今日访问UV”和“今日已签到用户数”以及“按距离优先推荐的店铺列表”。一个页面三个卡片分别调用HyperLogLog的PFCOUNT、BitMap的BITCOUNT、GEO的GEOSEARCH互不干扰三条命令并行执行返回结果组装一次API响应整个后台接口的逻辑清晰到一眼能看懂。还有一种更复杂的应用用HyperLogLog做流量初筛用BitMap做用户分层。比如先判断一个用户是否是本周活跃用户用HyperLogLog判断是否存在虽然不能准确告诉你是否存在但可以用BitMap为每个用户打标签按天记录是否活跃。HyperLogLog负责告诉你有多少人BitMap负责告诉你是哪一群人两个结构配合既拿到总量又拿到明细。另外GEO的数据结构底层是ZSET意味着你可以直接复用ZSET的所有能力。比如对某个商圈的热门店铺可以把店铺ID和评分映射到另一个ZSET里做排序再把GEO的距离排序和评分排序做加权组合成一个“综合排序”推荐结果。这个玩法比单纯的“按距离排”或者“按销量排”要高级不少项目里也是加分项。5.4 关于Redis版本和运维的一点经验GEO的GEOSEARCH命令是Redis 6.2才正式引入的如果你用的是6.0或者更早版本只能用GEORADIUS和GEORADIUSBYMEMBER。GEORADIUS在旧版本上也能完成大部分工作但命令语义没有新版本清晰分页和排序的参数也有一点差异。我的建议是项目环境能升级就升级到Redis 6.2以上不仅GEO更好用其他特性也会受益。内存监控方面运营环境一定要给这三个结构单独建监控key。我在一个环境里踩过一次坑HyperLogLog key没有设置过期时间运营每天写入大量UV数据两周后Redis内存从几百MB涨到几GB。排查下来全是两周前没清理的HLL key堆着。后来我写了一个定时任务每天凌晨清理所有超过30天的HLL key和BitMap key同时给GEO key设置一个较长的过期时间彻底解决问题。最后再分享一个小技巧。这三个结构写进项目笔记和简历时一定要突出“为什么不用传统方案”这是技术深度的表现。我在黑马点评项目中做附近商铺功能时特意对比了MySQL空间索引方案和Redis GEO方案做签到功能时对比了签到明细表方案和BitMap方案做UV统计时对比了Set方案和HyperLogLog方案。每次对比都会得到一组性能数据比如查询耗时、内存占用、并发吞吐量。把这些数据写进笔记里整个项目的技术含量会提升一个档次而不是停留在“我用Redis存了一个数据”这种浅层。实际敲代码的过程中我的体会是这三个Redis数据结构的命令本身都不复杂真正让人摔跟头的往往是细节——坐标顺序、offset从0还是从1开始、误差范围有多大、key怎么命名怎么过期。把这四类细节处理好了这些“不常用但一用就惊艳”的数据结构就成了你项目里最扎实的技术亮点。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

9DVR帽椅:沉浸式科普体验的技术解析与应用 2026/9/11 5:04:07

9DVR帽椅:沉浸式科普体验的技术解析与应用

1. 9DVR帽椅:重新定义沉浸式科普体验 在科技馆的角落里,一群孩子正戴着造型奇特的"帽子",身体随着画面不断倾斜转动,时而发出惊呼,时而开怀大笑。这不是什么魔法道具,而是最新一代的9DVR帽椅——…

阅读更多 →
W55MH32跑小智聊天机器人:嵌入式语音交互开发实战 2026/9/11 5:04:07

W55MH32跑小智聊天机器人:嵌入式语音交互开发实战

前阵子我把手头一个桌面小音响改造成了能聊天的语音助手,主控用的是 W55MH32,软件底座是社区里很火的小智聊天机器人项目。折腾了大概三周,踩了七八个坑,最后总算达到“喊一声就应答、闲聊不尬住”的状态。这篇文章就围绕这套组合…

阅读更多 →
嵌入式开发板完整使用流程:从串口调试到Qt部署 2026/9/11 5:04:07

嵌入式开发板完整使用流程:从串口调试到Qt部署

1. 开发板不是“插电就能跑”的玩具,而是嵌入式开发的最小完整系统 很多人第一次拿到开发板,第一反应是接上USB线、打开串口终端、敲个 ls ——然后发现什么都没输出,或者卡在U-Boot界面不动。我刚入行那会儿也这样,以为开发板和…

阅读更多 →
Linux解压命令从tar到7z:核心用法、算法选型与工程避坑指南 2026/9/11 5:04:07

Linux解压命令从tar到7z:核心用法、算法选型与工程避坑指南

这两年帮人排查过不少线上事故,发现一个很有意思的现象:很多人在 Linux 上解压文件靠的是肌肉记忆——看到 .tar.gz 就 tar -zxvf,看到 .zip 就 unzip,遇到 .7z 当场懵住。装 JDK、pnpm、RocketMQ 这类中间件,文档第一…

阅读更多 →
Novu自托管如何配置JWT_SECRET、STORE_ENCRYPTION_KEY等关键密钥 2026/9/11 5:04:07

Novu自托管如何配置JWT_SECRET、STORE_ENCRYPTION_KEY等关键密钥

Novu自托管如何配置JWT_SECRET、STORE_ENCRYPTION_KEY等关键密钥 【免费下载链接】novu The open-source communication infrastructure for agents and products 项目地址: https://gitcode.com/GitHub_Trending/no/novu 用 Docker Compose 自托管 Novu 时,…

阅读更多 →
CMSIS-FreeRTOS源码深度解析:架构、隐式依赖与工程避坑指南 2026/9/11 5:01:06

CMSIS-FreeRTOS源码深度解析:架构、隐式依赖与工程避坑指南

1. 项目概述:为什么CMSIS-FreeRTOS值得你花三天时间逐行读完它的源码我第一次在STM32F407上跑通CMSIS-FreeRTOS的hello world时,以为自己已经“掌握”了RTOS。直到半年后,一个电机控制任务在高负载下出现毫秒级的调度延迟,中断嵌套…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞