所有文章 > API设计 > 如何打造PHP的Restful API自动化监控系统?

如何打造PHP的Restful API自动化监控系统?

背景

伴随租房业务的不断发展,租房各个子业务系统的集群数量也迎来了一波增长,提供的API接口数量和服务数量也有了井喷式增长,但在业务层自动化监控方面我们却缺失统一的工具平台,随之而来我们开发同学会经常碰到这些问题:

1. 有哪些的接口访问超时/特别慢,往往关注的时候就是服务出现瓶颈的时候。

2. 线上top uri每日的访问量是多少,没有可视化的工具,只能去单机通过awk命令统计。

3. 不知道接口的平均耗时是多少,也就没有优化服务的动力。但当时我们面对类似问题通常采用下面的方法解决:

1. 采用公司的人工埋点系统Wmonitor手动在代码里埋点,优点是能实现部分可视化和预警,缺点是埋点流程复杂每个接口都需要申请埋点值并在接口中手动埋点,管理复杂效率低下,对开发同学的开发意识要求很高。

2.  通过nginx日志查询访问详细情况如 uri,访问状态,耗时等信息,优点是能得到访问量和访问时长等数据,缺点是每次都需要人工手动查询,缺乏无可视化平台和预警,如某一台机器故障/性能故障则完全无感知 。

由于公司内部已有基于Java Web框架的API监控系统,鉴于这套系统已成熟,PHP侧可以复用其数据存储、数据视图和报警部分,然后自己实现数据采集和上报即可实现PHP可用的API监控系统。那我们的核心重点就是解决在PHP-FPM多进程模型下,如何灵活、高效、稳定的实现数据的采集和上报。

设计理念

1. 灵活定制

面对不同的业务集群,作为服务方需要兼顾灵活和标准, 首先通过从众多的接口访问信息中抽出来标准化的数据信息集,如集群名称,接口uri,http Code,访问量,访问时长,超时量,失败量等具体的数据,标准化的数据保证了传输的顺畅,为后续的分析,统计,展示奠定基础。其次提供灵活的可配置方案,兼容不同集群,不同采集间隔和自动化uri采集模型,去实现业务的灵活变通。

wrm.enable=1
wrm.tick=60
wrm.timeout=2000
wrm.collector_register_url=https://****.**.com/****/****
wrm.collector_port=****
wrm.cluster=***_***_***
wrm.total_url_num=500
wrm.enable_sync_uri=1
wrm.sync_uri_url=****.**.com/****/****

2. 低成本接入

利用PHP的底层特性以扩展形式在PHP进程启动的时候加载,避免开发同学因接入监控服务而需要做出额外的开发,实现无感知的数据采集。

3. 资源利用率高

合理选型数据结构和接入方式,依托于PHP-FPM主进程的监控进程,避免对系统额外的cpu和内存资源占用,保证线程安全调用。

系统架构

1.  首先启动PHP Zend引擎后,在注册动态扩展的过程中通过调用MINIT钩子,fork出来监听进程,从而在每次request开始/结束时 执行业务逻辑。

2. 监听根据系统配置,初始化全局变量如上报时长,内存大小,上报地址等信息,以及通过mmap开辟共享内存空间和pthread去实现线程安全上报。

3. 每次request在请求时会初始化php_request_startup,并依次遍历调用扩展中调用Rinit,在这个过程中收集到uri,http code,访问时长等信息存储如对应的结构体。

4. 监听进程根据配置tick收集指定间隔时间的数据后,通过切换数据hash的内存块指针,平滑变更hashtable的角色属性,启动上报线程,开始压缩数据并异步上报至数据中心服务侧。

功能设计&实践

1. 进程启动

PHP引擎启动过程中通过php_module_startup遍历php_extension_list中在php.ini注册的动态扩展,并依次调用对应的Minit钩子函数,在这个过程中我们在Minit中fork出来上报进程,根据配置的url个数初始化共享内存,并依据bkrdhash算法实现uri的hash存储,同时一个hash做输出,一个hash做存储,并通过指针动态切换.该进程同时会监听主进程。

2. 日志收集

在Rinit的钩子中,依赖于sapi_getenv和SG去获取本次请求的参数,如header和Server信息,根据uri路由,收集后存储于trace实体中,在此钩子中不做其他操作,保证最小粒度干扰线上的资源开销,在Rshutdown钩子中,统计响应状态,耗时等日志信息,并存储到对应的hash中。

typedef struct wrm_trace_analyze {
int mutex; // 互斥量
struct timeval total_duration;
int success_times;
int failed_times;
int timeout_times;
int server_port;
char server_addr[32];
char uri[256];
} wrm_trace_analyze;

3.  数据上报

依托于配置中的上报周期,上报线程会将数据存储的hash指针做切换,从这一刻起,另外一个空hash结构体开始接受新数据做存储,同时原输出hash开始压缩并上报数据中心服务侧,上报完成后等待下个周期的切换,如此循环往复。

关键问题

1. 如何避免对上报服务端的流量冲击,合理利用资源

鉴于每次的http请求都会产生一条日志数据,如果每次都进行上报则对服务端是个很大的流量冲击,针对这种情况我们通过合理设计本地存储结构,实现时间周期内的日志数据汇总存储,压缩&定期上报汇总数据。


2. 如何设计一个有效的数据采集数据结构

数据实现hash存储,key值采用bkrdhash算法,以质数作为种子,每个字符串的ascii加和,保证每个字符串都参与到运算中,同时hash中只存储有效的日志数据,如http code 200/500次数,时间等信息,以10000个uri举例,只额外占用不到10M内存,并在数据udp上报到服务侧之前通过gz压缩,保证资源的合理优化。

3. 如何去解决hash数据集的并发性写入

我们采用的futex内核级同步锁.在众多php-fpm在同一时间点写入同一个hash的,如果采用自旋锁+等待,在线程较多的时候其他上下文的切换的资源开销不低,但是通过futex内核级锁,在通过双向链表的队列维护竞争的线程,在占用锁资源的线程释放资源后,会按顺序从队列中唤起等待的线程写入。


4. 如何在保证对数据采集和上报流程隔离,实现线程安全的全流程监控

我们采用的方案是通过监听进程的存储层开辟一个共享内存区域,分别为分为存储区A和上报区B,在监控服务收集的过程中所有数据沉淀在A上,等待上报周期时间点一到则会将数据写入的指针指向B,此时B角色切换为存储区,A角色变更为上报区,线程将A上的数据进行上报,上报成功后clean掉数据,等待下一次的角色变更,如此循环往复,实现线程安全的异步上报流程。


5. 如何低成本部署和兼容

我们以php扩展为独立载体,不依赖任何框架,支持公司内部物理机和私有云等集群环境。业务集群需要监控服务则可将此.so文件加载到项目extension/下目录或者 php配置级的extension下,同时在项目中的extension.conf配置采集参数即可方便使用,也因此实现项目级的日志采集能力。此扩展语法同时支持PHP7.0.0以上的版本集群环境。

总结成果

Restful的自动化日志监控服务,在租房业务线集群以扩展的形式已成功部署至生产环境,各业务线负责人可以方便的通过公司的提供的可视化工具平台(WF manager)直观看到所属的接口访问详情,从而节省各个业务线手动开发,标准不一的开发代价和维护成本。 目前租房业务线累计已有几十个集群先后通过此扩展接入wfmanager系统,覆盖百余台PHP服务器,每分钟采集日志超上百万次,覆盖90%的租房业务线上流量,为服务的平稳运行提供有力保障。通过可视化的线上接口实时访问数据,对应服务的负责人实现了对所属服务的主动关注/被动报警能力,为服务质量和稳定性提供了有力的支持,具体体现在以下两个方面:

1. 在工程效率方面,过往线上出现接口访问超时现象到问题解决的时间跨度周期往往超过1天,通过此扩展接入日志采集能力后,实现了线上的访问实时监控预警,从问题出现到解决的时间跨度也降低为分钟级别,如 一些APP端的详情页在优化之前接口加载时长达到300ms以上,接入日志监控扩展后清晰看到线上的真实耗时,对应同学收到预警后,经过几轮实时技术优化对比,已成功将耗时降低为100ms以下。

2. 在数据分析维度,过往分析日志从单机awk统计到现在集群整体视角,粒度甚至细分至单机uri的级别,直观监测到实时访问数据,实现了同比,环比的流量波动分析,精准掌握产品的线上的访问流量,为保证服务可用性的增扩容以及优化提供有力决策支持。 

文章转自微信公众号@58技术

#你可能也喜欢这些API文章!