• 从零开始写一个APM监控程序(一)协议


    APM(Application Performance Monitoring)是一种用于监控和管理应用程序性能的解决方案。它通过收集、分析和报告应用程序的性能数据,帮助开发人员和系统管理员更好地了解应用程序的运行状况,识别潜在的性能问题,并进行优化。

    一般来说,APM 工具提供以下功能:

    1. 性能监控: 实时监控应用程序的性能指标,包括响应时间、吞吐量、错误率等。

    2. 事务追踪: 能够跟踪和记录应用程序中各个事务的执行情况,从而了解事务的执行路径、时间分布等。

    3. 错误检测: 检测并报告应用程序中的错误和异常,帮助快速定位和解决问题。

    4. 性能分析: 分析应用程序的性能瓶颈,提供可视化的分析工具,帮助开发人员优化代码和系统配置。

    5. 资源利用率监控: 监控硬件资源(如 CPU、内存、磁盘等)的利用率,帮助发现资源瓶颈。

    APM 工具通常可以与各种类型的应用程序一起使用,包括 Web 应用、移动应用、微服务架构等。它们通过在应用程序中嵌入代理或采集器来收集数据,并将数据传送到中央存储或云服务中进行分析和展示。

    一些常见的 APM 工具包括 New Relic、AppDynamics、Datadog、Elastic APM 等。这些工具的具体特性和支持的技术栈可能有所不同,选择合适的工具通常取决于应用程序的特点和需求。

    国产的开源东西主要是SkyWalking。Apache SkyWalking 是一个开源的应用性能管理(APM)系统,用于监控、诊断和优化分布式系统的性能。它支持多种语言,包括 Java、.NET、Node.js、Go 等,可以跟踪分布式系统中的请求流,并提供详细的性能数据和可视化的监控工具。

    以下是一些 SkyWalking 的主要特点:

    1. 分布式追踪: SkyWalking 提供了分布式追踪能力,可以跟踪请求在整个分布式系统中的传播路径,包括服务之间的调用和调用链路的详细信息。

    2. 性能指标: 提供系统的性能指标,包括吞吐量、响应时间、错误率等,以便开发人员和运维人员更好地了解系统的运行状况。

    3. 自动仪表板: 提供直观的仪表板和可视化工具,帮助用户监控和诊断分布式应用程序的性能问题。

    4. 跨语言支持: SkyWalking 支持多种语言,使其适用于各种技术栈和混合语言环境。

    5. 插件扩展: 具有插件系统,可以通过插件扩展功能,支持集成其他系统和工具。

    6. 开源社区: 作为 Apache 项目,SkyWalking 拥有活跃的开源社区,不断更新和改进系统。

    SkyWalking 有助于开发团队识别性能瓶颈、优化代码,并提高系统的可维护性。

    但是,别人的东西永远不一定完全适合自己,大多数时候改造的成本比从零开始写一个成本更高。

    典型的APM(Application Performance Monitoring)通常是通过收集、分析和展示应用程序的性能数据,以监控和优化应用程序的运行状态。以下是一般的 APM 实现思路:

    1. 数据采集(Instrumentation): 在应用程序代码中插入监控点,收集关键性能指标。这通常包括跟踪请求、记录方法执行时间、捕获错误、以及收集资源利用情况等。开发者需要在代码中嵌入监控仪器,以便收集有关应用程序性能的数据。

    2. 数据传输: 将采集到的数据传输到中央收集点。这可以通过使用 Agent、SDK 或代理来完成。这些组件可以负责收集数据并将其传输到 APM 系统的后端。

    3. 数据存储: 将数据存储在后端数据库或数据仓库中,以供分析和查询。通常,APM 系统会使用数据库或其他持久性存储来保存监控数据。

    4. 数据分析: 对收集到的数据进行分析,以识别性能瓶颈、错误和潜在的优化点。分析可能包括生成报告、绘制性能图表、检测异常等。

    5. 可视化: 将分析结果以可视化的方式展示给开发者、运维人员和其他利益相关者。这可以通过仪表板、图表、报表等方式呈现。

    6. 告警和通知: 在性能达到或超过阈值时,发出告警,通知相关人员。这有助于快速响应性能问题,保持应用程序在高性能状态。

    7. 追踪请求和事务: 通过追踪请求和事务,可以了解整个系统的性能状况,包括前端和后端服务的交互。

    8. 支持多语言和多平台: APM 工具通常需要支持多种编程语言和运行环境,以便适用于各种应用程序和服务。

    9. 安全性: 保护监控数据的安全性,确保监控数据不被未授权的访问。

    采集数据的部分分为探针和SDK两种,比如是JAVA和dotNet有虚拟机的,可以使用hook技术来拦截自己需要的方法,如果是编译型语言基本上使用的是SDK,程序员插码的方式实现。当然在linux下也可以使用eBPF的方式来构建挂钩程序,但是做到通用比较麻烦。

    我认为与其在后期加HOOK,不如在项目之初就考虑需要监控的位置,用SDK来解决。

    首先我们要解决交换协议:

    一般,采集的内容分类3类:

    • 某个代码块执行的时长效果等数据叫span,多个span可以串为一个trace;
    • 针对硬件使用率的监控一般叫metric;
    • 再有,就是分级部署时候探针(sdk)与后端之间还有一级,会对数据融合一次,减少数据量。

    我打算采用protobuf3的方式定义数据交互格式,每个消息都定义最基本的常用的数据,其他的部分作为键值对可以扩展,后续只需要定义键值对就好了;其实也没有啥好扩展的,这么多年就这么点东西;数据传输可以用json,也可以用protobuf编码,目前大多数产品都是http传输,我觉得可以再进一步,使用http2或者websocket更好。

    1. syntax = "proto3";
    2. package model;
    3. option go_package = "birdim/server/pb";
    4. // 服务定义
    5. service ApmService {
    6. rpc AgentEvent (ApmAgentMsg) returns (ApmServerMsg);
    7. rpc FrontEvent (ApmAgentMsg) returns (ApmServerMsg);
    8. }
    9. // 发送起端都是ApmAgentRequest 接收端应答ApmResponse
    10. // 具体根据类型字段区分
    11. // 1) ApmHello ----> ApmHelloReply
    12. // 2) ApmSpan ----> ApmReply
    13. // 3) ApmStatistic ----> ApmReply
    14. // 4) APmUsage ----> ApmReply
    15. // 5) ApmHeartBeat ----> ApmReply | ApmCtrl
    16. // 服务端发起参数调整 执行后应答
    17. // 6) ApmCtrl ----> ApmReply
    18. // 展示端发起查询 服务端返回数据
    19. // 7) ApmQuery ----> ApmData
    20. // 消息的所有类型
    21. enum ApmMsgType {
    22. // Invalid value. The name must be globally unique.
    23. AMTUnused = 0;
    24. AMTHello= 1;
    25. AMTHelloReply = 2;
    26. AMTSpan = 3;
    27. AMTStatistic = 4;
    28. AMTCtrl = 5;
    29. AMTReply = 6;
    30. AMTQuery = 7;
    31. AMTData = 8;
    32. AMTHeartBeat = 9;
    33. AMTUsage = 10;
    34. }
    35. // 控制命令代码
    36. enum ApmCmdCode{
    37. ACUnused = 0;
    38. ACRestart = 1;
    39. ACSuspend = 2;
    40. ACResume = 3;
    41. ACConfig = 4;
    42. }
    43. // 执行状态代码
    44. enum ApmStateCode{
    45. ASUnused = 0;
    46. ASOk = 1;
    47. ASFail = 2;
    48. ASPending = 3;
    49. }
    50. // 键值对,未用
    51. message KeyValuePair {
    52. // 定义单个键值对
    53. string key = 1;
    54. string value = 2;
    55. }
    56. // 如果需要登录,则需要认证
    57. enum ApmAuthType{
    58. AUTNone = 0; // 可以不使用
    59. AUTPass = 1; // 前端客户使用
    60. AUTKey = 2; // agent 使用
    61. }
    62. // 如果需要登录,一定是从客户端发起
    63. message ApmHello{
    64. int64 seq = 1;
    65. int64 tm = 2;
    66. string u_id = 3;
    67. ApmAuthType auth_type = 4; // 认证类型
    68. string secret = 5; // 密码或者key,或者啥也不用
    69. map<string, string> params = 6;
    70. }
    71. // 协商的应答
    72. message ApmHelloReply{
    73. int64 seq = 1;
    74. int64 tm = 2;
    75. string u_id = 3; // 应答者的身份
    76. string state = 4; // 继续其他步骤,成功或者错误
    77. map<string, string> params = 6;
    78. }
    79. // 采集的具体内容
    80. message ApmSpan{
    81. // Request fields here
    82. int64 seq = 1;
    83. int64 tm = 2;
    84. string u_id = 3;
    85. // APM-related fields
    86. string trace_id = 4;
    87. string trace_name = 5;
    88. string span_id = 6;
    89. string parent_span_id = 7;
    90. string span_name = 8;
    91. string file = 9;
    92. string class = 10;
    93. string func = 11;
    94. string line = 12;
    95. int64 tm_start = 13;
    96. int64 tm_end = 14;
    97. ApmStateCode state = 15;
    98. string detail = 16;
    99. map<string, string> params = 17;
    100. }
    101. // 如果需要在agent或者collector 统计过滤
    102. message ApmStatistic{
    103. int64 seq = 1;
    104. int64 tm = 2;
    105. string u_id = 3;
    106. string trace_name = 4;
    107. string span_name = 5;
    108. int64 tm_start = 6;
    109. int64 tm_end = 7;
    110. int64 span_count = 8;
    111. int64 span_errors = 9;
    112. int64 span_slow_count = 10;
    113. int64 span_avg_time_cost = 11;
    114. int64 span_max_time_cost = 12;
    115. int64 span_min_time_cost = 13;
    116. map<string, string> params = 14;
    117. }
    118. message ApmCtrl{
    119. int64 seq = 1;
    120. int64 tm = 2;
    121. string u_id =3;
    122. ApmCmdCode code = 4; // 控制的代码
    123. string cmd = 5;
    124. string detail = 6;
    125. map<string, string> params = 7;
    126. }
    127. // 应答一条或者多条上报
    128. message ApmReply{
    129. int64 seq = 1;
    130. int64 tm = 2;
    131. string u_id = 3;
    132. int64 ref_seq1 = 4;
    133. int64 ref_seq2 = 5;
    134. int32 state = 6;
    135. string detail = 7;
    136. }
    137. // 心跳数据
    138. message ApmHearBeat{
    139. int64 seq = 1;
    140. int64 tm = 2;
    141. string u_id = 3;
    142. }
    143. // 使用率
    144. message ApmUsage{
    145. int64 seq = 1;
    146. int64 tm = 2;
    147. string u_id = 3;
    148. int64 start_tm = 4; // 搜索开始时间
    149. int64 end_tm = 5; // 搜索结束时间
    150. int64 delta_tm = 6; // 时间片大小
    151. map<string, string> params = 7; // 内存,CPU,磁盘;的最大值,最小值,平均值,
    152. }
    153. // 查询类型
    154. enum ApmQueryType{
    155. AQTUnused = 0;
    156. AQTUsage = 1; // 利用率按时间片绘图
    157. AQTSpanDetail = 2; //
    158. AQTSpanStatistic = 3;
    159. }
    160. enum ApmOpCodeType{
    161. AOCTUnused = 0; // 说明是一个括号,里面有多个child
    162. AOCTEqual = 1; // x = 1
    163. AOCTUnEqual = 2; // x != 1
    164. AOCTBetween = 3; // 1 < x < 2
    165. AOCTBetweenBoth = 4; // 1 <= x <= 2
    166. AOCTBetweenLeft = 5; // 1 <= x < 2
    167. AOCTBetweenRight = 6; // 1 < x <= 2
    168. AOCTGreaterThan = 7; // x > 1
    169. AOCTGreaterOrEqual = 8; // x >= 1
    170. AOCTLessThan = 9; // x < 9
    171. AOCTLessOrEqual = 10; // x <= 9
    172. AOCTLike = 11; // 正则匹配字符串
    173. AOCTUnlike = 12;
    174. AOCTIn = 13; // 集合之中
    175. }
    176. // 定义一个查询条件的树状结构
    177. message ApmQueryCondition{
    178. string name = 1;
    179. string field = 2; // 字段,
    180. ApmOpCodeType op_code = 3; // 计算符,值个数与运算符有关
    181. bool join_code = 4; // 同级条件合并运算符,and --> true, or ---> false
    182. repeated string values = 5; // 根据字段的类型做数据转换
    183. repeated ApmQueryCondition children = 6; // 如果有多级,就用子节点方式,本级就相当于一个括号,op = Unused
    184. }
    185. // 查询, 查询条件为空,就是认为全都要,统计类型需要时间分片大小,而详情不需要
    186. message ApmQuery{
    187. int64 seq = 1;
    188. int64 tm = 2;
    189. string u_id = 3;
    190. int64 start_tm = 4; // 搜索开始时间
    191. int64 end_tm = 5; // 搜索结束时间
    192. int64 delta_tm = 6; // 时间片大小
    193. ApmQueryType type = 7;
    194. repeated ApmQueryCondition conditions = 8; // 一级条件也是一组,按照顺序合并
    195. }
    196. // 使用率的列表
    197. message ApmUsageList{
    198. repeated ApmUsage list = 1;
    199. }
    200. // 上报详细情况列表
    201. message ApmSpanList{
    202. repeated ApmSpan list = 1;
    203. }
    204. message ApmStatisticList{
    205. repeated ApmStatistic list = 1;
    206. }
    207. // 查询数据得到的结果
    208. message ApmData{
    209. int64 seq = 1;
    210. int64 tm = 2;
    211. string u_id = 3;
    212. int64 start_tm = 4; // 搜索开始时间
    213. int64 end_tm = 5; // 搜索结束时间
    214. int64 delta_tm = 6; // 时间片大小
    215. ApmQueryType type = 7;
    216. oneof message {
    217. ApmUsageList usages = 8;
    218. ApmSpanList spans = 9;
    219. ApmStatisticList statistic = 10;
    220. }
    221. }
    222. // the message that one agent would send
    223. message ApmAgentMsg{
    224. ApmMsgType date_type=1;
    225. oneof message {
    226. ApmHello hello = 2;
    227. ApmSpan span = 3;
    228. ApmStatistic statistic = 4;
    229. ApmUsage usage = 5;
    230. ApmReply reply = 6;
    231. }
    232. }
    233. // 前端查询使用
    234. message ApmFrontMsg{
    235. ApmMsgType date_type=1;
    236. oneof message{
    237. ApmHello hello = 2;
    238. ApmQuery query = 3;
    239. }
    240. }
    241. // reponse from server, collector or agent
    242. message ApmServerMsg{
    243. ApmMsgType date_type=1;
    244. oneof message{
    245. ApmHelloReply hello_reply = 2;
    246. ApmReply reply = 3;
    247. ApmCtrl ctrl = 4;
    248. ApmData data = 5;
    249. }
    250. }

    未完待续……

  • 相关阅读:
    JavaScript -- Map对象及常用方法介绍
    如何开发一个标准的云原生应用?
    Mysql连接无效(invalid connection)解决方案
    [补题记录]LeetCode 151.反转字符串中的单词
    C++语法基础(3)——分支结构程序设计
    MongoDB数据库网站网页实例-编程语言Python+Django
    python lower函数用法
    2025考研~数据结构试卷
    FP为什么更适合做独立站?自己掌握话语权才是王道
    javascritp如何判断是从刷新(重新加载)、正常打开(或链接打开)、还是从浏览器回退进入页面的
  • 原文地址:https://blog.csdn.net/robinfoxnan/article/details/134479631