一尘不染

从PHP应用程序记录页面请求数据的可扩展方式?

redis

我正在开发的Web应用程序(使用PHP)需要能够记录每个页面请求。

就像普通的access_log一样,它将存储详细信息,例如请求的url,源IP地址,日期/时间,但我还需要它来存储登录用户的用户ID(存储在php会话变量中)。

然后,这些数据将被查询,以便日后根据需要创建站点范围或每个用户的分析报告-
诸如访问/唯一访问的总数,特定时间段内的页面浏览量,对ip地址进行地理位置定位以及查找在地点,一天中最活跃的时间,最活跃的成员等。

显而易见的事情是在每个页面上都有一个mysql
insert语句,但是如果应用程序接收到数千个请求/秒,这将成为数据库的巨大瓶颈,因此我正在研究替代的,可扩展的方法这对基础设施没有很大的要求。

我有一些想法是:

1)研究一种方法,使Nginx能够从会话/应用程序中的普通Web服务器access_log中记录user_id,可以将其解析并定期(每晚)加载到数据库中。感觉有点像破解,随着系统的扩展,需要在每个Web服务器上进行操作。

2)将每个页面请求登录到具有高速写入速度的Redis中-这是因为缺少在以后的日期中查询日期的能力。

3)将每个页面请求登录到充当缓存的Memcache / Redis(或消息队列)中,然后从那里定期提取,将其插​​入MySQL并删除。

4)像MongoDB这样具有更多查询功能的东西是否合适?

我对您如何处理此问题以及是否有任何类似应用程序经验(或在线遇到过任何问题)感兴趣。

我也对如何适当地构造数据以将其存储在memcache / redis中的想法感兴趣。

谢谢


阅读 256

收藏
2020-06-20

共1个答案

一尘不染

当然可以通过多种方法来实现。我将介绍列出的每个选项以及一些其他评论。

1)如果NGinx可以做到,那就放手吧。我用Apache以及JBOSS和Tomcat来做。然后,我使用syslog-
ng集中收集它们并从那里进行处理。对于此路由,我建议使用带分隔符的日志消息格式,例如以制表符分隔的格式,因为它使解析和阅读更加容易。我不知道它记录PHP变量,但是它肯定可以记录标题和cookie信息。如果您将要全部使用NGinx日志记录,我将尽可能建议您使用此路由-
为什么要记录两次?

2)不存在“缺乏在以后的日期查询日期的能力”,下面更多内容。

3)这是一个选项,但是是否有用取决于您要保留数据多长时间以及要写入多少清除数据。下面更多。

4)MongoDB当然可以工作。您将不得不编写查询,它们不是简单的SQL命令。

现在,将数据存储在redis中。我目前已按照说明使用syslog-
ng记录事物,并使用程序目标来解析数据并将其填充到Redis中。就我而言,我有几个分组标准,例如按vhost和按群集,因此我的结构可能有所不同。您首先要解决的问题是“我要从这些数据中获取什么数据”?其中一些将是计数器,例如流量费率。其中一些将是聚合的,还有更多将是诸如“按受欢迎程度订购我的页面”之类的事情。

我将展示一些技巧,以轻松地将其转换为Redis(从而退出)。

首先,让我们考虑一段时间内的流量统计信息。首先确定粒度。您要按分钟统计还是按小时统计就足够了?这是一种跟踪给定URL流量的方法:

使用键“ traffic-by-url:URL:YYYY-MM-
DD”将数据存储在已排序的集合中,在此已排序的集合中,您将使用zincrby命令并提供成员“
HH:MM”。例如在Python中,“ r”是您的redis连接:

r.zincrby("traffic-by-url:/foo.html:2011-05-18", "01:04",1)

本示例在5月18日凌晨1:04增加URL“ /foo.html”的计数器。

要检索特定日期的数据,您可以在按键上调用zrange(“ traffic-by-
url:URL:YYYY-MM-
DD”),以从最受欢迎到最受欢迎的顺序进行排序。要获得前10名,例如,您将使用zrevrange并为其指定范围。Zrevrange返回一个反向排序,命中率最高的将在顶部。还有更多排序的set命令可供使用,这些命令可让您执行漂亮的查询,例如分页,获取最低分数等的结果范围。

您可以简单地更改或扩展密钥名称以处理不同的时间窗口。通过将其与zunionstore结合使用,您可以自动累计到更短的时间段。例如,您可以在一周或一个月内对所有键进行联合,然后存储在新的键中,例如“
traffic-by-url:monthly:URL:YYYY-
MM”。通过在给定的一天中对所有URL进行上述操作,您可以每天获取。当然,您也可以设置每日总点击量并将其增加。这主要取决于您何时输入数据-
通过日志文件导入脱机或作为用户体验的一部分。

我建议不要在实际的用户会话中做太多事情,因为它会延长用户体验它(以及服务器负载)所花费的时间。最终,这将是基于流量水平和资源的呼叫。

您可以想象上述存储方案可以应用于您想要或确定的任何基于计数器的统计信息。例如,将URL更改为userID,就可以进行按用户跟踪。

您还可以将原始日志存储在Redis中。我这样做是为了将一些日志存储为JSON字符串(我将它们作为键值对)。然后,我进行第二步处理,将它们拉出并处理数据。

为了存储原始命中,您还可以使用以大纪元时间为等级的排序集,并使用zrange /
zrevrange命令轻松获取时间窗口。或将它们存储在基于用户ID的密钥中。集和排序集都适用于此。

我没有讨论过的另一个选择,但是对于您的某些数据可能有用的是将其存储为散列。例如,这对于存储有关给定会话的详细信息可能很有用。

如果您确实想要数据库中的数据,请尝试使用Redis的发布/订阅功能,并让订阅者将其解析为定界格式并转储到文件中。然后进行一个导入过程,该过程使用copy命令(或DB的等效命令)进行批量导入。您的数据库将感谢您。

最后的建议(我可能已经花了很多时间)是明智和自由地使用expire命令。使用Redis
2.2或更高版本,您可以在偶数计数器键上设置到期时间。这里的最大优势是自动数据清除。想象一下,您遵循的是我上面概述的方案。通过使用到期命令,您可以自动清除旧数据。也许您希望长达3个月的每小时统计信息,然后是每日统计信息;连续6个月的每日统计,然后仅每月统计。只需在三个月后(86400 * 90),您每天的时间为6(86400 * 180),使小时密钥过期,就无需进行清理了。

对于地理标记,我对IP进行离线处理。想象一下一个具有以下关键结构的排序集:使用IP作为元素,并使用上面提到的zincrybyby命令,获取“每个IP流量数据”的“
traffic-by-ip:YYYY-MM-
DD”。现在,在您的报告中,您可以获取排序后的集合并进行IP查找。为了节省报告时的流量,您可以在Redis中设置一个哈希,将IP映射到所需的位置。例如,以“
geo:country”作为键,以IP作为哈希成员,以国家/地区代码作为存储值。

我要补充的一个大警告是,如果您的流量水平很高,则可能要运行两个Redis实例(或更多取决于流量)。第一个是写实例,它不会启用bgsave选项。如果您的流量很高,那么您将始终进行bgsave。这是我推荐的第二个实例。它是第一个的从属,并且将保存到磁盘。您还可以对从属服务器运行查询以分配负载。

我希望能给您一些想法和尝试的东西。尝试各种选项,以了解最适合您的特定需求的选项。我在Redis的高流量网站上跟踪了很多统计信息(以及MTA日志统计信息),并且效果非常好-
与Django和Google的Visualization API结合使用,我得到了非常漂亮的图形。

2020-06-20