百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis Hash类型的坑之单个key中field过多

mhr18 2024-11-23 19:22 21 浏览 0 评论

对投票数据统计的时候发现了Redis Hash类型的一个大坑,单个key中field过多,导致取不出来。特记录下尝试解决和探索的过程。

第一阶段:问题描述

一个投票类的产品,对单个选项mid投票成功后,记录了总票数,还记录了用户投票日志(可以理解成投票明细),用的都是Redis Hash类型来存储。投票日志的存储格式如下:

1

$redis->hset('vote_log', 'uid|mid|timestamp', 1);

最近,运营需要统计固定时间段每个选项投票的用户数,要的比较急,就很快写了一个脚本
算法:

0、全部取不来
1、第一次遍历,筛选出mid
2、第二次遍历,筛选出每个mid对应的uid
3、去重
4、统计数量
运行就报错:


脚本的使用内存已经设定为1024M了,查看Redis.php中163行使用的是hGetAll获取全量数据,估计是数据太大,登陆上Redis 使用hlens显示2700W,确实太大了,改为hKeys获取还是一样报错。Goolge搜索了一番,发现唯一的解决方案就是使用hscan, 测试发现线上redis server版本2.4不支持hscan(hscan命令版本要求>=2.8), 顿时都绝望了,跟运营反馈数据存储集中,并且数量比较大,传统的方案统计不出来,需要时间用其他方式来处理。好在,运营了解情况后,表示可以不用统计用户数。

第二阶段:探索解决方案

但是技术上这个问题并没有解决,如何解决这个问题呢

目前分析思考结果如下:
0、使用内存足够的机器跑脚本
1、使用hscan[自己的阿里云上redis server version 3.2.11测试了hscan确实可以分页取数据]
第一种:升级redis server version到2.8以上
第二种:导出redis key,导入高版本redis server中

具体实施的解决方案,还在探索中…(redis hash key导出在尝试中,如果大家有好的建议,欢迎留言)

针对hscan这里有一个地方需要格外注意(scan不存在这个问题)

观察下面几条命令,我们看到vote_info中现在有4个键值对,但是我们设置hscan的count为2,还是返回了全部内容,并不是预期的2条

我们知道Redis Hashes是由ziplist(压缩列表)和字典(Dict)两种编码方式实现,当我们创建一个空的Hashes的时候使用的ziplist编码, 当某个键或某个值的长度大于hash_max_ziplist_value设定的值,会切换的Dict编码,还有一种情况也会切换就是ziplist的entries(节点数)大于hash_max_ziplist_entries。hash_max_ziplist_value和 hash_max_ziplist_entries在redis.conf中设置,默认值分别是512和64。

hash-max-zipmap-entries 512 (hash-max-ziplist-entries for Redis >= 2.6)
hash-max-zipmap-value 64  (hash-max-ziplist-value for Redis >= 2.6)

查看redis scan 文档,Hashes使用ziplist编码的时候,通常忽略count参数,直接返回全部元素。

打开redis.conf, 把hash_max_ziplist_entries修改为10,hset多个元素,直到hlen为11的时候,count才生效,观察下面一组命令

按照上面的实验,ziplist中一对key-value算一个entries,没有找到理论说明, 参考《Redis设计与实现第一版》中的结构

一个ziplist的分布结构:

key-value一同压入ziplist后的结构:

测试遗留问题:当hdel一条记录后,hscan的count选项还是生效,返回的数量也有异常,暂未找到原因


第三阶段:优化存储结构,避免问题

去年PHP开发者大会上,记得鸟哥说,避免问题也是一种解决问题的好办法。

如何存储来避免这种问题呢
方案一:存储key的优化, 按照mid来拆分key

1

$redis->hset('vote_log_mid', 'uid|timestamp', 1);

方案二:存储key的优化,按天来存

1

2

3

4

5

$redis->hIncrBy('vote_log_mid_20180718', 'uid', 1);

$redis->hIncrBy('vote_log_20180718', 'uid', 1);

方案三:redis2mysql, 直接存到mysql中
–表结构

1

2

3

4

5

6

7

8

9

10

11

12

CREATE TABLE `vote_log` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',

`aid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动ID',

`feed_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'feed id',

`mid` bigint(11) unsigned NOT NULL DEFAULT '0' COMMENT 'mid',

`uid` bigint(11) unsigned NOT NULL DEFAULT '0' COMMENT 'uid',

`create_time` bigint(11) unsigned NOT NULL DEFAULT '0' COMMENT '投票时间',

`ext` varchar(64) NOT NULL DEFAULT '' COMMENT '扩展字段',

PRIMARY KEY (`id`),

KEY `key_a_f_c` (`aid`,`feed_id`,`create_time`),

KEY `key_a_f_m` (`aid`,`feed_id`,`mid`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='投票日志';

结合实际情况,虽然方案一和方案二改造起来很方便,这个日志数据并不需要实时读取,放在Redis中有对Redis误用乱用的嫌疑,也并不方便运营未知的统计需求。存到mysql中开始考虑到按照日期或者活动id来分表,查看发现不是每个月都有这样的投票活动,也不是每个活动都有投票,完全可以存一张表,等到时候数据太大了,可以写脚本清历史数据,或者手动清。

以上,选中了方案三。

其他已知的方案:
拆分key, 参考《Redis单key值过大,优化方式》
单独存一份field,参考《Redis Hash结构遍历某一个key下所有field value的方法》

第四阶段:思考总结

在解决问题的过程中,发现很多朋友遇到了类似的问题,确实值得我们深思,在当初设计存储的时候,必须要考虑到这种情况,最好的解决办法还是设计阶段提前预判和规避,就像昨天法国VS克罗地亚的世界杯决赛上解说说的,追不上姆巴佩,提前预判他的路线,打断他的进攻,不给发挥速度的机会,这也是架构设计的意义吧。

最后补充一个有趣的发现,删除hash总最后一个field后,hash key也会被删除

1

2

3

4

5

6

7

8

9

10

redis 10.235.25.242:6379> hmset salmonl_20190514 id 100 type 1

OK

redis 10.235.25.242:6379> hdel salmonl_20190514 id

(integer) 1

redis 10.235.25.242:6379> exists salmonl_20190514

(integer) 1

redis 10.235.25.242:6379> hdel salmonl_20190514 type

(integer) 1

redis 10.235.25.242:6379> exists salmonl_20190514

(integer) 0

(全文完)

参考资料:
https://redis.io/topics/memory-optimization
https://redis.io/commands/scan#the-count-option
http://origin.redisbook.com/compress-datastruct/ziplist.html#id2
https://stackoverflow.com/questions/34503876/redis-hscan-command-cannot-limit-the-counts

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: