【实战ES】实战 Elasticsearch:快速上手与深度实践-5.4.2用户画像聚合(Terms Aggregation + Cardinality)
👉 点击关注不迷路
👉 点击关注不迷路
👉 点击关注不迷路
文章大纲
- 5.4.2 用户画像聚合深度实践:Terms Aggregation与Cardinality高阶应用
- 1. 核心聚合原理
- 1.1 聚合类型对比矩阵
- 1.2 组合聚合公式
- 2. 全链路配置实战
- 2.1 索引与映射设计
- 2.2 多级聚合查询模板
- 3. 性能调优策略
- 3.1 参数调优矩阵
- 3.2 性能测试数据
- 4. 企业级应用案例
- 4.1 电商账号风险识别
- 4.2 社交网络异常检测
- 5. 深度优化技巧
- 5.1 存储层优化
- 5.2 查询层优化
- 6. 问题排查与监控
- 6.1 常见问题矩阵
- 6.2 `关键监控指标`
5.4.2 用户画像聚合深度实践:Terms Aggregation与Cardinality高阶应用
用户画像聚合分析系统架构与数据流向
- 典型应用场景
- 精准营销: 根据 city 和 interest_tags 定向推送广告。
- 用户分层: 通过 age_group 和 consumption_level 划分高价值用户。
- 产品优化: 分析 device_type 和 user_behavior 优化交互设计。
Cardinality(基数)
- 在数据处理和分析领域,Cardinality(基数)是一个重要的概念,尤其在数据库、数据分析工具以及 Elasticsearch 等技术中经常被提及和使用。
Cardinality 指的是数据集中唯一值的数量。
例如,在一个包含用户 ID 的数据列中,Cardinality 就是该列中不同用户 ID 的个数。在数据库中,Cardinality 常用于评估数据的分布情况和查询性能。
高 Cardinality 意味着数据集中有很多不同的唯一值,而低 Cardinality 则表示数据集中的唯一值较少,存在较多的重复值。
1. 核心聚合原理
1.1 聚合类型对比矩阵
聚合类型 | 计算方式 | 空间复杂度 | 精确度 | 适用场景 |
---|---|---|---|---|
Terms Aggregation | 分桶统计Top N项 | O(n) | 精确 | 高频值分布分析 |
Cardinality | HyperLogLog++近似算法 | O(log n) | 99.9% | 海量数据去重统计 |
Composite | 多维度组合分桶 | O(n²) | 精确 | 多维交叉分析 |
Percentiles | TDigest算法 | O(n) | 近似 | 数据分布分析 |
-
HyperLogLog++近似算法
- HyperLogLog++ 是 Elasticsearch 中用于近似计算 Cardinality(唯一值数量)的核心算法,其设计目标是在内存使用和计算精度之间取得平衡。
- 其核心思想是通过概率统计来估算唯一值数量,而非精确计算。
- 典型应用场景
- 用户行为分析: 统计日活用户(DAU)、独立访客(UV)。
- 日志分析: 统计异常请求的唯一 IP 数量。
- 电商场景: 分析商品浏览的唯一用户数或设备类型分布。
-
TDigest算法
- TDigest 是 Elasticsearch 中用于高效计算近似百分位数的核心算法,其设计目标是在内存占用和计算精度之间取得平衡。
- 适用场景
- 百分位数统计:如响应时间 P99、用户访问延迟 P95 等。
- 数据分布分析:监控数值型数据的分布特征(如交易金额、请求耗时)。
- 实时分析:需快速返回结果的场景(如监控仪表盘)。
1.2 组合聚合公式
2. 全链路配置实战
2.1 索引与映射设计
// 向 Elasticsearch 发送 PUT 请求,创建一个名为 user_behavior 的索引
PUT /user_behavior
{
"settings": {
"index": {
// 设置索引的主分片数量为 9。分片是 Elasticsearch 中数据的分布式存储单元,
// 合理的分片数量有助于提高索引的读写性能和扩展性。这里设置为 9 可根据集群规模和数据量进行调整
"number_of_shards": 9,
// 设置每个主分片的副本分片数量为 1。副本分片用于提高数据的可用性和容错性,
// 当主分片出现故障时,副本分片可以接替工作。同时,副本分片也可以分担查询请求,提高查询性能
"number_of_replicas": 1,
// 设置索引的刷新间隔为 30 秒。刷新操作会将内存中的数据写入磁盘,使数据可以被搜索到。
// 较长的刷新间隔可以减少磁盘 I/O 操作,提高写入性能,但会增加数据的可见延迟
"refresh_interval": "30s"
}
},
"mappings": {
// 设置动态映射策略为 strict,表示只允许显式定义的字段被索引。
// 如果遇到未定义的字段,会抛出异常,避免意外索引不必要的字段
"dynamic": "strict",
"properties": {
// 定义 @timestamp 字段,类型为 date,用于存储时间戳信息。
// 该字段常用于时间序列分析和基于时间的查询
"@timestamp": { "type": "date" },
"user_id": {
// 定义 user_id 字段,类型为 keyword,用于存储用户的唯一标识符。
// keyword 类型适用于精确匹配和聚合操作
"type": "keyword",
// 启用 doc_values,这是一种磁盘数据结构,用于在内存中高效访问字段值。
// 对于需要进行排序、聚合和脚本操作的字段,启用 doc_values 可以提高性能
"doc_values": true
},
"behavior_type": {
// 定义 behavior_type 字段,类型为 keyword,用于存储用户的行为类型。
// keyword 类型适合存储离散的、可枚举的值
"type": "keyword",
// 启用 eager_global_ordinals,这会在索引刷新时预先计算全局序号。
// 对于频繁用于聚合操作的 keyword 字段,启用该选项可以提高聚合性能
"eager_global_ordinals": true
},
"device_fingerprint": {
// 定义 device_fingerprint 字段,类型为 keyword,用于存储设备的指纹信息。
// keyword 类型确保可以精确匹配设备指纹
"type": "keyword",
// 设置 ignore_above 为 256,表示如果该字段的值长度超过 256 个字符,将被忽略不进行索引。
// 这可以避免存储过长的、可能无意义的数据,节省存储空间
"ignore_above": 256
},
"geoip": {
// 定义 geoip 字段,类型为 object,用于存储地理信息相关的数据。
// object 类型可以包含多个子字段
"type": "object",
"properties": {
// 定义 geoip 对象下的 city 字段,类型为 keyword,用于存储用户所在的城市名称。
// keyword 类型适合进行精确匹配和聚合操作
"city": { "type": "keyword" },
// 定义 geoip 对象下的 location 字段,类型为 geo_point,用于存储用户的地理位置坐标(经纬度)。
// geo_point 类型支持地理空间查询,如距离计算、区域过滤等
"location": { "type": "geo_point" }
}
}
}
}
}
2.2 多级聚合查询模板
// 向 Elasticsearch 的 user_behavior 索引发送 GET 请求进行搜索
GET /user_behavior/_search
{
// 设置返回的文档数量为 0,因为我们主要关注聚合结果,不需要具体的文档内容
"size": 0,
"query": {
// 使用范围查询来筛选符合时间条件的文档
"range": {
"@timestamp": {
// gte 表示大于等于,now-7d/d 表示当前时间往前推 7 天并取当天的开始时间
"gte": "now-7d/d",
// lte 表示小于等于,now/d 表示当前时间所在天的开始时间
"lte": "now/d"
}
}
},
"aggs": {
// 定义一个名为 user_profile 的聚合操作
"user_profile": {
// 使用 terms 聚合按 user_id 字段对文档进行分组
"terms": {
// 指定分组的字段为 user_id
"field": "user_id",
// 设置返回的分组数量上限为 1000,即最多返回 1000 个不同的 user_id 分组
"size": 1000,
// 按照每个分组中文档的数量(_count)进行降序排序
"order": { "_count": "desc" },
// 设置执行提示为 map,这种模式适合在数据量较小或者每个分片上数据分布比较均匀的情况下使用,能提高性能
"execution_hint": "map"
},
"aggs": {
// 在 user_id 分组内定义一个名为 unique_behaviors 的子聚合
"unique_behaviors": {
// 使用 cardinality 聚合计算每个 user_id 分组下 behavior_type 字段的唯一值数量
"cardinality": {
// 指定要计算唯一值数量的字段为 behavior_type
"field": "behavior_type",
// 设置精度阈值为 3000,该值越高,计算结果越精确,但会消耗更多的内存
"precision_threshold": 3000
}
},
// 在 user_id 分组内定义一个名为 city_distribution 的子聚合
"city_distribution": {
// 使用 terms 聚合按 geoip.city 字段对每个 user_id 分组内的文档进行进一步分组
"terms": {
// 指定分组的字段为 geoip.city
"field": "geoip.city",
// 设置返回的分组数量上限为 10,即每个 user_id 分组下最多返回 10 个不同的城市分组
"size": 10
}
},
// 在 user_id 分组内定义一个名为 risk_score 的子聚合
"risk_score": {
// 使用 bucket_script 聚合,用于根据其他聚合结果进行自定义的脚本计算
"bucket_script": {
// 指定要引用的其他聚合结果的路径,这里引用了 unique_behaviors 聚合的结果
"buckets_path": {
"behaviorCount": "unique_behaviors"
},
// 定义一个 Painless 脚本,根据 unique_behaviors 的计算结果进行风险评分
// 如果 behaviorCount 大于 10,则风险评分为 100,否则为 0
"script": "params.behaviorCount > 10 ? 100 : 0"
}
}
}
}
}
}
3. 性能调优策略
3.1 参数调优矩阵
参数 | 默认值 | 推荐值 | 优化效果 |
---|---|---|---|
shard_size | size*1.5 | size*3 | 分片级精度提升25% |
execution_hint | global_ordinals | map | 查询速度提升40% |
precision_threshold | 3000 | 10000 | 精度提升至99.99% |
max_buckets | 10000 | 100000 | 支持更大规模分桶分析 |
doc_values | true | 按需关闭 | 内存使用降低30% |
execution_hint
- execution_hint 能够指导 Elasticsearch 采用何种算法与策略来执行聚合操作,特别是在 terms 聚合中应用广泛。通过合理设置该参数,可以减少内存占用、提升计算速度,从而提高聚合操作的整体性能。
取值及含义: map、global_ordinals、global_ordinals_hash
- map 模式会在每个分片上直接对文档进行遍历,为每个文档的指定字段值维护一个计数器。
- global_ordinals 模式会先在所有分片上构建全局序号(global ordinals),这是一种用于表示字段值的整数映射。对于高基数字段(即字段包含很多不同的值)和大规模数据,global_ordinals 模式可以显著减少内存占用,提高性能。
- 相比于 global_ordinals,global_ordinals_hash 可以更快地构建和使用全局序号,尤其在内存资源相对充足的情况下。
3.2 性能测试数据
数据规模 | 聚合复杂度 | 默认配置耗时 | 优化后耗时 | 内存消耗降低 |
---|---|---|---|---|
1千万文档 | 单层Terms | 820ms | 480ms | 28%↓ |
5千万文档 | Terms+Cardinality | 6.2s | 3.8s | 42%↓ |
1亿文档 | 三级嵌套聚合 | 14.5s | 8.9s | 55%↓ |
4. 企业级应用案例
4.1 电商账号风险识别
// 向 Elasticsearch 的 user_login 索引发送 GET 请求进行搜索操作
GET /user_login/_search
{
// 设置返回的文档数量为 0,因为我们主要关注聚合结果,而非具体的文档内容
"size": 0,
"aggs": {
// 定义一个名为 risk_users 的聚合操作,用于找出可能存在风险的用户
"risk_users": {
// 使用 terms 聚合按 user_id 字段对文档进行分组
"terms": {
// 指定分组依据的字段为 user_id
"field": "user_id",
// 设置返回的分组数量上限为 100,即最多返回 100 个不同的 user_id 分组
"size": 100,
// 按照 device_count 子聚合的结果进行降序排序
// 意味着会优先展示使用设备数量较多的用户分组
"order": { "device_count": "desc" }
},
"aggs": {
// 在 user_id 分组内定义一个名为 device_count 的子聚合
"device_count": {
// 使用 cardinality 聚合计算每个 user_id 分组下 device_id 字段的唯一值数量
// 即统计每个用户使用的不同设备的数量
"cardinality": { "field": "device_id" }
},
// 在 user_id 分组内定义一个名为 ip_variety 的子聚合
"ip_variety": {
// 使用 cardinality 聚合计算每个 user_id 分组下 login_ip 字段的唯一值数量
// 即统计每个用户登录时使用的不同 IP 地址的数量
"cardinality": { "field": "login_ip" }
},
// 在 user_id 分组内定义一个名为 time_span 的子聚合
"time_span": {
// 使用 stats 聚合对每个 user_id 分组下的 @timestamp 字段进行统计分析
// 会返回该分组内 @timestamp 字段的最小值、最大值、平均值、总和以及文档数量等统计信息
// 可用于了解每个用户登录时间的跨度和分布情况
"stats": { "field": "@timestamp" }
}
}
}
}
}
-
风险判定规则:
- 典型应用场景
- 反欺诈系统:检测账号接管(Account Takeover)攻击
- 风控系统:识别异常登录模式
- 安全审计:监控特权账号的异常行为
// 风险评分规则:当以下三个条件同时满足时,判定为高风险用户 // 用户使用超过 3 种不同设备登录。异常场景:正常用户通常不会在短时间内频繁更换设备,可能存在账号共享或被盗用 // 用户使用超过 5 个不同 IP 地址登录。异常场景:跨地域 / 运营商频繁切换 IP,可能涉及账号暴力破解或恶意操作 // 登录时间跨度小于 24 小时(86400000ms)。异常场景:在一天内使用多设备多 IP 登录,符合自动化攻击工具的特征 if (device_count > 3 && ip_variety > 5 && time_span.max - time_span.min < 86400000) { return RISK_SCORE.HIGH; }
- 典型应用场景
-
实施效果:
指标 | 优化前 | 优化后 | 提升幅度 |
---|---|---|---|
风险账号识别率 | 68% | 93% | 37%↑ |
误报率 | 22% | 6% | 73%↓ |
平均响应时间 | 2.8s | 1.2s | 57%↓ |
最大并发处理能力 | 800 QPS | 3500 QPS | 337%↑ |
4.2 社交网络异常检测
// 向 Elasticsearch 的 social_activity 索引发送 GET 请求进行搜索,重点在于进行聚合分析
GET /social_activity/_search
{
"aggs": {
// 定义一个名为 suspicious_groups 的聚合,用于找出可能存在可疑行为的用户组和行为类型组合
"suspicious_groups": {
// 使用 composite 聚合,它允许通过多个维度组合生成聚合桶
// 适用于需要对多个字段进行分组聚合,并且可能有大量唯一组合的场景
"composite": {
"sources": [
// 第一个聚合源,按 user_group 字段进行分组,创建名为 user_cluster 的分组
{ "user_cluster": { "terms": { "field": "user_group" } } },
// 第二个聚合源,按 action_type 字段进行分组,创建名为 behavior_type 的分组
{ "behavior_type": { "terms": { "field": "action_type" } } }
]
},
"aggs": {
// 在每个由 user_group 和 action_type 组合的分组内,定义一个名为 unique_devices 的子聚合
"unique_devices": {
// 使用 cardinality 聚合计算每个分组下 device_hash 字段的唯一值数量
// 即统计每个用户组和行为类型组合下使用的不同设备的数量
"cardinality": { "field": "device_hash" }
},
// 在每个由 user_group 和 action_type 组合的分组内,定义一个名为 ip_entropy 的子聚合
"ip_entropy": {
// 使用 cardinality 聚合计算每个分组下 client_ip 字段的唯一值数量
// 即统计每个用户组和行为类型组合下使用的不同 IP 地址的数量
"cardinality": { "field": "client_ip" }
}
}
}
}
}
5. 深度优化技巧
5.1 存储层优化
优化策略 | 实施方法 | 性能收益 | 适用场景 |
---|---|---|---|
列式存储优化 | 启用doc_values+压缩编码 | 35%↑ | 高基数字段聚合 |
预聚合数据 | 使用Rollup API | 60%↑ | 历史数据分析 |
分层存储 | 冷热数据分离 | 40%↑ | 时序数据场景 |
字段类型优化 | keyword代替text+fielddata | 50%↑ | 精确值聚合 |
5.2 查询层优化
// 向 Elasticsearch 集群发送 PUT 请求,用于更新集群的持久化设置。
// 持久化设置会在集群重启后依然生效。
PUT _cluster/settings
{
"persistent": {
// 设置一次搜索请求中聚合操作所能生成的最大桶(Bucket)数量为 1000000。
// 在 Elasticsearch 里,聚合操作会对搜索结果进行分组和统计,每个分组就是一个桶。
// 此设置可防止因聚合操作生成过多桶而导致内存溢出或查询性能下降。
// 若搜索请求中的聚合操作尝试生成超过该数量的桶,Elasticsearch 会返回错误。
"search.max_buckets": 1000000,
// 设置布尔查询(bool query)中允许的最大子句数量为 10000。
// 布尔查询是 Elasticsearch 中常用的查询类型,可通过 must、should、must_not 等子句组合多个查询条件。
// 当子句数量过多时,会增加查询的复杂度和资源消耗,此设置可避免因复杂查询导致性能问题。
"indices.query.bool.max_clause_count": 10000,
// 设置搜索线程池的大小为 32。
// 搜索线程池用于处理搜索请求,线程池中的线程会并行执行搜索任务。
// 合适的线程池大小可确保搜索请求能高效处理,若设置过小,可能导致请求排队等待;若设置过大,会造成资源竞争。
"thread_pool.search.size": 32,
// 设置搜索线程池的队列大小为 2000。
// 当搜索线程池中的所有线程都在忙碌时,新的搜索请求会被放入队列等待处理。
// 队列大小决定了最多可容纳多少个等待处理的请求,若队列已满,新的请求会被拒绝。
"thread_pool.search.queue_size": 2000
}
}
6. 问题排查与监控
6.1 常见问题矩阵
现象 | 根因分析 | 解决方案 | 工具支持 |
---|---|---|---|
聚合结果不完整 | 分片大小限制 | 调整shard_size参数 | Search Profiler |
内存溢出 | 全局序数加载过多 | 启用eager_global_ordinals | Hot Threads API |
响应时间波动 | 分片负载不均 | 定制routing策略 | Cluster Allocation |
聚合精度不足 | HyperLogLog精度限制 | 提高precision_threshold | Explain API |
6.2 关键监控指标
指标名称 | 健康阈值 | 采集方式 | 优化方向 |
---|---|---|---|
aggregation_latency | <1s | Nodes Stats API | 优化聚合顺序 |
fielddata_memory_usage | <30% JVM | Cluster Stats | 限制fielddata使用 |
query_cache_hit_rate | >85% | Indices Stats | 增加缓存内存 |
circuit_breaker_tripped | 0次/分钟 | Cluster Health | 调整断路器阈值 |
附录:聚合分析工具箱
工具类别 | 推荐方案 | 核心功能 |
---|---|---|
可视化分析 | Kibana Lens | 聚合结果动态展示 |
性能诊断 | Elasticsearch Profile | 聚合阶段耗时分析 |
数据采样 | Sampler Aggregation | 大数据量下快速抽样 |
压力测试 | Rally Benchmark | 聚合查询性能压测 |
实施建议:
生产环境必须设置断路器阈值
高频聚合
字段启用eager_global_ordinals
- 定期执行_forcemerge优化段文件
- 重要聚合查询需进行版本控制