本文主要参考<>一书,主要分为以下六个部分:

1.redisObject对象

2.缩减键值对象

3.共享对象池

4.字符串优化

5.编码优化

6.控制key的数量

一. redisObject对象

Redis存储的所有值对象在内部定义为redisObject结构体,内部结构如下图所示。

Redis存储的数据都使用redisObject来封装,包括string,hash,list,set,zset在内的所有数据类型。理解redisObject对内存优化非常有帮助,下面针对每个字段做详细说明:

1.type字段:

表示当前对象使用的数据类型,Redis主要支持5种数据类型:string,hash,list,set,zset。可以使用type {key}命令查看对象所属类型,type命令返回的是值对象类型,键都是string类型。

2.encoding字段:

表示Redis内部编码类型,encoding在Redis内部使用,代表当前对象内部采用哪种数据结构实现。理解Redis内部编码方式对于优化内存非常重要 ,同一个对象采用不同的编码实现内存占用存在明显差异,具体细节见之后编码优化部分。

3.lru字段:

记录对象最后一次被访问的时间,当配置了 maxmemory和maxmemory-policy=volatile-lru | allkeys-lru 时, 用于辅助LRU算法删除键数据。可以使用object idletime {key}命令在不更新lru字段情况下查看当前键的空闲时间。

开发提示:可以使用scan + object idletime 命令批量查询哪些键长时间未被访问,找出长时间不访问的键进行清理降低内存占用。

4.refcount字段:

记录当前对象被引用的次数,用于通过引用次数回收内存,当refcount=0时,可以安全回收当前对象空间。使用object refcount {key}获取当前对象引用。当对象为整数且范围在[0-9999]时,Redis可以使用共享对象的方式来节省内存。具体细节见之后共享对象池部分。

5. *ptr字段:

与对象的数据内容相关,如果是整数直接存储数据,否则表示指向数据的指针。Redis在3.0之后对值对象是字符串且长度<=39字节的数据,内部编码为embstr类型,字符串sds和redisObject一起分配,从而只要一次内存操作。

开发提示:高并发写入场景中,在条件允许的情况下建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数从而提高性能。

二. 缩减键值对象

降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。

key长度:如在设计键时,在完整描述业务情况下,键值越短越好。

value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。

开发提示:当频繁压缩解压json等文本数据时,开发人员需要考虑压缩速度和计算开销成本,这里推荐使用google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具,且支持所有主流语言环境。

三. 共享对象池

对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。

整数对象池在Redis中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount 命令查看对象引用数验证是否启用整数对象池技术,如下:

redis> set foo 100 OKredis> object refcount foo(integer) 2redis> set bar 100OKredis> object refcount bar(integer) 3

设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3。

为什么只有整数对象池?

首先整数对象池复用的几率最大,其次对象共享的一个关键操作就是判断相等性,Redis之所以只有整数对象池,是因为整数比较算法时间复杂度为O(1),只保留一万个整数为了防止对象池浪费。如果是字符串判断相等性,时间复杂度变为O(n),特别是长字符串更消耗性能(浮点数在Redis内部使用字符串存储)。对于更复杂的数据结构如hash,list等,相等性判断需要O(n 2 )。对于单线程的Redis来说,这样的开销显然不合理,因此Redis只保留整数共享对象池。

四. 字符串优化

字符串对象是Redis内部最常用的数据类型。所有的键都是字符串类型, 值对象数据除了整数之外都使用字符串存储。比如执行命令:lpush cache:type “redis” “memcache” “tair” “levelDB” ,Redis首先创建”cache:type”键字符串,然后创建链表对象,链表对象内再包含四个字符串对象,排除Redis内部用到的字符串对象之外至少创建5个字符串对象。可见字符串对象在Redis内部使用非常广泛,因此深刻理解Redis字符串对于内存优化非常有帮助:

1.字符串结构

Redis没有采用原生C语言的字符串类型而是自己实现了字符串结构,内部简单动态字符串(simple dynamic string),简称SDS。结构下图所示。

Redis自身实现的字符串结构有如下特点:

O(1)时间复杂度获取:字符串长度,已用长度,未用长度。

可用于保存字节数组,支持安全的二进制数据存储。

内部实现空间预分配机制,降低内存再分配次数。

惰性删除机制,字符串缩减后的空间不释放,作为预分配空间保留。

2.预分配机制

字符串预分配每次并不都是翻倍扩容,空间预分配规则如下:

1) 第一次创建len属性等于数据实际大小,free等于0,不做预分配。

2) 修改后如果已有free空间不够且数据小于1M,每次预分配一倍容量。如原有len=60byte,free=0,再追加60byte,预分配120byte,总占用空间:60byte+60byte+120byte+1byte。

3) 修改后如果已有free空间不够且数据大于1MB,每次预分配1MB数据。如原有len=30MB,free=0,当再追加100byte ,预分配1MB,总占用空间:1MB+100byte+1MB+1byte。

开发提示:尽量减少字符串频繁修改操作如append,setrange, 改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化。

3.字符串重构

字符串重构:指不一定把每份数据作为字符串整体存储,像json这样的数据可以使用hash结构,使用二级结构存储也能帮我们节省内存。同时可以使用hmget,hmset命令支持字段的部分读取修改,而不用每次整体存取。

五. 编码优化

1.了解编码

Redis对外提供了string,list,hash,set,zet等类型,但是Redis内部针对不同类型存在编码的概念,所谓编码就是具体使用哪种底层数据结构来实现。编码不同将直接影响数据的内存占用和读写效率。使用object encoding {key}命令获取编码类型。

Redis针对每种数据类型(type)可以采用至少两种编码方式来实现,下表表示type和encoding的对应关系。

表:type和encoding对应关系表

了解编码和类型对应关系之后,我们不禁疑惑Redis为什么需要对一种数据结构实现多种编码方式?

主要原因是Redis作者想通过不同编码实现效率和空间的平衡。比如当我们的存储只有10个元素的列表,当使用双向链表数据结构时,必然需要维护大量的内部字段如每个元素需要:前置指针,后置指针,数据指针等,造成空间浪费,如果采用连续内存结构的压缩列表(ziplist),将会节省大量内存,而由于数据长度较小,存取操作时间复杂度即使为O(n2)性能也可满足需求。

2.控制编码类型

编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。

3.ziplist编码

ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。首先从ziplist编码结构开始分析,它的内部结构类似这样: 。一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组),内部结构如下图所示。

ziplist结构字段含义:

1) zlbytes:记录整个压缩列表所占字节长度,方便重新调整ziplist空间。类型是int-32,长度为4字节

2) zltail:记录距离尾节点的偏移量,方便尾节点弹出操作。类型是int-32,长度为4字节

3) zllen:记录压缩链表节点数量,当长度超过216-2时需要遍历整个列表获取长度,一般很少见。类型是int-16,长度为2字节

4) entry:记录具体的节点,长度根据实际存储的数据而定。

a) prev_entry_bytes_length:记录前一个节点所占空间,用于快速定位上一个节点,可实现列表反向迭代。

b) encoding:标示当前节点编码和长度,前两位表示编码类型:字符串/整数,其余位表示数据长度。

c) contents:保存节点的值,针对实际数据长度做内存占用优化。

5) zlend:记录列表结尾,占用一个字节。

根据以上对ziplist字段说明,可以分析出该数据结构特点如下:

1) 内部表现为数据紧凑排列的一块连续内存数组。

2) 可以模拟双向链表结构,以O(1)时间复杂度入队和出队。

3) 新增删除操作涉及内存重新分配或释放,加大了操作的复杂性。

4) 读写操作涉及复杂的指针移动,最坏时间复杂度为O(n2)。

5) 适合存储小对象和长度有限的数据。

开发提示:

1)针对性能要求较高的场景使用ziplist,建议长度不要超过1000,每个元素大小控制在512字节以内。

2)命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数,总耗时,平均耗时,单位微秒。

4.intset编码

intset编码是集合(set)类型编码的一种,内部表现为存储有序,不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。执行以下命令查看intset表现:

127.0.0.1:6379> sadd set:test 3 4 2 6 8 9 2(integer) 6 //乱序写入6个整数127.0.0.1:6379> object encoding set:test"intset" //使用intset编码127.0.0.1:6379> smembers set:test"2" "3" "4" "6" "8" "9" // 排序输出整数结合redis> config set set-max-intset-entries 6OK //设置intset最大允许整数长度redis> sadd set:test 5(integer) 1 //写入第7个整数 5redis> object encoding set:test"hashtable" // 编码变为hashtableredis> smembers set:test"8" "3" "5" "9" "4" "2" "6" //乱序输出

以上命令可以看出intset对写入整数进行排序,通过O(log(n))时间复杂度实现查找和去重操作,intset编码结构如下图所示。

intset的字段结构含义:

1) encoding:整数表示类型,根据集合内最长整数值确定类型,整数类型划分三种:int-16,int-32,int-64。

2) length:表示集合元素个数。

3) contents:整数数组,按从小到大顺序保存。

intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。升级操作将会导致重新申请内存空间,把原有数据按转换类型后拷贝到新数组。

开发提示:使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。

六 控制key的数量

当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash,list,set,zset 等结构。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。如下图所示,通过在客户端预估键规模,把大量键分组映射到多个hash结构中降低键的数量。

hash结构降低键数量分析:

1.根据键规模在客户端通过分组映射到一组hash对象中,如存在100万个键,可以映射到1000个hash中,每个hash保存1000个元素。

2.hash的field可用于记录原始key字符串,方便哈希查找。

3.hash的value保存原始值对象,确保不要超过hash-max-ziplist-value限制。

关于hash键和field键的设计:

1) 当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480 哈希key=group:hash:1948,哈希field=480。

2) 当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。

3) 尽量减少hash键和field的长度,如使用部分键内容。

使用hash结构控制键的规模虽然可以大幅降低内存,但同样会带来问题,需要提前做好规避处理。如下:

1.客户端需要预估键的规模并设计hash分组规则,加重客户端开发成本。

2.hash重构后所有的键无法再使用超时(expire)和LRU淘汰机制自动删除,需要手动维护删除。

3.对于大对象,如1KB以上的对象。使用hash-ziplist结构控制键数量。

4.不过瑕不掩瑜,对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。

开发提示:使用ziplist+hash优化keys后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用hscan命令扫描数据,找出hash内超时的数据项删除即可。

redis存储对象_redis内存优化总结相关推荐

  1. Redis 存储对象信息是用 Hash 还是 String

    Redis 内部使用一个 RedisObject 对象来表示所有的 key 和 value,RedisObject 中的 type,则是代表一个 value 对象具体是何种数据类型,它包含字符串(St ...

  2. redis存储对象和集合

    使用redis存储对象或集合时,不能直接存储.需要将对象或集 合通过序列化转换为可存储的json,这里使用了fastjson来转型 redis是一个key-value存储系统.和Memcached类似 ...

  3. redis存储对象(通过JSON方式)

    redis存储对象 在Java程序中,redis提供的方法插入的key,value要么是string,要么就是byte[]数组.那如果是要插入的value是个对象怎么办呢?网上的方法大多是对这个对象进 ...

  4. redis循环键_Redis 性能优化的 13 条军规!史上最全

    Redis性能优化实战方案 Redis 是基于单线程模型实现的,也就是 Redis 是使用一个线程来处理所有的客户端请求的,尽管 Redis 使用了非阻塞式 IO,并且对各种命令都做了优化(大部分命令 ...

  5. netcore redis 存储集合_Redis的简单入门

    Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久 ...

  6. redis存储数据类型_Redis与Memcahe的区别最全整理

    经常看到大家只能对比redis和memecache的前两项区别[持久化]和[数据类型],这里,给整理了最全的memcache和redis的区别,欢迎交流哦 缓存 (1)[持久化能力]Redis支持持久 ...

  7. Java操作Redis存储对象类型数据

    背景描述 关于JAVA去操作Redis时,如何存储一个对象的数据,这是大家非常关心的问题,虽然官方提供了存储String,List,Set等等类型,但并不满足我们现在实际应用.存储一个对象是非常常见的 ...

  8. Redis存储对象的三种方式

    存储对象的三种方式分别为: 1.将对象序列化后保存到Redis 2.将对象用FastJSON转为JSON字符串后存储 3.将对象用Hash数据类型存储 序列化工具类SerializeUtil publ ...

  9. redis存储二进制对象 python_python redis存储对象

    往往我们存放redis的时候都是字符串,可有时候我们也想存储对象怎么办? 首先我们构造对象:class Duankeke(object): def keke(self): print 33333下面演 ...

最新文章

  1. 另存为里面没有jpg_选用打印捕捉功能将 SolidWorks 画面另存为JPG格式高清大图
  2. 高压桥电路-驱动旧式电话机振铃
  3. Kendo Web UI Grid数据绑定,删除,编辑,并把默认英文改成中文
  4. 1090 危险品装箱 (25 分)
  5. jQuery之简单的表单验证
  6. kindeditor编辑器和图片上传独立分开的配置细节
  7. Java启动参数与内存调优一些学习笔记
  8. Python random() 函数
  9. LQR轨迹跟踪算法Python算法实现3
  10. [Diary]忧伤,止不住忧伤……
  11. python分页技术
  12. 网页错误在线检测网站汇总
  13. 时区的转换nbsp;格林尼治标准时间(GMT…
  14. 神经网络拟合函数表达式,神经网络拟合函数matlab
  15. N皇后问题 - 构造法原理与证明: 时间复杂度O(1)
  16. OPENSTREETMAP电力数据的情况
  17. 企业债券的发行与承销
  18. 小米笔记本装linux教程视频教程,小米笔记本安装Ubuntu 18.04系统后使用特殊键的方法...
  19. 老调重弹之Java引用类型
  20. 智能编码助手Copilot

热门文章

  1. 分类问题处理整体正规流程
  2. 全面的SVM理论讲解
  3. jsp和html的作用域,JSP(二):JSP九大内置对象、四个作用域对象
  4. python封装第三方接口_PYTHON 实现 SIGN 签名: 用于提供给外部(第三方) 做系统对接...
  5. 在Java eclipse 中Spring Boot工具安装步骤
  6. 4K视频在线看,网速跟不上怎么办?
  7. 十 suprocess模块
  8. Luogu2467 SDOI2010 地精部落 DP
  9. html兼容手机浏览器
  10. python3练习-查找文件