此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表。本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题  5. conf文件配置。在实践上的操作主要是对conf配置文件进行配置,具体上没有确切的一个值,更多是经验值。也有的项目会直接使用原本的默认值。此篇对于更好地理解一个数据库底层的存储逻辑会有一点帮助。修学储能,既要博,也要渊。希望这篇文章对同样也是在学习Redis的各位同伴有点用。

一、压缩列表(ziplist)的使用场景:

Redis为了优化数据存储,节约内存,在列表、字典(哈希键)和有序集合的底层实现了使用压缩列表这一优化方案。

例如,假如一个哈希键里面存储的字符串比较短,那么Redis就会将它用压缩列表的格式去存储,即转换为字节数组存储。而一个哈希键内部存储的整数值比较小,同样也会把它存储为压缩列表的一个节点。同理,列表键的对小数据的存储跟哈希键的操作类似。

如此说来,压缩列表并不是开发者可以直接调用的Redis中的一种存储数据结构,而是Redis中为优化数据存储而在底层做的一项努力。理解好这点还是比较重要的。

二、如何达到节约内存的效果?

压缩列表是一种序列化的数据结构,这种数据结构的功能是将一系列数据与其编码信息存储在一块连续的内存区域,这块内存物理上是连续的。但逻辑上被分为多个组成部分,即节点。目的是为了在一定可控的时间复杂度条件下尽可能的减少不必要的内存开销,从而达到节省内存的效果。需要理解是怎么达到节约内存作用的,还需要去了解压缩列表的存储格式。

三、压缩列表的存储格式:

压缩列表(ziplist)是Redis列表键、哈希键和有序集合键的底层实现之一,其实质是一种序列化的数据存储结构。有别于普通情况下,Redis用双端链表表示列表,使用散列表表示哈希键,用散列表+跳跃表表示有序集合。当一个列表或者哈希字典/有序集合只包含很少内容,并且每一个列表项或者哈希项/有序集合项如果是小整数,或者比较短的字符串。那么Redis就会用压缩列表来做底层的实现。

 

压缩列表由一系列经Redis特殊编码的连续内存块组成,每一个内存块称为一个节点(entry),而一个压缩列表可以包含很多个节点。每个节点存储的数据格式可以是字节数组(中文字符串等都会转换为字节数组)或者整数值。

字节数组的长度可以是以下的其中一种:

1. 长度小于等于63字节(2的6次方)

2. 长度小于等于16383字节(2的14次方)

3. 长度小于等于4294967295字节(2的32次方)

整数值可能是以下六种中的其中一种:

1. 4位长,介于0-12之间的无符号整数

2. 1字节长的有符号整数

3. 3字节长的有符号整数

4. int16_t类型整数

5. int32_类型整数

6. int64_t类型整数

普通存储格式下和压缩列表存储格式下的不同点:

列表存储结构典型的为双端链表,每一个值都是用一个节点来表示,每个节点都会有指向前一个节点和后一个节点的指针,以及指向节点包含的字符串值的指针。而字符串值又分为3个部分存储,第一部分存储字符串长度,第二部分存储字符串值中剩余可用的字节量,第三部分存储的则是字符串数据本身。所以一个节点往往都需要存储3个指针、2个记录字符串信息的整数、字符串本省和一个额外的字节。总体上额外的开销是很大的(21字节)。

压缩列表节点的格式:

每一个节点都有previous_entry_length,encoding,content三个部分组成,在遍历压缩列表的时候是从后往前遍历的。

1. previous_entry_length记录了前一个节点的长度,只要用当前指针减去这个值就可以达到前一个节点的起始地址。

2. encoding记录了节点content属性所保存数据的类型和长度

3. content记录了一个节点的值

显然压缩列表这种方式节约了不少存储空间。但同时也会引发下面的问题。

四、连锁更新的问题:

一般而言如果前一个节点的整体长度小于254字节,previous_entry_length属性只需要1个字节的空间来保存这个长度值。而当前一个节点大于254字节的时候,previous_entry_length属性要用5个字节长的空间来记录长度值。

当长度为254字节左右的节点前插入一个新的节点的时候,需要增加previous_entry_length来记录这个节点到新节点的偏移量。这个时候,这个节点的长度肯定就大于254字节了。所以这个节点的后一个节点就不能只用一个字节的previous_entry_length来记录这个节点的信息了,而是需要5个字节来记录。如果连续多个节点的长度都为254字节左右,在其中的某一个节点前/后发生节点的插入和删除(删除的推理与插入相反,原本用5字节记录前一节点的可能变为1字节),都可能引发连锁的更新,显然,这样对系统地运行效率是很不利的。不过,在实际应用中这种情况还是比较少发生的。

而双端链表在节点的更新、增加和删除上显得就会“轻松”很多了。 因为每一个节点存储的信息都是相对独立的。

实践意义:

要预估一个节点大概占据多少字节的存储空间,适当地调整字段的存储格式而不要使存储的字段值占据存储空间落在254字节(除去encoding属性和previous_entry_length属性)左右。

Redis中查看字符串和哈希键值的长度相关命令:

1. 查询字符串键对应的值长度

命令:

Strlen

例如:

127.0.0.1:6379> strlen m_name

(integer) 8

2. 查询哈希键某一个域长度

命令:

Hstrlen

例如:

127.0.0.1:6379> hstrlen good_list good_list1

(integer) 226

五、Conf文件配置:

通过修改配置文件,可以控制是否使用压缩列表存储相关键的最大元素个数和最大元素的大小

Conf文件中的配置:
1.

[] -max-ziplist-entries : 表示对于键的最大元素个数,即一个键中在该指定值下的数量的节点个数都会用压缩列表来储存

[] -max-ziplist-value :表示压缩列表中每个节点的最大体积是多少字节

实际使用中,一个列表键/哈希键的某一个元素往往存储着比较大的信息量,会大于64字节,所以配置时很有可能会比64大,同时考虑到实际存储数据的容量大小以及上面谈到的previous_entry_length的大小问题,对[] -max-ziplist-value进行合理的配置。

配置文件内容:

############## ADVANCED CONFIG ##########################
哈希键
# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64 有序集合键
# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64 列表键,比较特殊,直接使用制定大小kb字节数表示(有些conf文件的列表键与hash键的表达式没太大区别)
# Lists are also encoded in a special way to save a lot of space.
# The number of entries allowed per internal list node can be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2

案例:

修改配置前使用默认配置:

hash-max-ziplist-entries 512

hash-max-ziplist-value 64

127.0.0.1:6379> hstrlen good_list good_list1

(integer) 226

127.0.0.1:6379> object encoding good_list

"hashtable"

修改配置:

hash-max-ziplist-entries 512

hash-max-ziplist-value 254

注意:修改配置后需要重启服务器

127.0.0.1:6379> hstrlen good_list good_list1

(integer) 226

127.0.0.1:6379> object encoding good_list

"ziplist"

可以看到存储方式已将变为ziplist

较官方的压力测试和指导建议:

当一个压缩列表的元素数量上升到几千(实际使用可能远小于这个值)的时候,压缩列表的性能可能会下降,因为Redis在操作这种结构的时候,编解码会出现一定的压力。

压缩列表的长度限制在500-2000之内,每个元素体积限制在128字节或以下,压缩列表的的性能都会处于合理范围之内。

参考资料:

《redis设计与实现》

Redis压缩列表的更多相关文章

  1. Redis压缩列表原理与应用分析

    摘要 Redis是一款著名的key-value内存数据库软件,同时也是一款卓越的数据结构服务软件.它支持字符串.列表.哈希表.集合.有序集合五种数据结构类型,同时每种数据结构类型针对不同的应用场景又支 ...

  2. 图解Redis之数据结构篇——压缩列表

    前言     同整数集合一样压缩列表也不是基础数据结构,而是 Redis 自己设计的一种数据存储结构.它有点儿类似数组,通过一片连续的内存空间,来存储数据.不过,它跟数组不同的一点是,它允许存储的数据 ...

  3. 【Redis】ziplist压缩列表

    压缩列表 压缩列表是列表和哈希表的底层实现之一: 如果一个列表只有少量数据,并且数据类型是整数或者比较短的字符串,redis底层就会使用压缩列表实现. 如果一个哈希表只有少量键值对,并且每个键值对的键 ...

  4. redis源码之压缩列表ziplist

    压缩列表ziplist1.简介连续,无序的数据结构.压缩列表是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构. 2.组成 属性 类型 长 ...

  5. redis底层数据结构--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表

    1.动态字符串 redis中使用c语言的字符床存储字面量,默认字符串存储采用自己构建的简单动态字符串SDS(symple dynamic string) redis包含字符串的键值对都是用SDS实现的 ...

  6. redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

    文中内容摘自<redis设计与实现> 简单动态字符串 1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态 ...

  7. redis 底层数据结构 压缩列表 ziplist

    压缩列表是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,redis就会使用压缩列表来做列表键的底层实现 当一个哈希键只包含少量键 ...

  8. Redis底层探秘(四):整数集合及压缩列表

    整数集合 整数集合(intset)是集合键的底层实现之一,当一个集合只包含 整数值元素,并且这个集合的元素数量不多时,Redis就会使用郑书记和作为集合键的底层实现. 整数集合的实现 整数集合是red ...

  9. Redis实现之压缩列表

    压缩列表 压缩列表(ziplist)是列表键和哈希键的底层实现之一,当一个列表键只包含少量列表项,并且每个列表项要嘛是整数值,要嘛是比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现. ...

随机推荐

  1. JS中的特殊类别注意区分

    undefined和null 在JavaScript中存在这样两种原始类型:Null与Undefined. 这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是 ...

  2. [Swift]LeetCode979. 在二叉树中分配硬币 | Distribute Coins in Binary Tree

    Given the root of a binary tree with N nodes, each node in the tree has node.val coins, and there ar ...

  3. linux终端文本编辑神器vi的使用

    vi —— 终端中的编辑器 目标 vi 简介 打开和新建文件 三种工作模式 常用命令 分屏命令 常用命令速查图 01. vi 简介 1.1 学习 vi 的目的 在工作中,要对 服务器 上的文件进行 简 ...

  4. 为OPENCV添加freetype支持并显示中文字符(在mac上编译opencv及contrib库)

    在mac电脑上管理这些gnu的库一般都使用Homebrew,但总有一些你个性化的需要是官方的Homebrew配方无法满足的.比如在屏幕的输出中使用中文字符. 在OPENCV中输出UTF8字符集早已经有 ...

  5. 大战Java虚拟机【2】—— GC策略

    前言 前面我们已经知道了Java虚拟机所做的事情就是回收那些不用的垃圾,那些不用的对象.那么问题来了,我们如何知道一个对象我们不需要使用了呢?程序在使用的过程中会不断的创建对象,这些所创建的对象指不定 ...

  6. 初学Java Web(3)——第一个Servlet

    这学期 Java Web 课程的第一节课就简短复习了一下 Java 的一些基础知识,所以觉得 Java 的基础知识还是很重要的,但当我想要去写一篇 Java 回顾的文章的时候发现很难,因为坑实在太多了 ...

  7. centos6.5-vsftp搭建

    我的机子是默认是没有的vsftp. yum install -y vsftp 创建账户专为ftp而生.useradd ftp01 更改账户不可登录系统.usermod -s /sbin/nologin ...

  8. Docker Compose 原理

    Docker 的优势非常明显,尤其是对于开发者来说,它提供了一种全新的软件发布机制.也就是说使用 docker 镜像作为软件产品的载体,使用 docker 容器提供独立的软件运行上下文环境,使用 do ...

  9. Java提高班(六)反射和动态代理(JDK Proxy和Cglib)

    反射和动态代理放有一定的相关性,但单纯的说动态代理是由反射机制实现的,其实是不够全面不准确的,动态代理是一种功能行为,而它的实现方法有很多.要怎么理解以上这句话,请看下文. 一.反射 反射机制是 Ja ...

  10. MariaDB主从复制的逻辑与实现

    一.关系型数据库的劣势 “关系型数据库:指采用了关系模型来组织数据的数据库,而关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织.”——Wiki 关系型数据 ...