近期项目进展

2020-06-26

摘要

我在6月份凭借着热情和夏日的烈火先后开始了几个小项目,分别是explorebeyondthestars/online——网站访客流量统计系统的后端(1),explorebeyondthestars/online-frontend——网站访客流量统计系统的前端(2),explorebeyondthestars/links——博客友情链接自助提交系统(3),在其中,(1)和(2)是我主要维护的项目,而(3)则更多的是出于探索性质:想了解一下实现一个简单的CRUD应用是否简单、不用数据库是否能实现相应的功能,以及实现的大概过程等等,后面可能会取消(3)的专属的前端页面,并把链接重定向到当前这个博客的相应页面,同时把用户操作接口简化,做成HTML表单的形式并取代现在的JSON字符串输入.在这里我主要说(1)和(2).

网站访客流量统计系统

它分为两部分,浏览器部分(客户端)和服务器部分(服务端),具体是,当一个访客访问装有该系统的网站(以下简称「网站」)时,浏览器部分会首先利用window.localStorage这个浏览器实现了的API(它类似Cookie)检测这个用户是否是新用户,如不是则向服务端请求一个新的唯一标识符并利用window.localStorage使之持久化在访客电脑的本地,之后向服务端周期性地发送带有该标识符以及一些附加信息(UA,Origin,Date.now(),以及将来可能还有Referer等,具体见explorebeyondthestars/online)的被称作是「心跳」(heartbeat)的JavaScript对象,服务端的工作是存储这些「心跳」对象,并在收到来自explorebeyondthestars/online-frontend的请求的时候,从数据库中取出一定时间内的「心跳」对象集,执行一些计算,并向explorebeyondthestars/online-frontend返回计算结果,返回的计算结果经过处理,效果如这个Demo

相比上一次仅仅实现了一个能输出JSON内容的后端,进展就是我已经实现了一个基本可看的访客流量统计可视化系统,可以直观地看到各个统计数字,同时,还配有相应的图表,其中,「描述性统计」主要统计各个时间区段内系统所观测到的独立访客数量,其中,「过去5分钟内独立访客数量」可以被认为是「当前在线数量」,它代表网站即刻(instant)的热度(popularity),「过去1天内独立访客数量」它代表一般的热度,而「过去1个月内独立访客数量」它代表平均而言中期(mid-term)热度.

在「移动平均」这一节中,前三个数字能够反应即刻的网站热度变化,如果#1 > #2 > #3那么说明热度在快速上升,反之则说明热度在快速下降,其他则具体分析,中间三个数字代表最近几天的热度变化,如果#3 > #4 > #5表明这几天网站的热度有所增长,反之同理,而后面三个数字代表中期热度变化趋势,如果#6 > #7 > #8则说明中长期而言网站的热度是增长的,反之同理.

在「时间序列」这一节中,「图1-1」所显示的是从此刻到2592000毫秒(也就是30天)之前这段时间内,以24小时为周期,以每24小时所录得独立访客数为观测值的时间序列的时序图,当鼠标悬浮在相应的区段上时,会有文字补充显示具体的时间范围和录得的具体的独立访客人数,具体显示效果可能会随版本迭代发生变化,可参见此Demo

在「统计推断」这一节中,我们主要试图从样本数据重建(re-introduce)总体分布,具体地,来自一个独立访客的浏览器发送的两个「心跳」对象是属于同一个「会话」当前仅当这两个「心跳」对象的发送时间(或服务端接收时间)的差的绝对值不超过30秒,依据该规则我们能复原出每一个「会话」的生存周期,以给定时间区段内共计录得的所有「会话」组成的集合作为样本集,统计出各个长度的会话出现的频数并做出图中的「图2-1」所展示的是频数分布直方图.从直方图中我们可以看到,大部分的会话时长都是处在0到10分钟这个区段,令人欣慰的是也有少数的样本显示会话时长可达20分钟以上.直方图的$x$轴的采样范围的选取采用了“$3 \sigma$原则”——取所有大小不超过$x_m + 3 sd(x)$的样本,其中$x_m$是样本中位数,而$sd(x)$是样本标准差(standard deviation).

每个会话的长度是一个随机变量不妨记做$X$它来自未知总体$D$,现有来自未知总体$D$的观测值$X_1, X_2, \cdots, X_N$,我们依据公式

$$ \hat{F}(x) = \sum_{i = 1}^{N} I(X_i \leq x) / N $$

用$\hat{F}(x)$作为该未知总体分布$D$的累积概率函数(Cumulative Probability Function)$F(x)$的估计,估计结果见Demo图示中的「图2-2」.公式中的$I(\cdot)$是「示性函数」,它检查括号中的表达式是否成立,当括号中的表达式的值为「真」时,函数的返回值是1,否则是0.从「图2-2」中可以看到约莫在$x=8$时累计概率就已经达到了$70%$,说明百分之70的会话时长是在8分钟以内.

有了来自经验分布的$\hat{F}(x)$我们可以对现有数据做拟合优度检验,从「图2-1」的直方图中我们可以看到数据近似服从泊松分布或指数分布(常识告诉我们「会话时长」更应该服从指数分布)而不是正态分布,我们可以用Kolmogorov–Smirnov检验方法,首先计算

$$ D_n = max |\hat{F}(x) - F(x)| $$

然后再用$D_n$和Kolmogorov–Smirnov临界值表中查得的临界值比对判断是否拒绝原假设$H_0: \text{样本数据不是产生自一个指数分布总体}$,其中$F(x)$是作为参照的一个指数分布总体的累积概率函数.另外,卡方检验(Chi-square test)也能起到类似的效果.

从开发的角度

相比之前的版本,我们用stream.Readablestream.Writable取代了旧的基于HTTP的模块间的通信方式,废除了/identitiesLogs这个API,同时/identities/heartbeats都变为「仅可写」,数据的外部读取通道仅剩下/onlinesInfo.同时,流可以使「读取完毕」这个消息被传导到DataManagementSystem模块,在数据调用方读取完毕后关闭相应的数据库连接.

Caddy的自动配置模块被移除因为新的开发思路指导我们在开发一个系统时应当专注于使这个系统在它本该更好地完成的任务上完成得更好而不是尝试去完成本不该由它完成的任务.同时,这样做,方便使用者将该系统部署到更广阔的天地,例如Cloudflare Workers或者Amazom Lambda,以及方便更换前置Web Server模块.

在前端explorebeyondthestars/online-frontend和后端explorebeyondthestars/online我们都实现了一些简单的缓存机制,使得不必要的数据库请求/HTTP请求被消去,从而优化总体性能.例如,每30秒内我们的后端系统的StatsAggregator模块只会计算一次数据,30秒内出现的多次请求获得的都将是同一份数据.

后端系统变得更加简单,代码变得更加简练和健壮,相比之前的版本我们丢弃了很多不必要的代码,丢弃或整合了很多不必要的功能单元,

ng分支引入的新设计

ng分支引入的新设计

图中所示是ng分支所引入的新设计,相比之前,功能模块的数量大幅减少,功能模块的布局看起来更加大气和精简,其中HeartbeatsCollectorIdentitiesCollectorOnlinesInfoServer可看做是三个独立的服务器兼功能单元,可以分别启动,浏览器端的Heartbeats仅向服务器端的HeartbeatsCollector发送信息,Identities仅向服务器端的IdentitiesCollector发送信息,数据管理统一由DataManagementSystem提供的流实现,在Analytics分支我们可以按需地自由添加或者删减负责统计量计算的功能单元,其中StatsAggregator负责整合并缓存OnlinesInfoStatsTimeSeries计算出来的统计量(再加上现在的StatisticalInferences),而OnlinesInfoServer仅和StatsAggregator通信,实现了模块化设计.三个Division职责分明,Collectors Division负责收集,Data Management Divison复制存和取,Analytics Division负责计算和输出.

Githook远端仓库自动拉取和Gulp自动部署业已实现,极大地提高了开发效率,Caddy配置功能被交由外部项目实现.

未来展望

向优秀项目Plausible.io借鉴,实现更加简明且赏心悦目的用户界面,使得数据的展示看起来更加直观和一目了然.实现Referer的统计.创建可供直接部署的Docker镜像以简化安装部署难度和提高平台间兼容性.套用Amazon API Gateway,套用Cloudflare Workers,增强服务可用性监控和势态感知能力,撰写详细的文档和代码注释,提供完整的API参考文档.

项目进度analytics

小小的改进给探索子博客带来了大大的进步

展示一下我自己做的友链自助提交系统!