Matomo|Matomo 从了解到落地——页面流量统计与分析最佳实践
背景
在开发面向内部使用的「内容管理平台」的过程中,我们不时会收到一些页面问题的反馈,但在本地调试的过程中,有大量无法在本地重现的问题,这些问题的出现跟用户的访问设备、网络环境、访问路径可能存在关联。为了方便快捷地去定位这些问题,我们试图为所有页面点击操作都加上打点记录,但在实际操作中,由于业务变更频繁,开发框架的限制,展示打点数据较为复杂等因素,通过打点排查问题的实际效果并不理想,因此我们希望引入完整的流量统计和用户行为分析来定位问题。
不同的方案分析对比
对于流量统计和用户行为分析记录的工具,行业内已经有大量成熟的解决方案,相对于自行打点,这些专门的流量通过平台和工具对于业务的基本没有侵入性,也解决了如何展示数据的问题。这些平台和工具中,有著名的 Google Analytics、百度统计、WebTrends 等,也有相对冷门的今天的主角 —— Matomo,而这些方案之间各有优劣:
解决方案/平台 | 优势 | 劣势 |
---|---|---|
Google Analytics | 部署简单,只需在页面加入 JS 追踪器代码,数据分析快(小时级别),功能强大,分析维度丰富 | 数据量大的时候偶尔会丢失数据,无法定制化 |
Adobe Analytics | 数据展示清晰明了,功能强大 | 部署复杂,只有付费版本,技术支持和文档都较少 |
WebTrends | 数据分析维度丰富,报告全面,监控过程安全 | 主要针对大客户,费用非常高 |
CNZZ | 部署和接入简单,分析功能易用,报告简洁 | 没有用户细分数据,也不支持用户路径分析,功能较为单一 |
Matomo | 对标 Google Analytics 的功能,接入简单,功能强大,分析维度丰富,支持私有化部署,包括代码和数据都可以私有化处理,有强大的插件机制,可以自行开发功能 | 私有化需要自行部署和维护服务器、数据库等,部分分析功能需要二次开发 |
Matomo 是什么? 这里介绍一下 Matomo,作为一套基于 PHP 与 MySQL 的网页流量统计和分析平台,它的大部分功能已经开源,并且做了很好的封装,可以轻松地进行私有化部署,它的功能主要分成两块:
- 收集并存储页面访问数据,主要是用户信息,如设备型号、分辨率、用户地区、来源,以及页面信息,如页面访问路径、访问操作等。
- 对收集起来的数据进行指标量化并可视化的展示,例如用户设备型号分布、地区分布、某个页面的浏览人数、访问最多的页面、某个用户在某个页面的访问路径和具体操作等,并且在收集数据时,Matomo 会有大量的策略保护用户隐私,例如上报 IP 时隐藏最后一位字节等。
Matomo 落地到业务 在引入 Matomo 之前,先说明一下 Matomo 的主要组成追踪器和 Matomo 服务端,追踪器基于 JS 实现,需要在网页引入,用于上报数据。服务端主要提供了三个功能:
- HTTP 接口,追踪器可以收集所在网页的数据但不上报,通过 HTTP 接口发送给 Matomo。
- 归档任务运行并预处理数据,默认分为实时动态处理(页面访问数据,用户访问轨迹)和 cron 任务处理(用户维度的列表)。
- 可视化展现数据,也可以数据接口或者报表接口来访问这些数据。
- 部署私有化 Matomo 服务。
- 在需要流量统计ide页面上引入追踪器。
但在实际落地到内容平台的过程中,却遇到了问题——我们需要基于 Docker 进行部署。
由于业务的部署都基于 Docker 和 k8s 进行,因此私有化的 Matomo 也需要基于此进行部署,这样会带来几个问题:
- Matomo 的设置分成系统配置与功能设置,其中功能设置储存在 MySQL 中,而系统设置则储存在本地的配置文件中,当部署多个容器时,配置无法对齐,另外 Docker 重新部署后,这些配置修改也会丢失。
- 这套部署需要域名 + 路径的形式访问 Matomo,Matomo 社区镜像中是使用 Apache2 进行路由处理的,而 Apache2 默认的配置并不适配路径,需要修改 Apache 的配置文件。
Matomo 有官方发布的社区镜像可以直接使用,但为了解决上述的问题,需要在构建 Docker 镜像时进行额外的处理。
解决配置丢失的问题 Matomo 的配置文件是
config/config.ini.php
,不跟随版本管理,为了获取一份默认的配置文件,可以用社区镜像预先部署好一个 Matomo 容器,并在容器中获取一份默认的配置文件,例如:[database]
host = "${MATOMO_DATABASE_HOST}"
username = "${MATOMO_DATABASE_USERNAME}"
password = "${MATOMO_DATABASE_PASSWORD}"
dbname = "${MATOMO_DATABASE_DBNAME}"
tables_prefix = "${MATOMO_DATABASE_TABLES_PREFIX}"
charset = "utf8mb4"
multi_server_environment = 1
enable_installer = 0[General]
force_ssl = 0
assume_secure_protocol = 1
proxy_client_headers[] = "HTTP_X_FORWARDED_FOR"
proxy_client_headers[] = "HTTP_X_ORIGINAL_FORWARDED_FOR"
proxy_host_headers[] = "HTTP_X_FORWARDED_HOST"
salt = "xxxx" // 加密串,用于解密配置内容
trusted_hosts[] = "weread.qq.com"[Plugins]
Plugins[] = "CorePluginsAdmin"
// ...
// 需要启动的插件列表,由于篇幅有限,省略默认的启动插件[PluginsInstalled]
PluginsInstalled[] = "Diagnostics"
// ...
// 所有插件列表,,由于篇幅有限,省略默认的插件列表
复制出默认的配置文件后,即可根据业务进行修改,主要包括:
- 数据库的配置,建议使用环境变量进行配置。
- salt 是用于解密配置内容的加密串,保留默认配置中的值即可。
trusted_hosts[]
是部署 Matomo 的域名,支持多个域名配置,必须正确填写,否则无法使用。Plugins[]
和PluginsInstalled[]
分别是需要启用的插件和总插件列表,有需要调整插件的激活状态可以自行调整。
# 复制配置文件
COPY config.ini.php /var/www/html/config/config.ini.php
解决子目录部署的问题 Matomo 部署完成后,会以 weread.qq.com/weread-matomo 的形式去访问 Matomo 的服务,因此根据默认的 Apache2 配置,会尝试在 weread-matomo 这个目录中读取 Matomo,但实际上我们的 Matomo 是部署在根目录的,因此需要修改 Apache2 的配置文件,把针对
^/weread-matomo
的访问指向根目录。值得注意的是,出于安全考虑,我们不希望把 Matomo 的管理后台暴露到外网,因此在 Apache2 的配置中,可以通过正则指定只有追踪器相关的文件暴露到外网可以访问,方便业务引入。在了解了 Matomo 的源码后,追踪器相关的文件主要有
matomo.js
和 matomo.php
,其中 matomo.php
结尾会带有参数,因此最终的 AliasMatch 规则如下:
ServerAdmin webmaster@localhost
DocumentRoot /var/www/htmlErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combinedAliasMatch ^/weread-matomo/(matomo\.(js|php).*) /var/www/html/$1
Options All
AllowOverride All
order allow,deny
allow from all
社区镜像中 Apache2 的配置文件存放在
/etc/apache2/sites-available/000-default.conf
,把上面的配置内容在本地保存一份 000-default.conf
后,在构建 Docker 镜像时利用命令覆盖默认的 Apache2 配置:COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
无法显示城市信息 在解决了上面两个问题后,Matomo 的私有化部署基本已经跑通了,但后续我们发现,这样上报的数据中,并没有显示城市信息,Matomo 是基于 IP 信息来判定城市的,而 Matomo 自带的 IP 库仅能识别国家信息。
文章图片
如上图所示,城市都显示为“未知”。为了解决这个问题,需要引入 IP 地址库,Matomo 支持 DBIP 和 GeoIP 2 两个外部的地址库,地址库的格式都是一种特殊的地址库
.mmdb
格式。这里建议使用 GeoIP,在这里完成注册后,可以下载
.mmdb
格式的地址库。为了让 Matomo 识别出额外的地址库,需要把 .mmdb
放置到项目的 misc
目录,但由于 Matomo 使用了 Docker 部署,因此需要用 Docker 命令把 .mmdb
文件复制到容器的 misc
目录,最终完整的 Dockerfile 如下:# DockerfileFROM matomo:latestMAINTAINER kayoli# 复制 Apache2 配置文件
COPY 000-default.conf /etc/apache2/sites-available/000-default.conf# 复制 Matomo 配置文件
COPY config.ini.php /var/www/html/config/config.ini.php# 引入 IP 地址库,用于显示 IP 对应的城市
COPY mmdb/GeoLite2-ASN.mmdb /var/www/html/misc/GeoLite2-ASN.mmdb
COPY mmdb/GeoLite2-City.mmdb /var/www/html/misc/GeoLite2-City.mmdb
COPY mmdb/GeoLite2-Country.mmdb /var/www/html/misc/GeoLite2-Country.mmdb
前端引入追踪器也有坑
页面引入追踪器 经过上面的处理,已经解决了在 Docker 中部署服务端的问题,在 Matomo 的部署引导程序在完成后,会输出一段 JS 代码,用于给业务前端引入追踪器,例如:
Matomo 的追踪器包含了大量的选项和方法,主要包括:
- Tracker Object,用于记录某个行为,例如上面代码中的
trackPageView
则用于记录某个页面被访问,enableLinkTracking
则是用于开启链接跳转时自动记录的功能。 - Configuration of the Tracker Object,用于配置 Tracker Object,例如
setDocumentTitle
可以覆盖上报页面的标题,默认是获取document.title
。 - Ecommerce,电商相关的方法,提供了一系列记录商品信息的方法。
- Managing Consent,提供了一种机制来管理用户的跟踪上报。
自动记录 Vue SPA 的页面跳转 成功引入追踪器后发现,「内容管理平台」上报的用户行为,只有打开页面的操作,跳转页面并没有成功上报,但是默认的追踪器代码中,已经开启了 `` 选项。
【Matomo|Matomo 从了解到落地——页面流量统计与分析最佳实践】在 Matomo 的源码中,可以看到对 `` 的说明:
// @param bool enable Defaults to true.
//If "true", use pseudo click-handler (treat middle click and open contextmenu as
//left click). A right click (or any click that opens the context menu) on a link
//will be tracked as clicked even if "Open in new tab" is not selected.
//If "false" (default), nothing will be tracked on open context menu or middle click.
//The context menu is usually opened to open a link / download in a new tab
//therefore you can get more accurate results by treat it as a click but it can lead
//to wrong click numbers.
//
this.enableLinkTracking = function (enable) {
linkTrackingEnabled = true;
// ...
};
也就是说,这个选项仅对 link,也就是常见的
链接
这种形式的跳转才起作用,而「内容管理平台」是基于 Vue 开发的 Spa,页面跳转不是链接跳转,因此上报的记录里只有打开页面。要解决这个问题,可以在 Vue 进行跳转时主动调用 Matomo 的上报,但实际上已经有开源的插件实现了这个,例如vue-matomo,具体使用可以参考它的使用文档。
值得注意的是,vue-matomo 对 matomo 的初始参数进行封装,除了文档中列出来的选项,其他选项在初始化的时候是无效的,可以在 vue-matomo 初始化后,通过
_paq.push(['xxx'])
调用,_paq
对象的 push
方法已经被重写,调用 push
方法实际上相当于把某个方法放入调用队列并进行调用。Matomo 的最佳实践 经过上面的踩坑和填坑后,Matomo 最终得以在「内容管理平台」中落地投入使用,在经过一段时间的实践后,现有的自动记录还是不能满足我们的需求,例如我们需要自动上报 JS 错误信息,在点击 UI 元素时也需要上报,另外还需要在请求错误时进行自动上报。在经过一系列实践后,总结了一些最佳实践。
自动记录 JS 错误
在新版 Matomo 中,支持开启自动上报 JS 错误的功能,但功能尚未正式发布,因此官方文档中没有该功能的说明,需要调用的话可以通过
window._paq.push(['enableJSErrorTracking']);
开启该功能,为了保护 _paq
没有初始化好的情况,可以先判断 _paq
是否存在,例如:const enableJSErrorTracking = (): void => {
if (window._paq) {
window._paq.push(['enableJSErrorTracking']);
} else {
console.warn('can not found window._paq');
}
};
主动上报更多操作
除了链接跳转,页面中通常还会有一些 UI 操作不涉及链接变化,也不涉及请求,这类操作可以使用追踪器提供的 Tracker Object 进行主动上报,为了方便起见,可以抽取成工具方法,例如:
// 上报一个事件,例如点击事件,播放事件等,在主动上报中比较常用。
export const trackEvent = (category: string, action: string, name?: string, value?: number): void => {
if (window._paq) {
window._paq.push(['trackEvent', category, action, name, value]);
} else {
console.warn('can not found window._paq');
}
};
// 二次封装,专门上报弹窗的动作,例如 action 参数可以填写 show, close
export const trackDialogEvent = (action: string, name?: string, value?: number): void => {
if (window._paq) {
window._paq.push(['trackEvent', 'Dialog', action, name, value]);
} else {
console.warn('can not found window._paq');
}
};
// 上报错误
export const trackErrorEvent = (action: string, name?: string, value?: number): void => {
if (window._paq) {
window._paq.push(['trackEvent', 'Error', action, name, value]);
} else {
console.warn('can not found window._paq');
}
};
// 上报搜索动作
export const trackSiteSearch = (keyword: string, category?: string, resultsCount?: string): void => {
if (window._paq) {
window._paq.push(['trackSiteSearch', keyword, category, resultsCount]);
} else {
console.warn('can not found window._paq');
}
};
请求失败自动上报
业务中涉及请求的操作通常都比较关键,请求失败自动上报有利于记录下完整的用户动作路径,方便定位问题,在我们的业务中,我们的请求都是使用 Axios 发出的,因此可以利用 axios interceptors 劫持所有请求,在遇到指定错误时自动上报到 Matomo,例如:
const baseURL = 'xxx';
// axios instance
const service = axios.create({
baseURL,
timeout: 60000,
});
service.interceptors.response.use(
(response: AxiosResponse) => {
const errCode = response.data && (response.data.errCode || response.data.errcode);
if (errCode && errCode < 0) {
const URL = response.config && response.config.url;
const errMsg = response.data && (response.data.errMsg || response.data.errMsg);
trackErrorEvent(URL, errMsg, errCode);
}
return response;
},
(error) => {
return Promise.reject(error);
}
);
至此,已经可以很准确展示用户在访问页面时的完整操作路径了,开发者可以通过这些操作路径,结合业务日志,方便地去定位问题以及还原问题。
文章图片
效果展示
经过以上的处理,现在已经可以上报非常丰富的访问数据,以及用户路径了,例如:
访客分析 - 访问日志
文章图片
可以看到,界面上显示了完整的用户操作,通过时间轴的形式,配合不同的关键词和 icon 可以很好地呈现出实际的操作路径。
访客分析 - 设备
文章图片
除了设备,在 Matomo 中还有地区等用户维度,并且 Matomo 在不同的数据展示中,例如目标转化率等,都可以基于这些不同的维度进行展示,对于分析用户组成相当方便。
转化与收益分析 - 概览 Matomo 支持设定指定的目标,用于计算转化率,并进行多个维度的展示,包括转化流向,每个阶段的转化人数和转化率等,并且可以通过不同的维度,例如渠道类型、城市、设备类型分别展示各种维度下的转化数据,这也是 Matomo 一种重要的特性,数据的展示维度丰富。
文章图片
另外,Matomo 的插件机制也非常强大,可以插入自定义的数据,注入到各个界面或者基于 Matomo 自身收集的数据重新展示,基于篇幅所限,后续再对 Matomo 的插件机制进行实践说明。
Matomo 的性能分析与局限性 从上面的说明中可以看出,Matomo 的分析功能强大,分析的维度也很丰富,但同时也带来了较大的服务端资源消耗。
Matomo 的架构可以支撑千万级甚至亿级的月 PV,但同时对于服务端的 CPU,RAM 和硬盘空间都有相应的要求。因此在实际使用时,需要注意当前服务端的配置是否足以支撑上报页面的 PV 量,否则会导致 Matomo 无法及时处理数据甚至崩溃。
量化性能分析
- Matomo 的默认配置是 1GB 内存,在默认配置下,Matomo 可以轻松支撑 1000 PV/天的访问量,对于这种级别的访问量,通常是一些内部平台或者面向特定人群的辅助页面。
- 对于 3000 PV/天访问量的业务,则建议使用2核 CPU,2GB RAM,50GB 硬盘的配置。
- 对于 30000 PV/天访问量的业务,则建议使用4核 CPU,8GB RAM,250GB 硬盘的配置,这个量级的访问可以是面向大众用户的业务页面了。
- 对于 300000 PV/天访问量的业务,建议把 PHP 服务端和 MySQL 分开部署,对于这种量级的业务,MySQL 的瓶颈会更加明显,把 MySQL 部署进行单独部署,会更加稳定,建议最低的配置是8核 CPU,16GB RAM, 100GB 硬盘的机器作为 PHP 服务端,8核 CPU, 16GB RAM, 400GB 硬盘作为 MySQL 服务,。
- 对于更高访问量的业务,可以再叠加机器配置,硬盘空间主页是给 MySQL 消耗用的,一个参考数据是:大概每增加500万PV,数据库就会增加1GB的数据。
优化 Matomo 的性能的最佳实践
- Matomo 对于 PHP 的最低版本要求是 PHP5,但尽量使用 PHP7,PHP7 在性能上有大量的优化。
- 使用 PHP cache,PHP5 及以上版本默认开启了。
- 通过调整 Innodb 配置来优化 MySQL 的性能,例如增加
innodb_buffer_pool_size
来适应内存大小,另外可以把innodb_buffer_pool_size
设置为 MySQL 可用内存的80%。增大innodb_flush_log_at_trx_commit
来增加追踪器的吞吐量,具体可以参考这里。 - 业务访问量比较大的时候(例如 300000 PV/天的访问量),可以关闭实时动态处理的功能(管理 - 系统 - 通用设置 - 归档设置 - 在浏览器中查看报告时进行归档 - 否),关闭实时动态处理后,页面访问数据和用户访问轨迹也需要等待 cron 任务进行数据处理后才能展示,归档时间建议是设置为 3600 秒,减轻服务端的负担。
- 对于 URL 带 Query 的情况,如果无需要区分 Query 进行数据分析的情况,可以选择忽略这些 Query(管理 - 网站 - 管理 - 编辑网站 - 排除参数),否则同一个 URL 带有不同 Query,Matomo 会当作不同的 URL 来处理,大大增加 MySQL 的负担。
- 定期删除旧数据(管理 - 隐私设置 - 匿名化数据 - 定期删除旧的原始数据),旧数据的删除可以减小数据库的大小,既节省硬盘空间,也加快了数据的处理。
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 一个人的碎碎念
- 我从来不做坏事
- 从蓦然回首到花开在眼前,都是为了更好的明天。
- 西湖游
- 改变自己,先从自我反思开始
- leetcode|leetcode 92. 反转链表 II
- 从我的第一张健身卡谈传统健身房
- 自媒体形势分析
- 操作系统|[译]从内部了解现代浏览器(1)