`
ninghq
  • 浏览: 12323 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

(转)redis内存优化与存储手段

阅读更多

 

Redis 常用数据类型

 

Redis 最为常用的数据类型主要有以下五种:

  • String

  • Hash

  • List

  • Set

  • Sorted set

 

在具体描述这几种数据类型之前,我们先通过一张图了解下 Redis 内部内存管理中是如何描述这些不同数据类型的:



 

 

首先 Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value,redisObject 最主要的信息如上图所示。type 代表一个 value 对象具体是何种数据类型,encoding是不同数据类型在Redis内部的存储方式,比如:type=string 代表 value 存储的是一个普通字符串,那么对应的 encoding 可以是 raw 或者是 int,如果是 int 则代表实际 Redis 内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。

 

这里需要特殊说明一下 vm 字段,只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的,该功能会在后面具体描述。

 

通过上图我们可以发现 ,Redis 使用 redisObject 来表示所有的 key/value 数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给 Redis 不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用,随后会具体讨论。

 

下面我们先逐一分析这五种数据类型的使用和内部实现方式:

 

String

 

常用命令:

Set、get、decr、incr、mget 等。

 

应用场景:

String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类,这里就不所做解释了。

 

实现方式:

String 在 Redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr、decr 等操作时会转成数值型进行计算,此时 redisObject 的 encoding 字段为int。

 

Hash

 

常用命令:

Hget、hset、hgetall 等。

 

应用场景:

我们简单举个实例来描述下 Hash 的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:

 

用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,如果用普通的 key/value 结构来存储,主要有以下2种存储方式:

 



 

 

第一种方式将用户 ID 作为查找 key,把其它信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。

 



 

 

第二种方法是这个用户信息对象有多少成员就存成多少个 key-value 对儿,用用户 ID +对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户 ID 为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

 

那么 Redis 提供的 Hash 很好地解决了这个问题,Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口,如下图:

 



 

 

也就是说,Key 仍然是用户 ID,value 是一个 Map,这个 Map 的 key 是成员的属性名,value 是属性值,这样对数据的修改和存取都可以直接通过其内部 Map 的 Key(Redis 里称内部 Map 的 key 为 field),也就是通过 key(用户 ID) + field(属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题,很好地解决了问题。

 

这里同时需要注意,Redis 提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部 Map 的成员很多,那么涉及到遍历整个内部 Map 的操作,由于 Redis 单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

 

实现方式:

上面已经说到 Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有2种不同实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap,此时 encoding 为 ht。

 

List

 

常用命令:

Lpush、rpush、lpop、rpop、lrange等。

 

应用场景:

Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的 list 结构来实现,比较好理解,这里不再重复。

 

实现方式:

Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

 

Set

 

常用命令:

Sadd、spop、smembers、sunion 等。

 

应用场景:

Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

 

实现方式:

set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。

 

Sorted set

 

常用命令:

zadd、zrange、zrem、zcard等。

 

使用场景:

Redis sorted set的使用场景与 set 类似,区别是set不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set 数据结构,比如 twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。

 

实现方式:

Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

 

常用内存优化手段与参数

 

通过我们上面的一些实现上的分析可以看出 Redis 实际上的内存管理成本非常高,即占用了过多的内存,作者对这点也非常清楚,所以提供了一系列的参数和手段来控制和节省内存,我们分别来讨论下。

 

首先最重要的一点是不要开启 Redis 的 VM 选项,即虚拟内存功能,这个本来是作为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高,并且我们后续会分析此种持久化策略并不成熟,所以要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled 为 no。

 

其次最好设置下 redis.conf 中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好地保护好你的 Redis 不会因为使用了过多的物理内存而导致 swap,最终严重影响性能甚至崩溃。

 

另外 Redis 为不同数据类型分别提供了一组参数来控制内存使用,我们在前面详细分析过 Redis Hash 是 value 内部为一个 HashMap,如果该 Map 的成员数比较少,则会采用类似一维线性的紧凑格式来存储该 Map,即省去了大量指针的内存开销,这个参数控制对应在 redis.conf 配置文件中下面2项:

hash-max-zipmap-entries 64

hash-max-zipmap-value 512

hash-max-zipmap-entries

 

含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的 HashMap。

 

hash-max-zipmap-value 含义是当 value 这个 Map 内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。

 

以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap 的优势就是查找和操作的时间复杂度都是 O(1) 的,而放弃 Hash 采用一维存储则是 O(n) 的时间复杂度,如果成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。

 

同样类似的参数还有:

list-max-ziplist-entries 512

 

说明:list 数据类型多少节点以下会采用去指针的紧凑存储格式。

list-max-ziplist-value 64

 

说明:list 数据类型节点值大小小于多少字节会采用紧凑存储格式。

set-max-intset-entries 512

 

注:set 数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。

 

最后想说的是Redis内部实现没有对内存分配方面做过多的优化,在一定程度上会存在内存碎片,不过大多数情况下这个不会成为Redis的性能瓶 颈,不过如果在Redis内部存储的大部分数据是数值型的话,Redis内部采用了一个 shared integer 的方式来省去分配内存的开销,即在系统启动时先分配一个从 1~n,那么多个数值对象放在一个池子中,如果存储的数据恰好是这个数值范围内的数据,则直接从池子里取出该对象,并且通过引用计数的方式来共享,这样在系统存储了大量数值下,也能一定程度上节省内存并且提高性能,这个参数值 n 的设置需要修改源代码中的一行宏定义 REDIS_SHARED_INTEGERS,该值 默认是 10000,可以根据自己的需要进行修改,修改后重新编译就可以了。

 

Redis 的持久化机制

 

Redis 由于支持非常丰富的内存数据结构类型,如何把这些复杂的内存组织方式持久化到磁盘上是一个难题,所以 Redis 的持久化方式与传统数据库的方式有比较多的差别,Redis 一共支持四种持久化方式,分别是:

 

  • 定时快照方式(snapshot)

  • 基于语句追加文件的方式(aof)

  • 虚拟内存(vm)

  • Diskstore 方式

 

在设计思路上,前两种是基于全部数据都在内存中,即小数据量下提供磁盘落地功能,而后两种方式则是作者在尝试存储数据超过物理内存时,即大数据量的数据存储,截止到本文,后两种持久化方式仍然是在实验阶段,并且 vm 方式基本已经被作者放弃,所以实际能在生产环境用的只有前两种,换句话说 Redis 目前还只能作为小数据量存储(全部数据能够加载在内存中),海量数据存储方面并不是 Redis 所擅长的领域。下面分别介绍下这几种持久化方式:

 

定时快照方式(snapshot):

 

该持久化方式实际是在 Redis 内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统 fork 调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行 copy-on-write 保证父子进程之间不会互相影响。

 

该持久化的主要缺点是定时快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。

 

基于语句追加方式(aof):

 

aof 方式实际类似MySQL基于语句的 binlog 方式,即每条会使 Redis 内存数据发生改变的命令都会追加到一个 log 文件中,也就是说这个 log 文件就是 Redis 的持久化数据。

 

aof 的方式的主要缺点是追加 log 文件可能导致体积过大,当系统重启恢复数据时如果是 aof 的方式则加载数据会非常慢,几十G的数据可能需要几小时才能加载完,当然这个耗时并不是因为磁盘文件读取速度慢,而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写 log,所以使用 aof 的方式,Redis 的读写性能也会有所下降。

 

虚拟内存方式:

 

虚拟内存方式是 Redis 来进行用户空间的数据换入换出的一个策略,此种方式在实现的效果上比较差,主要问题是代码复杂、重启慢、复制慢等等,目前已经被作者放弃。

 

diskstore 方式:

 

diskstore 方式是作者放弃了虚拟内存方式后选择的一种新的实现方式,也就是传统的 B-tree 的方式,目前仍在实验阶段,后续是否可用我们可以拭目以待。

 

Redis持久化磁盘IO方式及其带来的问题

 

有 Redis 线上运维经验的人会发现 Redis 在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的问题,有人认为是基于快照方式持久化的 fork 系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为 fork 调用的 copy-on-write 机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写入而导致复制,那是什么原因导致 Redis 崩溃呢?

 

答案是 Redis 的持久化使用了 Buffer IO 造成的,所谓 Buffer IO 是指 Redis 对持久化文件的写入和读取操作都会使用物理内存的 Page Cache,而大多数数据库系统会使用 Direct IO 来绕过这层 Page Cache 并自行维护一个数据的 Cache,而当 Redis 的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内 存中作为操作系统对该文件的一层 Cache,而这层 Cache 的数据与 Redis 内存中管理的数据实际是重复存储的,虽然内核在物理内存紧张时会做 Page Cache 的剔除工作,但内核很可能认为某块 Page Cache 更重要,而让你的进程开始 Swap,这时你的系统就会开始出现不稳定或者崩溃了。我们的经验是当你的 Redis 物理内存使用超过内存总容量的3/5时就会开始比较危险了。

 

下图是 Redis 在读取或者写入快照文件 dump.rdb 后的内存数据图:

 



 

 

总结

 

  1. 根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。

  2. 当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量。

  3. 如果需要使用持久化,根据是否可以容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及 diskstore 方式。

  4. 不要让你的 Redis 所在机器物理内存使用超过实际内存总量的3/5。

         

 

 

  • 大小: 68.5 KB
  • 大小: 50.1 KB
  • 大小: 34.9 KB
  • 大小: 33.3 KB
  • 大小: 82.3 KB
分享到:
评论

相关推荐

    Redis缓存设计与性能优化精要

    6. **性能优化**:除了上述策略外,还可以通过调整Redis配置(如内存管理、持久化策略)、使用缓存淘汰策略(如LRU、LFU)、监控和调优网络带宽等手段,进一步提升Redis的性能和系统的稳定性。 总之,理解并掌握...

    Redis设计与实战

    此外,Redis还支持客户端缓存、命令阻塞策略以及Lua脚本等优化手段,进一步提升了性能。 实战部分,书中可能涵盖了如何在Web应用中使用Redis作为session存储,利用List进行发布订阅,使用Set进行去重,用Sorted Set...

    redis安装包

    最后,了解如何进行Redis性能优化也是开发者必须掌握的技能,这涉及到合理的配置参数设置、内存管理、连接池管理等。例如,调整`maxmemory`限制内存使用,使用`lua_cache`提升脚本执行效率,以及合理设计数据结构以...

    ServiceStack.Redis操作工具类

    ServiceStack.Redis是一个强大的C#客户端库,用于与Redis内存数据存储进行交互。Redis是一个高性能的键值数据库,常被用作缓存、消息队列或数据存储解决方案。ServiceStack.Redis库为.NET开发者提供了全面的Redis...

    redis-3.2.9安装包

    Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。其支持的数据结构包括字符串、哈希、列表、集合、有序集合,以及更复杂的结构如位图、地理空间索引等。Redis通过...

    linux-redis

    【Redis详解:Linux环境下的内存数据存储与持久化】 Redis,全称为Remote Dictionary Server,是一款高性能的键值存储系统,广泛应用于缓存、消息队列、数据库等多个领域。它以键值对的形式存储数据,支持丰富的...

    Redis可视化工具

    3. 实时监控:实时展示Redis服务器的内存使用、CPU占用、连接数、命中率等关键性能指标,帮助分析和优化服务器状态。 4. 数据导入导出:支持将Redis中的数据导出为文件,或者从文件导入数据到Redis,方便数据迁移和...

    个人整理的redis学习资料汇总

    它以数据持久化、支持多种数据结构、内存优化以及丰富的客户端库等特点受到广大开发者的青睐。本资料汇总将带你深入理解Redis的基础概念、核心特性、源码解析以及实际应用。 1. **Redis入门**: Redis入门文档通常会...

    Redis全套学习笔记

    Redis 是一款高性能的键值数据库,它以非关系型、内存存储的方式提供快速的数据访问。Redis 的设计目标是为了处理高并发、高扩展性场景,以及大数据存储问题。它支持多种数据结构,包括字符串(String)、链表(List)、...

    C# Redis调用示例

    在IT行业中,Redis被广泛用作高性能的内存数据存储,常作为数据库、缓存和消息代理使用。在C#开发环境中,与Redis交互是提升应用性能的重要手段。本示例将详细讲解如何在C#中调用Redis进行数据操作。 首先,你需要...

    Redis实战.pdf.zip

    Redis,全称Remote Dictionary Server,是一款高性能的键值对存储系统,常被用作数据库、缓存和消息中间件。Redis以其丰富的数据结构、高效的数据处理能力以及支持网络的特性,广泛应用于各种互联网服务和实时应用中...

    springmvc+mybatis+redis

    由于Redis的数据存储在内存中,读写速度极快,是提升Web应用性能的有效手段。 整合SpringMVC、MyBatis和Redis,通常是为了构建一个高效、可扩展的Web应用。在SpringMVC中,可以通过Spring的依赖注入(DI)和AOP...

    Redis数据库 v6.0.20.zip

    9. **命令行接口**:Redis自带的`redis-cli`工具,是与Redis服务器交互的主要方式,也是进行测试和调试的重要手段。 在"说明.htm"文档中,可能会包含关于如何编译和运行Redis,以及配置文件详解等内容,对于初学者...

    Redis3.0.504

    3.0.504可能包含了一些内存优化策略,如LRU(最近最少使用)替换策略,以控制数据库的大小并防止内存溢出。 8. **性能优化**:Redis因其高性能著称,3.0.504版本可能针对32位环境进行了优化,比如更有效地利用内存...

    redis桌面管理工具

    总之,"redis桌面管理工具"是开发和运维人员的得力助手,它简化了Redis的日常管理工作,提高了数据管理的效率,同时也是排查问题和优化性能的有效手段。在实际使用中,可以根据具体需求选择适合的工具,并结合Redis...

    redis技术文档

    例如,合理设置内存限制、利用内存数据淘汰策略、优化数据结构选择、利用Lua脚本减少网络通信等都是常见的优化手段。此外,监控和日志分析也是确保Redis稳定运行的重要环节,可以使用如`redis-stat`或集成到...

    redis百万并发访问数据库测试

    2. **Redis的内存管理**:Redis默认将所有数据存储在内存中,从而实现了快速读写。为了管理内存,Redis提供了LRU(Least Recently Used)和LFU(Least Frequently Used)淘汰策略,当内存满时,可以根据策略自动移除...

    Redis-Session-同步

    6. **安全考虑**:Redis作为内存数据库,其数据安全性尤为重要。需要设置适当的Redis访问权限,避免未授权的访问。同时,考虑到数据持久化,可以启用RDB或AOF机制。 这个"Redis-Session-同步"项目提供了一种实用的...

    redis各版本安装包(exe的)

    可以通过调整内存分配策略、优化数据结构、限制最大内存等手段提升性能。同时,合理设计数据模型和操作方式也能有效避免不必要的性能瓶颈。 9. **社区支持**:Redis拥有活跃的社区和丰富的生态系统,用户可以在官方...

Global site tag (gtag.js) - Google Analytics