Elasticsearch统计去重IP文档数:每日唯一请求计数方案
问题描述
我们计划将早年搭建的一款实现不佳的PHP每日唯一IP日志记录器迁移至Elasticsearch,拟将每个请求作为单独文档存储,文档结构示例如下:
{ "_index": "logger", "_type": "_doc", "_id": "-1q04XEBfzHON7FKVuMY", // Auto-generated "_source": { "ip": "211.543.232.533", "user": "", "request": "GET /index.php HTTP/1.1", "status": 200, "bytes": 10984, "refer": "https://www.google.com/search?q=some%20website", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "domain": "example.org", "timestamp": 1662865208000 } }
现在需要统计每日0点之后的唯一IP请求数(比如6个文档对应3个唯一IP时,计数为3)。我们已经写出基础查询,但不知道如何添加去重逻辑,请问该需求可通过查询实现,还是需要配置映射或分析器?目前每日约有50万请求,其中约3万为唯一IP。
基础查询示例:
POST /logger/_doc/_count { "query": { "bool": { "must": [ { "range": { "timestamp": { "gt": 1662854400000 // Epoch ms time at 24:00 } } } ] // And then something here? I'm not really sure what to do } } }
解决方案
核心实现:使用Cardinality聚合
这个需求完全可以通过聚合查询实现,不需要额外配置映射或分析器(只要ip字段的映射是keyword类型,默认自动映射的字符串字段会同时生成text和keyword子字段,直接用ip.keyword即可)。
具体来说,用cardinality聚合统计唯一IP数量,配合时间范围查询,完整请求如下:
POST /logger/_search { "size": 0, // 无需返回具体文档,仅获取聚合结果 "query": { "bool": { "must": [ { "range": { "timestamp": { "gt": 1662854400000 // 每日0点的毫秒级时间戳 } } } ] } }, "aggs": { "unique_ip_count": { "cardinality": { "field": "ip.keyword" // 使用keyword类型确保IP作为完整字符串去重 } } } }
关键注意点
- 字段类型要求:必须用
keyword类型的ip字段(或其子字段)。如果用text类型,IP会被分词拆分(比如211.543.232.533会拆成多个数字token),导致去重结果错误。若当前是自动映射,默认会有ip.keyword可用;若没有,需修改映射将ip设为keyword类型。 - 性能优化:针对每日50万请求、3万唯一IP的规模,
cardinality聚合性能完全够用。如果需要更高精度,可调整precision_threshold参数(默认值3000,你的唯一IP数接近这个值,设为5000能提升精度且性能损耗极小):"cardinality": { "field": "ip.keyword", "precision_threshold": 5000 }
为什么不用_count接口
_count接口仅能返回匹配查询的文档总数,无法实现去重统计,必须用_search配合聚合完成唯一值计数需求。
内容的提问来源于stack exchange,提问作者Typewar
相关产品推荐
相关产品推荐

