首页 » 网站推广 » phpfieldvalue技巧_Redis Hash类型的坑之单个key中field过多

phpfieldvalue技巧_Redis Hash类型的坑之单个key中field过多

访客 2024-12-18 0

扫一扫用手机浏览

文章目录 [+]

第一阶段:问题描述

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

phpfieldvalue技巧_Redis Hash类型的坑之单个key中field过多

1

phpfieldvalue技巧_Redis Hash类型的坑之单个key中field过多
(图片来自网络侵删)

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

最近,运营须要统计固定时间段每个选项投票的用户数,要的比较急,就很快写了一个脚本算法:0、全部取不来1、第一次遍历,筛选出mid2、第二次遍历,筛选出每个mid对应的uid3、去重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-optimizationhttps://redis.io/commands/scan#the-count-optionhttp://origin.redisbook.com/compress-datastruct/ziplist.html#id2https://stackoverflow.com/questions/34503876/redis-hscan-command-cannot-limit-the-counts

标签:

相关文章

php常量率低技巧_PHP 常量详解教程

PHP 常量常量是单个值的标识符(名称)。在脚本中无法改变该值。有效的常量名以字符或下划线开头(常量名称前面没有 $ 符号)。注释...

网站推广 2024-12-19 阅读0 评论0