我正在开发的Web应用程序(在PHP中)需要能够记录每个页面请求.
就像普通的access_log一样,它会存储详细信息,如请求的URL,源IP地址,日期/时间,但我还需要它来存储登录用户的用户ID(存储在PHP会话变量中).
然后将查询此数据,以便在以后根据需要创建站点范围或每个用户的分析报告 – 例如访问总次数/唯一访问次数,特定时间段内的页面浏览量,地理定位IP地址和查找在一些地方,一天中最活跃的时间,最活跃的成员等.
显而易见的事情是在每个页面上都有一个MysqL insert语句,但是如果应用程序正在接收数千个req / sec,那么这将成为数据库的一个重要瓶颈,所以我正在寻找其他可扩展的方法没有大的基础设施要求.
我所拥有的一些想法是:
1)为Nginx工作,以便能够从普通Web服务器access_log中的会话/应用程序记录user_id,可以定期(每晚)解析并加载到数据库中.这感觉有点像黑客攻击,并且随着系统扩展,需要在每个Web服务器上执行操作.
2)将每个页面请求记录到具有高写入速度的Redis中 – 问题在于缺乏在日后查询日期的能力.
3)将每个页面请求记录到作为缓存(或消息队列)的Memcache / Redis中,并从那里将其定期提取,插入MysqL并删除.
我对你如何处理这个以及是否有任何类似应用程序的经验感兴趣(或者在网上遇到过任何问题).
我也对如何将数据适当地结构化以存储在memcache / redis中的想法感兴趣.
谢谢
1)如果Nginx可以做到,那就让它.我使用Apache以及JBOSS和Tomcat.然后我使用syslog-ng集中收集它们并从那里进行处理.对于这个路由,我建议使用分隔符分隔日志消息格式,因为它可以更容易解析和读取.我不知道它记录PHP变量,但它肯定可以记录标题和cookie信息.如果您打算使用Nginx日志记录,我会尽可能推荐这条路线 – 为什么要记录两次?
2)没有“没有能力在以后查询日期”,更多信息如下.
3)这是一个选项,但它是否有用取决于您希望保留数据的时间长度以及要写入的清理量.更多下面.
4)MongoDB当然可以工作.您必须编写查询,它们不是简单的sql命令.
现在,将数据存储在redis中.我目前使用syslog-ng记录事项,并使用程序目标解析数据并将其填充到Redis中.在我的情况下,我有几个分组标准,如vhost和群集,所以我的结构可能有点不同.
您需要首先解决的问题是“我想从这些数据中获取哪些数据”?其中一些将是流量等计数器.其中一些将是聚合,更多的将是“按受欢迎程度订购我的页面”.
我将演示一些技巧,以便轻松将其转换为redis(从而退出).
首先,让我们考虑一下随时间变化的流量统计数据.首先确定粒度.您想要每分钟统计数据还是每小时统计数据就足够了?以下是跟踪给定网址流量的一种方法:
使用密钥“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分增加了网址“/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的发布/订阅功能,并让订阅者将其解析为分隔格式并转储到文件中.然后有一个导入过程使用复制命令(或数据库的等效命令)批量导入.你的DB会谢谢你.
这里的最后一点建议(我可能已经花了足够的心理时间)是明智和自由地使用expire命令.使用Redis 2.2或更新版本,您可以设置偶数计数器键的到期时间.这里的一大优势是自动数据清理.想象一下,你遵循我上面概述的方案.通过使用到期命令,您可以自动清除旧数据.也许你想要最多3个月的每小时统计数据,然后只需要每日统计数据;每日统计数据为6个月,然后仅为每月统计数据.只需在三个月(86400 * 90)之后使您的每小时密钥到期,您的每日时间为6(86400 * 180),您将无需进行清理.
对于地理标记,我执行IP的离线处理.想象一下具有这种密钥结构的有序集:“使用IP作为元素的ip-traffic-by-ip:YYYY-MM-DD”,并使用上面提到的zincryby命令获得每IP流量数据.现在,在您的报告中,您可以获取已排序的集并执行IP查找.要在执行报告时节省流量,您可以在redis中设置哈希,将IP映射到您想要的位置.例如,“geo:country”作为键,IP作为哈希成员,国家代码作为存储值.
我要补充的一个重要警告是,如果您的流量水平非常高,您可能需要运行两个Redis实例(或更多,具体取决于流量).第一个是写实例,它不会启用bgsave选项.如果你的流量非常高,你将永远做一个bgsave.这是我推荐的第二个实例.它是第一个的从属,它将保存到磁盘.您还可以针对从站运行查询以分配负载.
我希望能给你一些想法和尝试的东西.玩弄不同的选项,看看什么最适合您的特定需求.我正在redis上跟踪高流量网站上的大量统计数据(以及MTA日志统计数据)并且表现非常出色 – 结合Django和Google的可视化API,我得到了非常好看的图表.