二. 数据结构

思来想去,要把这个扩展目前的功能阐述完全,首先我们来描述一下相关数据结构。

数据结构是扩展运行的基石,如何构造简单的数据结构,既能完整记录相关数据,又能够减少数据查询次数提高数据进程间传输的便利性

2.1 数据结构要求

在设计扩展的数据结构时,我们需要保障以下几点。

  1. 数据结构足够简单,便于代码阅读。

  2. 数据结构具有较高效能,最多容忍O(n)复杂度。

  3. 数据结构要便于传输,由于牵扯到IPC通信,所以这个部分特别重要。

在这基础上,我们设计了三类数据结构,包括:进程全局变量SVIPC_Func_StructFunction_Prof_Struct

2.2 进程全局变量

由于目前我们的进程模型是PHP-FPM,每一个PHP-FPM进程拥有一份独立的全局变量区域,全局变量主要围绕以下几个方面来设计。

  1. INI配置文件加载变量

  2. 全局数据结构数据

数据1主要在PHP的 MI生命期阶段进行加载INI配置文件选项,数据2主要是为了完成插件功能而开辟的全局变量。

2.2.1 扩展配置参数

变量名 INI文件配置对应名称 默认值 含义
enabled PulseFlow.enabled false 是否启用插件?
disable_trace_functions PulseFlow.disable_trace_functions 空字符串 禁止跟踪的函数名列表(逗号分割)
disable_trace_class PulseFlow.disable_trace_class 空字符串 禁止跟踪的类名列表(逗号分割)
svipc_name PulseFlow.svipc_name /PulseFlow_sv_ipc System V IPC 所需要的文件路径名
svipc_gj_id PulseFlow.svipc_pj_id 1000 System V IPC 所需要的项目ID
max_package_size PulseFlow.max_package_size 0 组件内部允许的最大消息队列发包大小,超过这个大小进行分包发送
log_dir PulseFlow.log_dir 空字符串 Plugin Log Dir ( 如果此参数不为空,则开启插件日志记录,目前只会记录必要日志信息 )
sampling_rate PulseFlow.sampling_rate 100 采样率,默认是1/100,根据每次请求进行采样隔离

上面所列的参数并不是都需要一一设置,需要对若干参数进行相关解释:

  1. enabled:这个参数负责控制插件的可用开关,除了【 在php.ini文件中进行设置】 之外, 还可以【 通过 pulseflow_enable 和*pulseflow_disable 函数进行设置 】, 还可以【 在url参数中附属get参数 “pulseflowswitch=on” 或 “pulseflowswitch=off”进行选择开关 】。

  2. svipc_name:这个参数不是必须设置的参数,如果不设置就必须保障默认值的文件路径在操作系统中存在,并且php-fpm有权限访问。(这个参数代表文件路径,请保障文件存储在不易卸载的位置,tmp、dev目录不推荐)

  3. svipc_gj_id:这个参数不是必须进行设置的参数,这个参数和 svipc_name 是一对的,都必须保障和 后端程序配置相同

  4. max_package_size:这个参数用来设置消息队列的最大值,当监控数据的消息体大于此值时进入发送流程。因为为了支持更大的消息队列,需要调整内核参数,sysctl -p命令对于不同系别的Linux系统支持力度不同,centos可以不重启加载内核参数,对于其他系统,如果出现了sysctl -p 命令无法修改成功内核队列单条信息大小情况时,可以设置这个参数来达到在当前内核环境下发送信息。(这个参数非常重要,请结合内核进行理解
    max_package_size参数所牵连的内核消息队列的坑,可以参考文章:https://blog.icorer.com/index.php/archives/334/

2.3 Function_Prof_Struct 结构体

这个结构体针对每一个不同的函数,我们这里定义的不同函数,是指函数名和类名均不同的函数。结构体如下:

typedef struct Function_Prof_Struct {

    char className[CLASS_NAME_MAX_SIZE];  //类名

    char functionName[FUNC_NAME_MAX_SIZE];  //函数名

    unsigned int memoryUse;  //内存消耗

    unsigned int cpuTimeUse; //CPU时间消耗

    unsigned int refcount; //调用次数

    unsigned long funcNameHash; //函数名哈希值

    unsigned long classNameHash; //类名哈希值

} Function_Prof_Data;

通过结构体,我们发现我们定义了两个hash字段,这两个哈希采用的是 BKDRHash 字符串,通过这两个字段,我们可以大大提高后期的字符串查询效率。

2.4 SVIPC_Func_Struct 结构体

这个结构体,可以理解为一堆函数小结构体的上层结构体,也是进程间IPC通信所用的公用结构体。结构体如下:

typedef struct SVIPC_Func_Struct {

    long message_type;  //System V 消息队列的消息类型字段

    char opts[OPTS_STR_MAX_SIZE]; //消息体的附属参数

    int size; //函数的数量

    Function_Prof_Data Function_Prof_List[FUNCTION_PROF_LIST_SIZE]; //Function_Prof_Struct 结构体数组

} SVIPC_Func_Prof_Message;

特别说明:为了提高组件的控制动态化,我们提供了一个opts附属参数,这个参数通过函数 “pulseflow_set_options” 进行设置,使用样例:pulseflow_set_options(['service'=>'cloudapi']);,这个参数会传入后端程序,后端程序可以根据这个参数进行动态变化,只更新后台程序即可完成一定程度的组件变化。

2.5 SVIPC_Func_Struct 和 Function_Prof_Struct的关系

通过上面的存储结构,我们就可以跟踪每个函数的执行状况。

2.6 (借力)全局变量的存储区域

  • 拒绝反复分配与初始化:我们把 SVIPC_Func_Struct 的空间分配在静态区域,并且设置在MI阶段进行分配与初始化,这样可以保障在后面成千上万的php请求过程中,不需要进程空间分配与初始化。

  • 拒绝内存泄露:分配在静态资源区还能拒绝内存泄露

  • 拒绝序列化与反序列化:IPC通信,序列化操作的性能损耗不容小视,由于拒绝了动态分配,所以拒绝了深度拷贝,拒绝了深度拷贝,就能够更简单的进行IPC传输,由于IPC通信双方共用同一个结构体,所以拒绝了序列化和反序列化过程

文档更新时间: 2018-09-10 10:27   作者:李彪