用Python实现StatsD Server(一):蓝图

对于一名终日奋斗在业务最前线的后端工程师来说,监控打点服务就像是全视之眼,时刻监督着线上数据,并且在故障发生时为工程师们提供诊断信息。

监控打点服务收集的数据在我看来主要可以分为两类,一类是非业务类数据,一类是业务类数据。一般来说,运维工程师会对非业务类的数据比较关注,如线上服务器CPU负载,内存使用量等;而对于业务团队的后端工程师来说,则更为关注业务类的数据,比如说对于一个负责红包开发的业务团队,就会比较关注红包发放API的QPS,一旦红包发放的QPS相比往日异常,则可能线上出现了业务逻辑上的异常。

说到红包,就顺便说个笑话吧,有一天早上饿了么负责红包业务的工程师收到oncall,线上发红包API的QPS相比往日增加了一倍,怀疑是昨晚的发布导致了红包的异常发放。工程师们仔细地review了代码,发现昨晚发布的代码确实没有问题,半小时后,他们终于发现了原因:美团外卖的服务器挂了😊。

扯回正题,监控打点服务还有一个非常重要的报警功能,一旦某个数据指标达到工程师的阀门值,则通过邮件,短信,钉钉等方法通知工程师。

其实监控打点服务在当今业界已经有很多成熟开源的方案,我现在要实现的只不过是再造一次轮子罢了,但是既然要造轮子,总得清楚这个轮子的结构和功能。那么首先来谈谈,实现一个监控打点服务需要哪些组件。

监控打点服务,主要分为三个组件,分别是Client,Server,Backend

  • Client可以视为C/S架构中的C,通常作为SOA框架中的一个组件实现,不同的SOA服务通过Client发送打点数据。
  • Server则可以视为C/S架构中的S,负责收集来自不同服务的打点数据,定期将数据发送到Backend。
  • Backend可以视为存储磁盘,存储打点数据并提供查询功能,通常实现为一个时序数据库。

接下来我将会通过Python实现Client组件和Server组件,读者只需具有基本的Python基础和后端服务概念即可。

组件关系

监控组件

Client组件通过StatsD协议与Server进行沟通,Server则根据Backend的沟通协议,定时将收集到的数据存储到Backend之中。

在了解了三个组件的关系以后,下面来逐一分析每个组件需要实现的具体功能。

Client组件

指标函数

Client组件既然是面向广大后端工程师提供打点服务,那么最重要的莫过于提供打点的指标函数了,StatsD协议中提供了四种比较常见的指标函数:

  • counter:counter函数就是最常见的计数器操作,对某一个key的value进行增加或者减少。
  • timer:timer函数主要用于表示一个时间段内的数据变化,比如在一段时间内的最大值,最小值,平均值。
  • gauge:gauge函数与counter函数类似,但是Server会对gauge指标函数的数据进行特殊处理,在存储数据到Backend时不会重置gauge指标函数的数据。
  • set:set函数的概念类似于Python中的set概念,Server会收集通过set指标函数的unique key,形成一个集合。

之前提到Server会定时将数据存储到Backend中,其中counter,timer,set指标函数相关的数据在发送以后都会重置数据,而gauge指标函数相关的数据则会保留。

两个协议

Client与Server之间的沟通需要两个协议,一个是TCP/IP层的协议,一个是文本协议。

TCP/IP层的协议只有两个选择:选择TCP还是UDP,TCP主要用于一些对数据准确性比较重要的场景,比如一天只会发送一次的打点数据;而UDP则适用于短时间内发送大量数据的场景,对于性能也比较友好。

文本协议则选择StatsD协议,协议的规范非常简单:

1
<key>:value|type[|@sample_rate]

这里解释一下sample_rate的概念,sample_rate是介于0到1之间的一位小数,假设sample_rate设置为0.1,那么对于同一个key,10次之中大概只会有一次数据会被真正发送到Server中,当Server知道sample_rate为0.1时,就会自动将value乘上10。通过这种抽样的方法,虽然降低了打点数据的准确率,但是进一步提高了数据传输性能。

Server 组件

Server组件主要负责收集Client的数据,并将数据定时flush到Backend中。

基本功能

  • Server需要常驻于程序后台,因此将被实现为守护进程,由init进程管理。
  • Server需要根据StatsD协议解析外部的数据,并将数据存储于内存的数据结构中。
  • Server需要实现一个定时器,将数据定时flush到Backend中,可选的定时器模型如多线程模型(threading.Timer),协程模型(gevent)等。
  • Server需要对接多个Backend,因此需要将Backend的一些基本操作抽象出来。
  • Server启动时需要从命令行中读取很多的配置参数,一般来说Python的argparse库足以应付大多数的情况,但是这一回我选择使用更为优雅的click,写法则可以参考Gunicorn中settings meta写法。

协议交互

Server除了通过StatsD协议与Client进行交互以外,Server还要规定协议与Backend进行交互,以Graphite作为Backend为例,能与Graphite沟通的协议有纯文本的HTTP协议,Python的Pickle协议,AMQP协议等。

Backend组件

Backend通常使用开源的分布式时序数据库,在本系列教程中并不会实现,但是有兴趣的同学可以参考graphite的whisper组件,是一个通过Python实现的800多行的时序数据库,这规模大小实在是很有让人一探究竟的冲动。

0%