找回密码
 会员注册
查看: 10|回复: 0

共享内存原理与VCS监控采集实战

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
72784
发表于 2024-10-5 04:07:34 | 显示全部楼层 |阅读模式
一、前言共享内存广泛用于Redis,Kafka,RabbitMQ 等高性能组件中,本文主要提供一个共享内存在广告埋点数据采集的实战场景。二、共享内存原理1、原理在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。2、与传统文件对比共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式, 因为进程可以直接读写内存,而不需要任何 数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝 共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内 存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直 到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映 射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。传统文件UNIX 访问文件的传统方法是用 open 打开它们,如果有多个进程访问同一个文件,则每一个进程在自己的地址空间都包含有该文件的副本,这不必要地浪费了存储空间。下图说明了两个进程同时读一个文件的同一页的情形。系统要将该页从磁盘读到高速缓冲区中,每个进程再执行一个存储器内的复制操作将数据从高速缓冲区读到自己的地址空间。共享存储映射现在考虑另一种处理方法:进程 A 和进程 B 都将该页映射到自己的地址空间,当进程 A 第一次访问该页中的数据时, 它生成一个缺页中断。内核此时读入这一页到内存并更新页表使之指向它。以后,当进程B访问同一页面而出现缺页中断时,该页已经在内存,内核只需要将进程 B 的页表登记项指向次页即可。3、mmap()(1)mmap()系统调用mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap()系统调用形式如下:void*mmap(void*addr,size_tlen,intprot,intflags,intfd,off_toffset)(滑动可查看)mmap的作用是映射文件描述符fd指定文件的 [off,off + len]区域至调用进程的[addr, addr + len]的内存区域:参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的,MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。(2)mmap()返回地址的访问对mmap()返回地址的访问,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:三、VCS 共享内存采集实战VCS(vivo control system): 负责全网所有类型的监控指标采集,为上游运维平台提供底层命令通道能力和全网插件升级管控能力。1、数据结构2、分区读写为了要确保一个进程在写的时候不能被读,我们使用idx来标记可读块。3、规则,指标和值下图描述的是从连续内存空间转化成【规则,维度,值】语义的过程:4、源码分析5、general.proto通用监控上报协议:general.proto syntax = "proto2";package general;message Data { map kv = 1;}message GeneralData { optional string rule_id = 1; repeated Data data = 2; optional int64 count = 3; optional int64 left_size = 4; optional int32 version = 5;}(滑动可查看)6、constant.go 配置参数| 4k protect | magincNum1(4byte) | idx(4byte) | OssMapSz(1024*128byte)*2 | 4*64byte预留长度 | magincNum2(4byte) | 4k protect |package moni_shm const ( OssShmId uint32 = 0x3eeff00 MagicNum1 uint32 = 0x650a218 MagicNum2 uint32 = 0x138a4f2 CreateShmLock = "/var/run/.oss_shm_lock" OssMapOneAttrCnt = 1024 * 128 //1024 个规则 OssOneAttrEntryCnt = 128 //每个规则有128个指标 EntrySz = 4 OssMapCnt = 2 OneAttrSz = OssOneAttrEntryCnt * EntrySz OssMapSz = OssMapOneAttrCnt * OneAttrSz OssAttrSz = OssMapSz*OssMapCnt + 4 + 4 + 64*4 + 4 defaultIntervalSec = 60 defaultTopic = "moni_general_shared_memory")(滑动可查看)7、util.go 工具类内存清零工具和"整页"分配:cd package moni_shmimport ( "unsafe")//取整分配func align(actual, to uint64) uint64 { return (actual + to - 1) / to * to}//连续空间清0func zero(ptr uintptr, bts uint64) { if 0 == bts { return } const sz = 4096 var next uint64 cnt := 0 for ; next+sz
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-12 01:49 , Processed in 0.936254 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表