这篇Redis文章,图灵看了都说好

本文详细介绍了Redis的内存模型,包括Redis内存划分、数据存储细节,以及内存碎片的处理。此外,文章还深入探讨了Redis的持久化机制,包括RDB和AOF持久化的工作原理、优缺点以及文件重写流程。复制机制部分,解释了主从复制的连接建立、数据同步和命令传播阶段。最后,提到了哨兵模式和集群模式的高可用性解决方案。
摘要由CSDN通过智能技术生成

作者:lunnzhang,腾讯 CDG 后台开发工程师。

2007 年,他和朋友一起创建了一个网站。为了解决这个网站的负载问题,他自己定制了一个数据库。2009 年开发的,这个是 Redis。这位意大利程序员是萨尔瓦托勒·桑菲利波(Salvatore Sanfilippo),他被称为Redis之父,更广为人知的名字是Antirez。

一、Redis简介

REmote DIctionary Server(Redis) 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

二、内存模型

首先可以进行Redis的内存模型学习,对Redis的使用有很大帮助,例如OOM时定位、内存使用量评估等。

通过info memory命令查看内存的使用情况。

主要参数:

  1. used_memory:从Redis角度使用了多少内存,即Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap);

  2. used_memory_rss:从操作系统角度实际使用量,即Redis进程占据操作系统的内存(单位是字节),包括进程运行本身需要的内存、内存碎片等,不包括虚拟内存。一般情况下used_memory_rss都要比used_memory大,因为Redis频繁删除读写等操作使得内存碎片较多,而虚拟内存的使用一般是非极端情况下是不怎么使用的;

  3. mem_fragmentation_ratio:即内存碎片比率,该值是used_memory_rss / used_memory的比值;mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。如果mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);

  4. mem_allocator:即Redis使用的内存分配器,一般默认是jemalloc。

Redis内存划分
  1. 数据

作为数据库,数据是最主要的部分,这部分占用的内存会统计在used_memory中。

  1. 进程本身运行需要的内存

这部分内存不是由jemalloc分配,因此不会统计在used_memory中。

  1. 缓冲内存

缓冲内存包括:

  • 客户端缓冲区:存储客户端连接的输入输出缓冲;

  • 复制积压缓冲区:用于部分复制功能;

  • aof_buf:用于在进行AOF重写时,保存最近的写入命令。

这部分内存由jemalloc分配,因此会统计在used_memory中。

  1. 内存碎片

内存碎片是Redis在数据更改频繁分配、回收物理内存过程中产生的。

内存碎片不会统计在used_memory中。

Redis数据存储的细节

下面看一张经典的图。

  1. jemalloc

无论是DictEntry对象,还是RedisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。当Redis存储数据时,会选择大小最合适的内存块进行存储。

jemalloc划分的内存单元如下图所示:

  1. dictEntry

每个dictEntry都保存着一个键值对,key值保存一个sds结构体,value值保存一个redisObject结构体。

  1. redisObject

前面说到,Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过RedisObject对象进行存储。

RedisObject的每个字段的含义和作用如下:

  • type

type字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。

  • encoding

encoding表示对象的内部编码,占4个比特(redis-3.0)。

  • lru

lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。

  • refcount

refcount记录的是该对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收:

  • 当创建新对象时,refcount初始化为1;

  • 当有新程序使用该对象时,refcount加1;

  • 当对象不再被一个新程序使用时,refcount减1;

  • 当refcount变为0时,对象占用的内存会被释放。

Redis中被多次使用的对象(refcount>1)称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。

共享对象的具体实现

Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。

对于整数值,判断操作复杂度为O(1);

对于普通字符串,判断复杂度为O(n);

而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。

虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。

就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。

  • ptr

ptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。

  1. SDS

Redis没有直接使用C字符串(即以空字符‘\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值