Redis的String类型,原来这么占内存
1、场景介绍
假设现在我们要开发一个图片存储系统,要求这个系统能够根据图片 ID 快速查找到图片存储对象 ID。图片 ID 和图片存储对象 ID 的样例数据如下: photo_id: 1101000060 photo_obj_id: 3302000080
在这种场景下,图片 ID 和图片存储对象 ID 刚好是一对一的关系,是典型的"键 - 单值"模式,Redis 的 String 类型提供了"一个键对应一个值的数据"的保存形式,在这种场景下刚好适用。
确定使用 String 类型后,接下来我们通过实战,来看看它的内存使用情况。首先通过下面命令连接上 Redis。
本文我使用的 Redis Server 及下文源码都是 6.2.4 版本。 redis-cli -h 127.0.0.1 -p 6379
然后执行下面的命令查看 Redis 的初始内存使用情况。 127.0.0.1:6379> info memory # Memory used_memory:871840
接着插入 10 条数据: 10.118.32.170:0> set 1101000060 3302000080 10.118.32.170:0> set 1101000061 3302000081 10.118.32.170:0> set 1101000062 3302000082 10.118.32.170:0> set 1101000063 3302000083 10.118.32.170:0> set 1101000064 3302000084 10.118.32.170:0> set 1101000065 3302000085 10.118.32.170:0> set 1101000066 3302000086 10.118.32.170:0> set 1101000067 3302000087 10.118.32.170:0> set 1101000068 3302000088 10.118.32.170:0> set 1101000069 3302000089
再次查看内存: 127.0.0.1:6379> info memory # Memory used_memory:872528
可以看到,存储 10 个图片,内存使用了 688 个字节。一个图片 ID 和图片存储对象 ID 的记录平均用了 68 字节。
但问题是,一组图片 ID 及其存储对象 ID 的记录,实际只需要 16 字节就可以了。图片 ID 和图片存储对象 ID 都是 10 位数,而 8 字节的 Long 类型最大可以表示 2 的 64 次方的数值,肯定可以表示 10 位数。这样算下来只需 16 字节就可以了,为什么 String 类型却用了 68 字节呢?
为了一探究竟,我们不得不从 String 类型的底层实现扒起。 2、String 类型的底层实现
当你保存的数据中包含字符时,String 类型就会用简单动态字符串(Simple Dynamic String,SDS)结构体来保存。 2.1 SDS
SDS 的结构定义在 sds.h 文件中,在 Redis 3.2 版本之后,SDS 由一种数据结构变成了 5 种数据结构。 /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) hisdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; };
这 5 种数据结构依次存储不同长度的内容,Redis 会根据 SDS 存储的内容长度来选择不同的结构。 • sdshdr5:存储大小为 32 字节(2 的 5 次方),只被应用在了 Redis 中的 key 中。 • sdshdr8:存储大小为 256 字节(2 的 8 次方)。 • sdshdr16:存储大小为 64KB(2 的 16 次方)。 • sdshdr32:存储大小为 4GB(2 的 32 次方)。 • sdshdr64:存储大小为 2 的 64 次方字节。
以 sdshdr8 为例。
• buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个 " " ,这就会额外占用 1 个字节的开销。 • len:占 4 个字节,表示 buf 的已用长度,不包括 " " 。 • alloc:也占 4 个字节,表示 buf 的实际分配长度,不包括 " " 。 • flags:占 1 个字节,标记当前字节数组的属性,是 sdshdr8 还是sdshdr16 等。(flags 值的定义可以看下面代码)
在源码 sds.h 中,flags 值定义如下: #define HI_SDS_TYPE_5 0 #define HI_SDS_TYPE_8 1 #define HI_SDS_TYPE_16 2 #define HI_SDS_TYPE_32 3 #define HI_SDS_TYPE_64 42.2 RedisObject
因为 Redis 的数据类型有很多,而且,不同数据类型都有些相同的元数据要记录,所以,值对象并不是直接存储,而是被包装成 redisObject 对象,它的定义如下。 typedef struct redisObject { unsigned type:4;//对象类型(4位=0.5字节) unsigned encoding:4;//编码(4位=0.5字节) unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间(24位=3字节) int refcount;//引用计数。等于0时表示可以被垃圾回收(32位=4字节) void *ptr;//指向底层实际的数据存储结构,如:sds等(8字节) } robj;
下面可以帮助我们理解:
为了节省内存空间,Redis 还做了一些优化。
当保存的是 Long 类型整数时,RedisObject 中的指针就直接赋值为整数数据了,这样就不用额外的指针再指向整数了。这种保存方式通常也叫作 int 编码方式。
当保存的是字符串数据,并且字符串小于等于 44 字节时,RedisObject 中的元数据、指针和 SDS 是一块连续的内存区域,这样就可以避免内存碎片。这种布局方式也被称为 embstr 编码方式。
当字符串大于 44 字节时,SDS 的数据量就开始变多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。这种布局方式被称为 raw 编码模式。
使用 OBJECT ENCODING 命令可以查看一个数据库键的值对象的编码: 127.0.0.1:6379> SET msg "hello world" OK 127.0.0.1:6379> OBJECT ENCODING msg "embstr" 127.0.0.1:6379> SET story "long long long ago..." OK 127.0.0.1:6379> OBJECT ENCODING story "raw" 127.0.0.1:6379> SADD numbers 1 3 5 (integer) 3 127.0.0.1:6379> OBJECT ENCODING numbers "intset" 127.0.0.1:6379> SADD numbers "seven" (integer) 1 127.0.0.1:6379> OBJECT ENCODING numbers "hashtable"
注意这个命令 SET story "long long long ago..." ,省略号指的是省略了很多字符。
知道了 SDS 和 RedisObject 额外元数据开销,现在,我们就可以计算 String 类型的内存使用量了。
图片存储对象 ID 是 Long 类型整数,所以可以直接用 int 编码的 RedisObject 保存。每个 int 编码的 RedisObject 元数据部分占 8 字节,指针部分被直接赋值为 8 字节的整数了。图片 ID 使用 sdshdr5 数据结构来保存,会为 10 位的图片 ID 分配 16 个字节,结束符 " " 占 1 个字节。
共占用 34 个字节。与上文所说的一个图片 ID 和图片存储对象 ID 的记录平均用了 68 字节相差有点大啊,另外的开销去哪儿了? 2.3 全局哈希表
为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。因为这个哈希表保存了所有的键值对,所以,也称为全局哈希表。哈希表的每一项是一个 dictEntry 的结构体,用来指向一个键值对。dictEntry 结构中有三个 8 字节的指针,分别指向 key、value 以及下一个 dictEntry,三个指针共 24 字节,如下图所示:
jemalloc 在分配内存时,会分配一个最接近 2 的 N 次方的数值。举个例子。如果你申请 6 字节空间,jemalloc 实际会分配 2 的 4 次方即 8 字节空间;如果你申请 24 字节空间,jemalloc 则会分配 32 字节。
最终我们分析出来的内存开销,为 66 字节,比较接近上文场景中的平均值 68 了。 最后
既然 String 类型这么占内存,那么你有好的方案来节省内存吗?
这篇文章内容我准备了一周,如果对你有帮助,可以点个赞吗?你的点赞会让作者兴奋得一晚上睡不着觉。
对后面的内容感兴趣,也可以关注公众号「杨同学technotes」,感谢支持! 参考资料• 文中的一些命令,参考菜鸟教程:https://www.runoob.com/redis/redis-tutorial.html • Redis 的 key 也是 SDS 类型的,参考:https://www.cnblogs.com/lonely-wolf/p/14261486.html • SDS 的定义,参考:https://juejin.cn/post/6844903936520880135#heading-6 • 文章大纲,参考极客时间《Redis核心技术与实战》 • 《Redis设计与实现》
手握50万辆订单,比亚迪年内已官宣涨价三次,累计最高涨1。9万元11月23日,比亚迪官方发布产品价格调整通知针对王朝海洋及腾势相关新能源车型的官方指导价进行调整,上调幅度为2000元6000元不等。今年1月21日和3月15日,比亚迪已经完成两次
江苏将诞生第二个苏州,打造1心3带5组群,晋级省域副中心近年来,随着我国经济结构的不断调整,区域经济发展格局也在发生变化,以东部为引擎中部提升西部追赶东北全面振兴的发展态势基本形成。具体来看,长三角珠三角京津冀依然是推动我国经济发展的三
量化投资的未来理解和应对不确定性01先给大家讲个故事。1940年代二战期间,德国开发了恩格玛机的加密密码器。因为密码机加密方式的复杂性和情报的及时性,破解密码的时间理论上只有24小时。如果第一天没有完全破解的话,
和平方舟用爱温暖了我们当地时间11月18日10时,中国海军和平方舟号医院船离开印尼首都雅加达丹戎不碌港起程回国。图为欢送仪式现场,任务官兵向送行人员挥手告别。孙飞摄视频加载中11月的印度尼西亚首都雅加达
延边足球期待重焕光芒写在延边龙鼎冲甲成功之后新华社长春11月24日电(记者周万鹏)在2022赛季中乙联赛中,延边龙鼎队最终以第三名的身份收获2023年中甲联赛的入场券。历经1357天的漫长等待,那个令无数球迷朝思暮想的延边队
多省份启动2023年公务员招录多地专设岗位招录残疾人下个月,2023年度国考笔试即将开考,近期,江苏山东等地2023年度公务员省考招录也已陆续启动。各地均强调加大对高校应届毕业生和基层岗位的支持力度,多地还专设岗位面向残疾人招录。资
录取比例41,日本年轻人为何不爱考公务员?(ICphoto图)在一般的印象中,考公应该是一件越来越难越来越卷的事情,但在日本,情况截然相反。近年来,日本报考公务员的人数在逐年减少。根据日本人事院统计,日本国家公务员考试报考
51岁知名男星成为公务员,此前还获得北大硕士学位头条创作挑战赛111月15日,前TVB演员马浚伟在社交网站发文,他开心写道一个已经生效的公职,我会努力,会很尽力地努力,还晒出了委任信,已经51岁的马浚伟入职香港艺术发展局演艺专业
浙江帅哥辞职公务员当和尚,女香客送400万法拉利求婚,如今怎样2022年11月7日,在浙江某地的篮球场上,出现了一名出家的和尚。他在原地待了几分钟后,便借来了便装,最后,当散场的时候,他就把自己打球的视频发送到了某网络社交平台。很快,不到一个
专家答疑如何化解考前焦虑?科学家教育儿育己11月,学生们迎来了期中考试。我们邀请江苏省大丰高级中学心理教师于燕支招儿,帮助学生化解考试焦虑。如何化解考前焦虑?Q我儿子读小学五年级,成绩处于班级中上游。前段时
可靠的,终究是他们几个库里推进,45汤神跟进,后者追身拔进三分。球入网窝前0。2秒,库里转身,手指天花板。球馆爆发山呼海啸,无助的对手教练请求暂停。库里示意全场,还可以再躁一点。我并不是在描述某个具体回
考生在省排名多少名,可能被中国科学技术大学录取?中国科学技术大学是中国科学院所属的一所以前沿科学和高新技术为主兼有特色管理和人文学科的综合性全国重点大学,是国内C9华东五校重要成员之一!尽管中科大办学在在合肥,但还是阻挡不了考生
义务兵选什么兵好,有前途又好留队?高中毕业,拿到大学毕业证准备当兵,这个时候,就是以高校新生的身份参军入伍,同样享受大学生入伍的相关优惠政策,记得办理好保留学籍,这样一来能够给自己的留一条退路。选什么兵种好?哪个兵
退休证有三种颜色,分别代表什么意思?人活着的这一辈子每个人都是要工作的,但是工作的时候这个时间不是长期的,人不可能一辈子都上班的,总是会要老去的那么这个时候会退休的。退休的时候都是会有一个退休证的,虽说都是退休证但是
我大姨丁克,60岁了,想让我养老,我该怎么办?我姨妈,今年65岁也是丁克,确切的说是她没有办法生孩子。老公早早的和她分开了,外公外婆过世后她就很孤单,想在我们这些孩子里面选一个人给她养老,没有想到她竟然选中了我。我姨妈做事业还
专科有没有军校,如果有一般需要多少分?不请自来的回答,我首先告诉你一个事实。现在专科没有军官学校,只有士官学校,专科军官学校只针对特定的少数民族军政干部,比如我在西藏时,我们排长和连长都是从昆陆出来的少尉排长,而且现在
三支一扶是属于什么编制?考上三支一扶之后是没有正式编制的,既不属于公务员,也不属于事业编。三支一扶在服务期内其实跟临时聘用工区别不大。工资比较低,但是给缴纳五险一金。三支一扶的服务期一般是2年,如果经考核
宝鸡目前最好的地段买房是哪里,综合,或者直接说楼盘?如果是纯居住清姜还是不错,基础设施齐全,没有污染企业,居民大都是国企人员素质好,物价不高交通便利。宝鸡市的房价总体上来说很低,加上地价建材一路疯涨,可以说宝鸡的房价涨是必然。如果是
出租车司机的收入如何?出租车司机的收入到底多少,就像你问明星月收入多少一样,没人会诚实暴露自己的收入,我包过一台出租车,月交两千八,每天自己六点起床洗漱完了,吃完早餐,六点四十出车,跑到九点多回家准备做
银行基层员工月薪不足3000元,你相信吗?不相信!月薪不到5000元,敢说自己在银行工作?在大家印象里,银行和电力局应该属于挣钱比较多又比较轻松的国企了吧。公务员和事业单位是人们都向往的职业,但是在银行和电力局面前比起来应
有人退休后为什么和同事都不想再联系了?前几年身边的老同事陆续退休回归家庭,我因为在部门一批人中年龄较小晚于大家五至十年才退休。因此回归社会后第一时间加入了前同事们自组的微信群,在群里热情的与大家打招呼,却没有几个人回应
我在男朋友妈妈的公司上班,拿最低的工资3500元,同职位的同事工资有6500元。我可以申请加工资吗?如果你有能力,那为什么要在他妈妈的公司上班呢?别人6500人家他妈妈肯定认为值,而你3500明显你男朋友妈妈认为你就值这么多,否则肯定会主动给你加薪的呀,你现在应该提出辞职才对,人