Redis学习笔记《二》

十一、为什么String类型不好用

11.1 为什么String类型的内存开销大?

String 除了记录数据之外,还需要记录额外的一些信息 元数据信息 (如数据长度、空间使用等信息)

11.2 String类型如何保存数据?

当你保存的是64位有符号的整数时,String类型会把它保存成一个8个字节的 Long类型整数,这种编码方式叫int类型编码

当你保持的数据中包含字符时,就会使用 简单动态字符来保存

(简单动态字符)SDS 结构体

image-20210327194355952

buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。

len:占 4 个字节,表示 buf 的已用长度。

alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。

11.3 Reais Object

因为 Redis 的数据类型有很多,而且,不同数据类型都有些相同的元数据要记录(比如最后一次访问的时间、被引用的次数等),所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。

包含了8字节的元数据(用来区分不同的类型) 和 8字节的指针

image-20210327195425910

不同的大小,有不同的存储格式

image-20210327200120370

11.4 10位 key - value 一共需要多少字节

整型 int 类型的编码 可以用redisoobject 直接存储数据,16个字节一个ID。

redis的全局hash表 dictEntry 的结构体,它由3个部分组成, key、value 以及下一个 dictEntry,三个指针共 24 字节

image-20210327201357429

但是由于 Redis 使用的内存分配库 jemalloc 了,每次分配时会找一个 >= 它请求分配的 最小2的N次幂,所以24个字节实际也就是 申请了32个字节。所以 32B + 32B = 64B

11.5 用什么数据结构可以节省内存

使用压缩表:

。表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量,以及列表中的 entry 个数。压缩列表尾还有一个 zlend,表示列表结束。

image-20210327203039271

prev_len: 表示前一个 entry 的长度

encoding:表示编码方式,1 字节;

len:表示自身长度,4 字节;

(key)content:保存实际数据;

当你用 String 类型时,一个键值对就有一个 dictEntry,要用 32 字节空间。但采用集合类型时,一个 key 就对应一个集合的数据,能保存的数据多了很多,但也只用了一个 dictEntry,这样就节省了内存。

总共消耗: 8 + 1 + 1 + 4 = 14个字节

11.6 如何用集合类型保存单值的k-v

前一部分作为 Hash 集合的 key,后一部分作为Hash 集合的 value,这样一来,我们就可以把单值数据保存到 Hash 集合中了。

例如。100001 1234

就可以 使用hashset进行存储:1000 01(key) - 1234(value)

Hash 类型的底层结构是 hash表 和 压缩表,什么时候相互转换呢?

设置了两个阀值 一但在阀值之外就会转换成 hash 表。

hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。

hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

十二、有一亿个key 要统计,应该用哪种集合?

image-20210327224023450

十三、数据类型GEO

在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在LBS 服务的场景中,我们来看一下它的底层结构。

image-20210330191536037 image-20210330191625776

经度 和 维度 进行编码成一串数字,能作为 sortSet的权值

13.1 RedisObject

RedisObject 包括元数和指针。元数据的功能是用来区分不同类型的数据类型;指针用来指向不同类型的值

image-20210330192237099

type:表示值的类型,涵盖了我们前面学习的五大基本类型;

encoding:是值的编码方式,用来表示 Redis 中实现各个基本类型的底层数据结构,

例如 SDS、压缩列表、哈希表、跳表等;

lru:记录了这个对象最后一次被访问的时间,用于淘汰过期的键值对;

refcount:记录了对象的引用计数;

*ptr:是指向数据的指针

十四、如何在redis中保存时间序列数据

14.1 基于Hash 和 sorted set 保存时间序列数据

Hash 保存 k-v数据

Sorted set 用来排序

利用MULTI 和 EXEC 命令 保证 redis 事务执行

14.2 基于 RedisTimeSeries 模块保存时间序列数据

十五、消息队列的考验:Redis有哪些考验

15.1 消息队列需要解决的3个问题:

1.消息保序

使用 LPUSH 命令 和 RPOP 命令 保证写入后读取消息的顺序 消费者一直使用 while(1)监听处理消息的时候 会一直消耗CPU

改进:提供使用 BRPOP 命令,再没有拿到数据时,会发生阻塞,不会消耗cup

2.重复消息处理

生产者定义一个 幂等id, 消费者自行控制幂等处理

3.消息可靠性保证

防止消费者,处理失败,消息丢失,可以使用BRPOPLPUSH 命令 ,这个命令可以将消息存储在另外一个 备份list当中

生产者很多消息堆积;消费者及时处理不了,如何构建成一个消费组?

15.2 基于Stream的消息队列解决方案

Streams 是 Redis 专门为消息队列设计的数据类型,它提供了丰富的消息队列操作命令。

XADD:插入消息,保证有序,可以自动生成全局唯一 ID;

XREAD:用于读取消息,可以按 ID 读取数据;

XREADGROUP:按消费组形式读取消息;

XPENDING 和 XACK:XPENDING 命令可以用来查询每个消费组内所有消费者已读取

但尚未确认的消息,而 XACK 命令用于向消息队列确认消息处理已完成。

十六、异步机制:如何避免单线程模型的阻塞?

16.1 Redis实例有哪些阻塞点?

客户端:网络 IO,键值对增删改查操作,数据库操作;

磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;

主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB

文件;

切片集群实例:向其他实例传输哈希槽信息,数据迁移。

image-20210331220610271

16.2 具体的阻塞操作

16.2.1 客户端交互

1.聚合查询和全量操作

2.bigkey 删除操作就是 Redis 的第二个阻塞点

3.清空数据库

16.2.2 和磁盘交互的时点

4.AOF日志同步写( AOF 日志时,会根据不同的写回策略对数据做落盘保存)

16.2.2主从节点交互时的阻塞点

5.加载RDB文件,主从同步时,会FLUSHDB 命令清空当前数据库,然后开始读取RDB文件

16.3 异步的子线程机制

Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。操作被封装成异步任务

image-20210331223653497

删除 和清空数据库都可以采用 异步线程进行

17. 为什么CPU的结构也会影响Redis性能

17.1 CPU 结构

一个 CPU 处理器中一般有多个运行核心,我们把一个运行核心称为一个物理核,每个物理核都可以运行应用程序。每个物理核都拥有私有的一级缓存(Level 1 cache,简称 L1cache),包括一级指令缓存和一级数据缓存,以及私有的二级缓存(Level 2 cache,简称 L2 cache)。

不同核之间会有共享内存 L3缓存;

image-20210401223002442

17.2 cpu多核对redis性能的影响

在一个 CPU 核上运行时,应用程序需要记录自身使用的软硬件资源信息(例如栈指针、CPU 核的寄存器值等),我们把这些信息称为运行时信息

线程在不同cpu间的切换,这个时候就要切换上下文信息了;

CPU NUMA 架构对 Redis 性能的影响

Redis 实例和网络中断程序的数据交互:

网络中断处理程序从网卡硬件中读取数据,并把数据写入到操作系统内核维护的一块内存缓冲区。内核会通过 epoll 机制触发事件,通知 Redis 实例,Redis 实例再把数据从内核的内存缓冲区拷贝到自己的内存空间。

image-20210401225457109

如果网络中断处理程序和 Redis 实例各自所绑的 CPU 核不在同一个 CPU Socket 上,那么,Redis 实例读取网络数据时,就需要跨 CPU Socket 访问内存,这个过程会花费较多时间。

image-20210401225840597

所以要进行核绑定,让他在一个核中处理,能够提高效率。

18、波动的响应延迟

18.1 排查和解决慢的操作

影响的关键因素: 文件系统操作系统

  1. 从慢查询命令开始排查,并且根据业务需求替换慢查询命令;

  2. 排查过期 key 的时间设置,并根据实际使用需求,设置不同的过期时间。

19、波动的响应延迟二

19.1 文件系统影响

AOF重写:当在进行AOF重写的时候,磁盘压力会很大,此时如果设置了aof刷盘策略是‘awalys’时,那么高磁盘io会影响fsync,子线程的f ync就会影响主线程,导致redis的性能变慢。

image-20210402203626953

19.2 操作系统的 swap

内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写,所以,一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。

引发原因: 物理机的内存不足

解决方法:增加机器的内存或者使用 Redis 集群

19.3 内存大页

Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。

当在作快照的时候,执行写时复制时,这个过程由其他线程完成,如果此时有数据被修改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。如果采用了内存大页,那么,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝2MB 的大页。但是如果只是4kb的时候,也就只需要拷贝4kb了

20. 删除数据后 为什么内存的占有率还是很高

20.1 什么是内存碎片

使用内存空间时不连续,会有存在未被利用的“小间隙” ,这部分小间隙,就是内存碎片

20.2 内存碎片是如何形成的

简单来说,内因是操作系统的内存分配机制,外因是 Redis 的负载特征。

内因:内存分配器的分配策略

Redis 可以使用 libc、jemalloc、tcmalloc 多种内存分配器来分配内存,默认使用jemalloc。会分配一个大于等于2的n次方的数据;

例如:申请一个20字节的数据内存,就会分配一个 32的内存 那么多的那部分 就会形成内存碎片;

外因:健值对大小不一样和删除操作

20.3 如何判断有内存碎片

Redis 是内存数据库,内存利用率的高低直接关系到它的性能,所以它提供了相关的命令,info 命令

INFO memory

# Memory

used_memory:1073741736

used_memory_human:1024.00M

used_memory_rss:1997159792

used_memory_rss_human:1.86G

mem_fragmentation_ratio:1.86

mem_fragmentation_ratio(碎片率) = (used_memory_rss)实际的 / used_memory(申请的)

当碎片率 >= 1.5 的时候 就需要注意了

20.4 如何清理碎片

1、直接重启red is 这并不是一个好的方法,可能会丢失数据

2、redis 4.0 之后 支持自动清理

但是整理碎片,会对
config set activedefrag yes

因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无法及时处理请求,性能就会降低。

通过参数设置阀值

active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始

清理;

active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的

总空间比例达到 10% 时,开始清理。

active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于

25%,保证清理能正常开展;

active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于

75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致

响应延迟升高。

二十一、11-20课后问题解答

21.1 除了 String 类型和 Hash 类型,还有什么类型适合保存第 11 讲中所说的图片吗?

除了 String 和 Hash,我们还可以使用 Sorted Set 类型进行保存。Sorted Set 的元素有 member 值和 score 值,可以像 Hash 那样,使用二级编码进行保存。具体做法是,把图片 ID 的前 7 位作为 Sorted Set 的 key,把图片 ID 的后 3 位作为 member 值,图片存储对象 ID 作为 score 值。

21.2 如果一个生产者发送给消息队列的消息,需要被多个消费者进行读取和处理

使用stream类型的消费组,消费者1 和消费者2 要属于不同的消费组;

21.3 在一台有两个 CPU Socket(每个 Socket 8 个物理核),改如何分配

有两个方案:

  1. 在同一个 CPU Socket 上运行 8 个实例,并和 8 个 CPU 核绑定;
  2. 在两个 CPU Socket 上各运行 4 个实例,并和相应 Socket 上的核绑定。

方案一:同一个 CPU Socket 上的进程,会共享 L3 缓存。如果把 8 个实例都部署在同一个Socket 上,它们会竞争 L3 缓存,这就会导致它们的 L3 缓存命中率降低,影响访问性能。

切片机群当中,不同实例间通过网络进行消息通信和数据迁移,并不会使用共享内存空间进行跨实例的数据访问

21.4 在 Redis 中,还有哪些命令可以代替 KEYS 命令,实现对键值对的 key 的模糊查询呢?这些命令的复杂度会导致 Redis 变慢吗?

Redis 提供的 SCAN 命令,以及针对集合类型数据提供的 SSCAN、HSCAN 等,可以根据执行时设定的数量参数,返回指定数量的数据,这就可以避免像 KEYS 命令一样同时返回所有匹配的数据,不会导致 Redis 变慢。以 HSCAN 为例,我们可以执行下面的命令,从 user 这个 Hash 集合中返回 key 前缀以 103 开头的 100 个键值对。

 HSCAN user 0 match "103*" 100

21.4 你遇到过redis变慢的情况吗?

\1. 使用复杂度过高的命令或一次查询全量数据;

\2. 操作 bigkey;

\3. 大量 key 集中过期;

\4. 内存达到 maxmemory;

\5. 客户端使用短连接和 Redis 相连;

当 Redis 实例的数据量大时,无论是生成 RDB,还是 AOF 重写,都会导致 fork 耗时

严重;

\7. AOF 的写回策略为 always,导致每个操作都要同步刷回磁盘;

Redis 实例运行机器的内存不足,导致 swap 发生,Redis 需要到 swap 分区读取数

据;

\9. 进程绑定 CPU 不合理;

\10. Redis 实例运行机器上开启了透明内存大页机制;

\11. 网卡压力过大。

21.5 mem_fragmentation_ratio 的值小于1 说明发生了什么

表明操作系统分配给red is的内存,小于redis 所申请内存的大小,此时redis实例内存已经不够用了,发生了swap

21.6 在和redis的实际交互中,应用程序的客户端要使用缓冲程序吗

1、可以控制客户端端发送数率,请求不会一下子全部打到客户端

2、进行主从切换时,需要一定的时间,可以先缓存客户端的请求

21.7 如何查询慢日志

redis的慢查询日志,记录了执行时间超过一定阀值的命令操作;

slowlog-log-slower-than:慢查询日志对执行时间大于多少微秒的数据进行记录

slowlog-max-len:慢查询日志最多能记录多少条记录,如果超出,则会被删除;因为慢查询太多的话,日志就会存储不下,

latency monitor 可以监控慢查询,并进行设置;

21.8 如何排查redis的 bigkey

./redis-cli --bigkeys

建议:在redis实例压力低的进行扫描检查,以面影响实例的正常运行;

参考极客时间redis编程实战与源码

猜你喜欢

转载自blog.csdn.net/weixin_43732955/article/details/116427573