使用 String 类型内存开销大

如果我们有大量的数据需要来保存,在选型数据类型我们就需要知道 String 的内存开销是很大的

这里我们来分析下使用一个 String 类型需要用到的内存

1、简单动态字符串

Redis 中的 String,使用的是简单动态字符串(Simple Dynamic Strings,SDS)。

来看下数据结构

  1. struct sdshdr {
  2. // 记录 buf 数组中已使用字节的数量
  3. // 等于 SDS 保存字符串的长度,不包含'\0'
  4. long len;
  5. // 记录buf数组中未使用字节的数量
  6. long free;
  7. // 字节数组,用于保存字符串
  8. char buf[];
  9. };

如果,使用 SDS 存储了一个字符串 hello,对应的 len 就是5,同时也申请了5个为未使用的空间,所以 free 就是5。对于 buf 来说,len 和 free 的内存占用都是额外开销。

2、RedisObject

因为 Redis 中有很多数据类型,对于这些不同的数据结构,Redis 为了能够统一处理,所以引入了 RedisObject。

  1. typedef struct redisObject {
  2. unsigned type:4; // 类型
  3. unsigned encoding:4; // 编码
  4. unsigned lru:LRU_BITS; // 最近被访问的时间
  5. int refcount; // 引用次数
  6. void *ptr; // 指向具体底层数据的指针
  7. } robj;

一个 RedisObject 包含了8字节的元数据和一个8字节指针,指针指向实际的数据内存地址。

不过需要注意的是这里 Redis 做了优化

1、当保存的数据是 Long 类型整数时,RedisObjec t中的指针就直接赋值为整数数据了,就不用使用额外的指针了。

2、如果保存的是字符串数据,并且字符串大小小于等于44字节时,RedisObject中的元数据、指针和SDS是一块连续的内存区域,这样就可以避免内存碎片。这种布局方式也被称为 embstr 编码方式。

3、如果保存的是字符串数据,并且字符串大小大于44字节时,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。这种布局方式被称为 raw 编码模式。

这个引用一张Redis核心技术与实战中的图片

3、全局哈希表

Redis 中会有一个全局的哈希表来保存所有的键值对,哈希表中每一项存储的是 dictEntry 结构体

  1. typedef struct dictEntry {
  2. void *key;
  3. union {
  4. void *val;
  5. uint64_t u64;
  6. int64_t s64;
  7. double d;
  8. } v;
  9. struct dictEntry *next;
  10. } dictEntry;

dictEntry 结构体中有三个指针,在64位机器下占24个字节,jemalloc 会为它分配32字节大小的内存单元。

jemalloc 作为 Redis 的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc 在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当 Redis 存储数据时,会选择大小最合适的内存块进行存储。

所以选用 String 类型来存储字符串,上面的 RedisObject 结构、SDS 结构、dictEntry 结构的都会存在一定的内存开销

Redis 中的底层数据结构,提供了压缩列表,这种是很节省内存空间的。

我们可以使用 Hash 这种数据结构,因为在一定情况下这种结构底层的用的是压缩列表,这是一种很节省内存的数据结构。

使用 Hash 来存储

关于压缩列表的细节可参见Redis中的压缩列表

这些entry会挨个儿放置在内存中,不需要再用额外的指针进行连接,这样就可以节省指针所占用的空间。

Redis基于压缩列表实现了 Hash 这样的集合类型,因为一个集合可以保存多个键值对,使用一个键值对就能对应到这个集合中了。使用 String 类型时,一个键值对就对应一个 dictEntry,这点对于使用集合类型来讲也是节省内存的一个点。

使用集合我们还需要注意一下几点:

1、我们要去保证存放到集合中的元素不要太多,使用 ziplist 作为内部数据结构的限制元素数默认不超过 512 个。可以通过修改配置来调整zset_max_ziplist_entries阀值的大小。如果超过了限制就不使用 ziplist 而是使用 Hash 类型来实现这个映射关系了。

2、同时元素也不能太少,如果一个 Hash 集合中只存入了一对filed/value,就相当于每个键值对也使用了一个全局的哈希表的 dictEntry。

3、同时键值对的 value 也不要太长,超过了hash-max-ziplist-value的限制也是会使用 Hash 类型而不是 ziplist。

原来使用 String 类型存储,是一个k/v结构,使用 Hash 类型,就需要两个 key 了,可以将原来的k/v中的 k 进行拆分,分成两部分即可。

  1. 127.0.0.1:6379> set 202220222111 xiaoming
  2. OK
  3. 127.0.0.1:6379> hset 20222 0222111 xiaoming
  4. (integer) 1

总结

String 类型的元数据是会占用一部分的内存空间,如果我们的数据,单个数据不大,但是数量很多,选用 String 这种类型的时候,需要考虑一下内存的占用。

参考

【Redis核心技术与实战】https://time.geekbang.org/column/intro/100056701

【Redis设计与实现】https://book.douban.com/subject/25900156/

【redis 一组kv实际内存占用计算】https://kernelmaker.github.io/Redis-StringMem

【Redis学习笔记】https://github.com/boilingfrog/Go-POINT/tree/master/redis

【Redis 中 String 类型的内存开销比较大】https://boilingfrog.github.io/2022/02/22/redis中的string类型内存开销比较大/

Redis 中 String 类型的内存开销比较大的更多相关文章

  1. Redis中String类型的Value最大可以容纳数据长度

    项目中使用redis存储,key-value方式,在Redis中字符串类型的Value最多可以容纳的数据长度是512M 官方信息: A String value can be at max 512 M ...

  2. Redis中String类型的相关命令操作

    String append 如果key已存在,则直接在value追加值,如果key不存在,则会插件一个新的value为空的key,然后在追加 127.0.0.1:6379> set name l ...

  3. Python(Redis 中 String/List/Hash 类型数据操作)

    1.下载 redis 模块 pip install redis 2.redis 数据库两种连接方式 简单连接 decode_responses=True,写入和读取的键值对中的 value 为 str ...

  4. Java中String类型细节

    Java中String类型细节 一 . String两种初始化方式 1 . String str1= “abc”;//String类特有的创建字符对象的方式,更高效 在字符串缓冲区中检测”abc”是否 ...

  5. 使用Redis数据库(String类型)

    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...

  6. Java中String类型详解

    这篇博客是我一直想总结的,这两天一直比较忙,先上传下照片吧,过后有时间再弄成正常的. 本文主要是对Java中String类型的总结,包括其在JVM中是怎么存储的...

  7. UWP中String类型如何转换为Windows.UI.Color

    原文:UWP中String类型如何转换为Windows.UI.Color 我在学习过程中遇到的,我保存主题色为string,但在我想让StatusBar随着主题色变化时发现没法使用. ThemeCol ...

  8. 深入浅出Redis02 使用Redis数据库(String类型)

    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...

  9. Delphi中String类型原理介绍

    Delphi中字符串的操作很简单,但幕后情况却相当复杂.Pascal传统的字符串操作方法与Windows不同,Windows吸取了C语言的字符串操作方法.32位Delphi中增加了长字符串类型,该类型 ...

随机推荐

  1. MongoDB-基础知识学习(一)

    概述 最近mongodb在互联网的活跃度直线上升,并且我们公司也使用了mongoDB 3.6 作为生产重要的数据库,我们项目组要监控mongodb的op.log日志,在此整理以前学习的知识,为以后备份 ...

  2. x86架构应用如何向Arm架构低成本迁移

    曾几何时,无论是在服务器还是个人电脑,CPU芯片领域一直是 Intel 独占鳌头,旗下的 X86_64 架构被广泛采用.然而王权没有永恒,近年来 Arm64 架构异军突起,服务器端有华为鲲鹏920高性 ...

  3. 记一次异步处理导致Jetty Request对象泄漏

    最近排查一个bug,发现了一系列有意思的东西,对「自定义线程池」.「Jetty线程模型」都有了一些新的认识. 本文预计阅读时间10分钟,包括: 问题表现 常见原因筛查 根因与源码分析 最佳实践 一些小 ...

  4. day7 对字母数字的编排

    1.函数fun()的功能:在s数组内寻找并且统计t数组在s数组中出现的次数 输入字符串:函数scanf()函数不好用,会出现很多bug,因此不使用scanf() 效果理想: 2.函数fun()功能:将 ...

  5. Linux - 文件处理

    链接服务器 ssh 使用ssh:ssh -p22 username@host(服务器地址) 输入后会提示输入密码 -p22是ssh默认端口 可以不用 登录之后会默认处于 home 路径 xshell ...

  6. DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ | TENSORS

    Tensor是一种特殊的数据结构,非常类似于数组和矩阵.在PyTorch中,我们使用tensor编码模型的输入和输出,以及模型的参数. Tensor类似于Numpy的数组,除了tensor可以在GPU ...

  7. MySQL查询处理——逻辑查询处理和物理查询处理

    对于查询处理,可将其分为逻辑查询处理和物理查询处理.逻辑查询处理表示执行查询应该产生什么样的结果,而物理查询代表MySQL数据库是如何得到结果的. 逻辑查询处理 MySQL真正的执行顺序如下: (8) ...

  8. golang中的标准库strconv

    strconv 包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi().Itia().parse系列.format系列.append系列. string与int ...

  9. gin中绑定uri

    package main import ( "github.com/gin-gonic/gin" "net/http" ) type Person struct ...

  10. 不难懂--------react笔记

      在jsx中不能使用class定义类名   因为class在js中是用来定义类的  定义类名的时候用className       label中的for必须写成htmlFor         Rea ...