使用 MongoDB 存储日志数据

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
日志服务 SLS,月写入数据量 50GB 1个月
简介: 线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误、警告及用户行为等信息。通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题。但当产生大量的日志之后,要想从大量日志里挖掘出有价值的内容,则需要对数据进行进一步的存储和分析。本文以存储 web 服务的访问日志为例,介绍如何使用 MongoDB 来存储、分析日志数据,让日志数据发挥最大的价值。本文的内容同样适用于其他的日志存储型应用。

44.jpeg
镜像下载、域名解析、时间同步请点击 阿里巴巴开源镜像站

一、模式设计

一个典型的web服务器的访问日志类似如下,包含访问来源、用户、访问的资源地址、访问结果、用户使用的系统及浏览器类型等。

1. 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "[http://www.example.com/start.html](http://www.example.com/start.html)" "Mozilla/4.08 [en](Win98; I ;Nav)"

最简单存储这些日志的方法是,将每行日志存储在一个单独的文档里,每行日志在MongoDB里的存储模式如下所示:

1. {
2.     _id: ObjectId('4f442120eb03305789000000'),
3.     line: '127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "**GET** /apache_pb.gif HTTP/1.0" 200 2326 "[http://www.example.com/start.html](http://www.example.com/start.html)" "Mozilla/4.08 [en](Win98; I ;Nav)"'
4. }

上述模式虽然能解决日志存储的问题,但这些数据分析起来比较麻烦,因为文本分析并不是MongoDB所擅长的,更好的办法是把一行日志存储到MongoDB的文档里前,先提取出各个字段的值。如下所示,上述的日志被转换为一个包含很多个字段的文档。

1. {
2.      _id: ObjectId('4f442120eb03305789000000'),
3.      host: "127.0.0.1",
4.      logname: **null**,
5.      **user**: 'frank',
6.      time: ISODate("2000-10-10T20:55:36Z"),
7.      path: "/apache_pb.gif",
8.      request: "**GET** /apache_pb.gif HTTP/1.0",
9.      status: 200,
10.      response_size: 2326,
11.      referrer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
12.      user_agent: "Mozilla/4.08 [en](Win98; I ;Nav)"
13. }

同时,在这个过程中,如果您觉得有些字段对数据分析没有任何帮助,则可以直接过滤掉,以减少存储上的消耗。比如数据分析不会关心user信息、request、status信息,这几个字段没必要存储。ObjectId里本身包含了时间信息,没必要再单独存储一个time字段 (当然带上time也有好处,time更能代表请求产生的时间,而且查询语句写起来更方便,尽量选择存储空间占用小的数据类型)。基于上述考虑,上述日志最终存储的内容可能类似如下所示:

1. {
2.     _id: ObjectId('4f442120eb03305789000000'),
3.     host: "127.0.0.1",
4.     time: ISODate("2000-10-10T20:55:36Z"),
5.     path: "/apache_pb.gif",
6.     referer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
7.     user_agent: "Mozilla/4.08 [en](Win98; I ;Nav)"
8. }

二、写日志

日志存储服务需要能同时支持大量的日志写入,用户可以定制writeConcern来控制日志写入能力,比如如下定制方式:

1. db.events.insert({
2.         host: "127.0.0.1",
3.         time: ISODate("2000-10-10T20:55:36Z"),
4.         path: "/apache_pb.gif",
5.         referer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
6.         user_agent: "Mozilla/4.08 [en](Win98; I ;Nav)"
7.     }
8. )

说明:

  • 如果要想达到最高的写入吞吐,可以指定writeConcern为 {w: 0}。
  • 如果日志的重要性比较高(比如需要用日志来作为计费凭证),则可以使用更安全的writeConcern级别,比如 {w: 1} 或 {w: “majority”}。

同时,为了达到最优的写入效率,用户还可以考虑批量的写入方式,一次网络请求写入多条日志。格式如下所示:
db.events.insert([doc1, doc2, ...])

三、查询日志

当日志按上述方式存储到MongoDB后,就可以按照各种查询需求查询日志了。

1. 查询所有访问/apache_pb.gif 的请求

q_events = db.events.find({'path': '/apache_pb.gif'})
如果这种查询非常频繁,可以针对path字段建立索引,提高查询效率:
db.events.createIndex({path: 1})

2. 查询某一天的所有请求

1. q_events = db.events.find({'time': { '$gte': ISODate("2016-12-19T00:00:00.00Z"),'$lt': ISODate("2016-12-20T00:00:00.00Z")}})

通过对time字段建立索引,可加速这类查询:
db.events.createIndex({time: 1})

3. 查询某台主机一段时间内的所有请求

1. q_events = db.events.find({
2.     'host': '127.0.0.1',
3.     'time': {'$gte': ISODate("2016-12-19T00:00:00.00Z"),'$lt': ISODate("2016-12-20T00:00:00.00Z" }
4. })

同样,用户还可以使用MongoDB的aggregation、mapreduce框架来做一些更复杂的查询分析,在使用时应该尽量建立合理的索引以提升查询效率。

四、无数据分片

当写日志的服务节点越来越多时,日志存储的服务需要保证可扩展的日志写入能力以及海量的日志存储能力,这时就需要使用MongoDB sharding来扩展,将日志数据分散存储到多个shard,关键的问题就是shard key的选择。

1. 按时间戳字段分片

使用时间戳来进行分片(如ObjectId类型的_id,或者time字段),这种分片方式存在如下问题:

  • 因为时间戳一直顺序增长的特性,新的写入都会分到同一个shard,并不能扩展日志写入能力。
  • 很多日志查询是针对最新的数据,而最新的数据通常只分散在部分shard上,这样导致查询也只会落到部分shard。

2. 按随机字段分片

按照_id字段来进行hash分片,能将数据以及写入都均匀都分散到各个shard,写入能力会随shard数量线性增长。但该方案的问题是,数据分散毫无规律。所有的范围查询(数据分析经常需要用到)都需要在所有的shard上进行查找然后合并查询结果,影响查询效率。

3. 按均匀分布的key分片

假设上述场景里 path 字段的分布是比较均匀的,而且很多查询都是按path维度去划分的,那么可以考虑按照path字段对日志数据进行分片,好处是:

  • 写请求会被均分到各个shard。
  • 针对path的查询请求会集中落到某个(或多个)shard,查询效率高。

不足的地方是:

  • 如果某个path访问特别多,会导致单个chunk特别大,只能存储到单个shard,容易出现访问热点。
  • 如果path的取值很少,也会导致数据不能很好的分布到各个shard。

当然上述不足的地方也有办法改进,方法是给分片key里引入一个额外的因子,例如原来的shard key是 {path: 1},引入额外的因子后变成:
{path: 1, ssk: 1}
其中ssk可以是一个随机值,例如:_id的hash值、时间戳,这样相同的path还是根据时间排序的。
这样做的效果是分片key的取值分布丰富,并且不会出现单个值特别多的情况。上述几种分片方式各有优劣,用户可以根据实际需求来选择方案。

五、应对数据增长

分片的方案能提供海量的数据存储支持,但随着数据越来越多,存储的成本会不断的上升。通常很多日志数据有个特性,日志数据的价值随时间递减。比如1年前、甚至3个月前的历史数据完全没有分析价值,这部分可以不用存储,以降低存储成本,而在MongoDB里有很多方法支持这一需求。

1. TTL 索引

MongoDB的TTL索引可以支持文档在一定时间之后自动过期删除。例如上述日志time字段代表了请求产生的时间,针对该字段建立一个TTL索引,则文档会在30小时后自动被删除。
db.events.createIndex( { time: 1 }, { expireAfterSeconds: 108000 } )

注意:TTL索引是目前后台用来定期(默认60s一次)删除单线程已过期文档的。如果日志文档被写入很多,会积累大量待过期的文档,那么会导致文档过期一直跟不上而一直占用着存储空间。

2. 使用Capped集合

如果对日志保存的时间没有特别严格的要求,只是在总的存储空间上有限制,则可以考虑使用capped collection来存储日志数据。指定一个最大的存储空间或文档数量,当达到阈值时,MongoDB会自动删除capped collection里最老的文档。
db.createCollection("event", {capped: true, size: 104857600000}

3. 定期按集合或DB归档

比如每到月底就将events集合进行重命名,名字里带上当前的月份,然后创建新的events集合用于写入。比如2016年的日志最终会被存储在如下12个集合里:

1.  events-201601
2.  events-201602
3.  events-201603
4.  events-201604
5.  ....
6.  events-201612

当需要清理历史数据时,直接将对应的集合删除掉:

1.  db["events-201601"].drop()
2.  db["events-201602"].drop()

不足:如果要查询多个月份的数据,查询的语句会稍微复杂些,需要从多个集合里查询结果来合并。

阿里巴巴开源镜像站 提供全面,高效和稳定的系统镜像、应用软件下载、域名解析和时间同步服务。”

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
1月前
|
存储 NoSQL MongoDB
数据的存储--MongoDB文档存储(二)
数据的存储--MongoDB文档存储(二)
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
123 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
16天前
|
存储 NoSQL 关系型数据库
【赵渝强老师】MongoDB的存储结构
MongoDB 是一个可移植的 NoSQL 数据库,支持跨平台运行。其逻辑存储结构包括数据库、集合和文档,而物理存储结构则由命名空间文件、数据文件和日志文件组成。视频讲解和示意图进一步解释了这些概念。
|
15天前
|
SQL Oracle 关系型数据库
【赵渝强老师】Oracle的联机重做日志文件与数据写入过程
在Oracle数据库中,联机重做日志文件记录了数据库的变化,用于实例恢复。每个数据库有多组联机重做日志,每组建议至少有两个成员。通过SQL语句可查看日志文件信息。视频讲解和示意图进一步解释了这一过程。
|
1月前
|
NoSQL MongoDB 数据库
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
|
1月前
|
存储 消息中间件 大数据
大数据-69 Kafka 高级特性 物理存储 实机查看分析 日志存储一篇详解
大数据-69 Kafka 高级特性 物理存储 实机查看分析 日志存储一篇详解
39 4
|
1月前
|
存储 消息中间件 大数据
大数据-70 Kafka 高级特性 物理存储 日志存储 日志清理: 日志删除与日志压缩
大数据-70 Kafka 高级特性 物理存储 日志存储 日志清理: 日志删除与日志压缩
41 1
|
1月前
|
存储 消息中间件 大数据
大数据-68 Kafka 高级特性 物理存储 日志存储概述
大数据-68 Kafka 高级特性 物理存储 日志存储概述
28 1
|
1月前
|
数据采集 机器学习/深度学习 存储
使用 Python 清洗日志数据
使用 Python 清洗日志数据
37 2
|
1月前
|
存储 NoSQL 关系型数据库
数据的存储--MongoDB文档存储(一)
数据的存储--MongoDB文档存储(一)
下一篇
无影云桌面