VLDB顶会论文Async-fork解读与Redis在得物的实践(2)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
性能测试 PTS,5000VUM额度
简介: VLDB顶会论文Async-fork解读与Redis在得物的实践

3. Fork原理

在默认fork的调用过程中,父进程需要将许多进程元数据(例如文件描述符、信号量、页表等)复制到子进程,而页表的复制是其中最耗时的部分(占据fork调用耗时的97%以上)。

Linux的fork()使用写时拷贝 (copy-on-write) 页的方式实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。在创建子进程的过程中,操作系统会把父进程的「页表」复制一份给子进程,这个页表记录着虚拟地址和物理地址映射关系,此时,操作系统并不复制整个进程的物理内存,而是让父子进程共享同一个物理内存。同时,操作系统内核会把共享的所有的内存页的权限都设为read-only。

那什么时候会发生物理内存的复制呢?

当父进程或者子进程在向共享内存发起写操作时,内存管理单元MMU检测到内存页是read-only的,于是触发缺页中断异常(page-fault),处理器会从中断描述符表(IDT)中获取到对应的处理程序。在中断程序中,内核就会把触发异常的物理内存页复制一份,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写,于是父子进程各自持有独立的一份,之后进程才会对内存进行写操作,这个过程也被称为写时复制(Copy On Write)。

image.png


4. Fork的痛点

在原生fork下,在父进程调用fork()创建子进程的过程中,虽然使用了写时复制页表的方式进行优化,但由于要复制父进程的页表,还是会造成父进程出现短时间阻塞,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长。

我们在测试中很容易观察到fork产生的阻塞现象,以及fork造成的Redis访问抖动现象。


4.1 测试环境

Redis版本:优化前Redis-server

机器操作系统:无Async-fork特性的系统

测试数据量:21.63G

127.0.0.1:6380> info memory
# Memory
used_memory:23220597688
used_memory_human:21.63G


4.2 阻塞现象复现

在使用Redis-benchmark压测的过程中,手动执行bgsave命令,观察fork耗时和压测指标TP100。

使用 info stats 返回上次fork耗时:latest_fork_usec:183632,可以看到fork耗时183毫秒。

在压测过程中分别不执行bgsave和执行bgsave,结果如下:

# 压测过程中未执行 bgsave
[root@xxx bin]# Redis-benchmark -d 256 -t set -n 1000000  -a xxxxxx -p 6380
====== SET ======
  1000000 requests completed in 8.15 seconds
  50 parallel clients
  256 bytes payload
  keep alive: 1
99.90% <= 1 milliseconds
100.00% <= 1 milliseconds
122669.27 requests per second
# 压测过程中执行 bgsave
[root@xxx bin]# Redis-benchmark -d 256 -t set -n 1000000  -a xxxxxx -p 6380
====== SET ======
  1000000 requests completed in 13.97 seconds
  50 parallel clients
  256 bytes payload
  keep alive: 1
86.41% <= 1 milliseconds
86.42% <= 2 milliseconds
99.95% <= 3 milliseconds
99.99% <= 4 milliseconds
99.99% <= 10 milliseconds
99.99% <= 11 milliseconds
99.99% <= 12 milliseconds
100.00% <= 187 milliseconds
100.00% <= 187 milliseconds
71561.47 requests per second

从压测数据可以看到,单机环境下压测,压测时未执行bgsave,TP100约1毫秒;如果压测过程中,手动执行bgsave命令,触发fork操作,TP100达到187毫秒。


4.3 Strace跟踪fork过程耗时

strace 常用来跟踪进程执行时的系统调用和所接收的信号。

$ strace -p 32088 -T -tt -o strace00.out
14:01:33.623495 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fbe5242fa50) = 37513 <0.183533>
14:01:33.807142 open("/data1/6380/6380.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 60 <0.000018>
14:01:33.807644 lseek(60, 0, SEEK_END)  = 8512 <0.000017>
14:01:33.807690 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=528, ...}) = 0 <0.000010>
14:01:33.807732 fstat(60, {st_mode=S_IFREG|0644, st_size=8512, ...}) = 0 <0.000007>
14:01:33.807756 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbe52437000 <0.000009>
14:01:33.807787 write(60, "35994:M 21 Mar 14:01:33.807 * Ba"..., 69) = 69 <0.000015>
14:01:33.807819 close(60)               = 0 <0.000008>
14:01:33.807845 munmap(0x7fbe52437000, 4096) = 0 <0.000013>

由于Linux中通过clone()系统调用实现fork();我们可以看到追踪到clone系统调用,并且耗时183毫秒,与 info stats 统计的fork耗时一致。


5. Async-fork

鉴于以上linux原生fork系统调用的痛点,对于像Redis这样的高性能内存数据库,将会增加fork期间的用户访问延迟,论文中设计了一个新的fork(称为Async-fork)来解决上述问题。

Async-fork设计的核心思想是将fork调用过程中最耗时的页表拷贝工作从父进程移动到子进程,缩短父进程调用fork时陷入内核态的时间,父进程因而可以快速返回用户态处理用户查询,子进程则在此期间完成页表拷贝。与Linux中的默认原生fork相比,Async-fork显著减少了Redis快照期间到达请求的尾延迟。


5.1 Async-fork 的挑战

然而,Async-fork的实现过程中,实际工作并非描述的这么简单。页表的异步复制操作可能导致快照不一致。以下图为例,Redis在T0时刻保存内存快照,而某个用户请求在T2时刻向Redis插入了新的键值对(k2, v2),这将导致父进程修改它的页表项(PTE2)。假如T2时刻这个被修改的页表项(PTE2)还没有被子进程复制完成, 这个修改后的内存页表项及对应内存页后续将被复制到子进程,这个新插入的键值对将被子进程最终写入硬盘,破坏了快照一致性。(快照文件应该记录的是保存拍摄内存快照那一刻的内存数据)

image.png

图片来源于:参考资料[1] 第8页

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
29天前
|
存储 缓存 NoSQL
深入理解Django与Redis的集成实践
深入理解Django与Redis的集成实践
49 0
|
6月前
|
存储 缓存 NoSQL
蚂蚁金服P7私藏的Redis原理与实践内部笔记
Redis 是完全开源免费的,是一个高性能的key-value类型的内存数据库。整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
108 1
|
6月前
|
缓存 NoSQL Java
Spring Cache 缓存原理与 Redis 实践
Spring Cache 缓存原理与 Redis 实践
342 0
|
存储 NoSQL Linux
VLDB顶会论文Async-fork解读与Redis在得物的实践(1)
VLDB顶会论文Async-fork解读与Redis在得物的实践
121 0
|
NoSQL 测试技术 Linux
VLDB顶会论文Async-fork解读与Redis在得物的实践(3)
VLDB顶会论文Async-fork解读与Redis在得物的实践
144 0
VLDB顶会论文Async-fork解读与Redis在得物的实践(3)
|
9天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
66 22
|
15天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:百万级数据统计优化实践
【10月更文挑战第21天】 在处理大规模数据集时,传统的单体数据库解决方案往往力不从心。MySQL和Redis的组合提供了一种高效的解决方案,通过将数据库操作与高速缓存相结合,可以显著提升数据处理的性能。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
46 9
|
2月前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
5月前
|
网络协议 NoSQL Redis
SMC-R 透明加速 TCP 技术,在 Redis 场景下的应用实践 | 干货推荐
SMC-R 作为一套与 TCP/IP 协议平行、向上兼容 socket 接口、底层使用 RDMA 完成共享内存通信的内核协议栈,其设计意图是为 TCP 应用提供透明的 RDMA 服务,同时保留了 TCP/IP 生态系统中的关键功能。
|
NoSQL 测试技术 Redis
VLDB顶会论文Async-fork解读与Redis在得物的实践(4)
VLDB顶会论文Async-fork解读与Redis在得物的实践
121 0
VLDB顶会论文Async-fork解读与Redis在得物的实践(4)
下一篇
无影云桌面