在生产环境中,搜索建议(Suggester)需要实时反映数据变化,例如:
- 新商品上架,用户输入“iph”应立即出现“iPhone 15 Pro”;
- 商品下架或改名,建议应同步移除或更新;
- 热门活动开启,临时增加“618大促”等运营词。
为此,我们必须设计一套 Elasticsearch 实时更新 suggest 数据流,实现 低延迟、高可靠、可扩展 的建议数据同步。
本文将为你提供一套完整的 实时更新 suggest 数据流架构与实现方案。
一、目标与核心需求
| 目标 | 说明 |
|---|---|
| ⚡ 低延迟 | 数据变更到建议可见 < 10 秒 |
| 📦 高可靠性 | 不丢数据,支持重试与幂等 |
| 🔄 双向同步 | 支持中文、拼音、首字母等多形式输入 |
| 🧱 可扩展 | 支持多业务线、多租户 |
| 🛡️ 高可用 | 组件故障不影响主业务 |
| 📊 可观测性 | 监控延迟、失败率、积压情况 |
二、系统架构设计
+------------------+ +---------------------+
| 业务系统 (MySQL) | --> | Debezium / Canal |
+------------------+ +----------+----------+
|
v
+--------------------------+
| Kafka (Change Event) |
| - topic: data-changes |
+------------+-------------+
|
v
+--------------------------+
| Flink / Spark Streaming |
| - 清洗、生成 suggest |
| - 写入 ES suggest 字段 |
+------------+-------------+
|
v
+--------------------------+
| Elasticsearch Cluster |
| - completion suggester |
+------------+-------------+
|
v
+--------------------------+
| Suggestion Cache Refresh |
| - 更新 Redis / 本地缓存 |
+--------------------------+
✅ 基于 CDC(Change Data Capture) + 消息队列 + 流处理 构建实时数据流。
三、数据流详细步骤
Step 1:捕获数据变更(CDC)
使用 Debezium 或 Canal 监听 MySQL binlog:
-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY,
title VARCHAR(255),
status ENUM('online', 'offline'),
tags JSON
);
Debezium 将每条变更转化为事件:
{
"op": "c", // create
"ts_ms": 1717236000000,
"before": null,
"after": {
"id": 1001,
"title": "iPhone 15 Pro",
"status": "online"
}
}
发送到 Kafka topic:
mysql.products
Step 2:流处理(Flink Job)
使用 Flink 消费 Kafka 事件,生成 suggest 数据。
示例代码(Java/Scala)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<ChangeRecord> stream = env
.addSource(new FlinkKafkaConsumer<>("mysql.products", new JsonDeserializationSchema(), props));
DataStream<SuggestDocument> suggestStream = stream
.filter(record -> "online".equals(record.getAfter().getStatus()))
.map(record -> {
String title = record.getAfter().getTitle();
List<String> inputs = PinyinUtils.generateInputs(title); // 生成中文、拼音、首字母
return new SuggestDocument(
record.getAfter().getId(),
inputs,
calculateWeight(title) // 热门商品权重高
);
});
// 写入 Elasticsearch
suggestStream.addSink(new ElasticsearchSinkBuilder<SuggestDocument>()
.setHosts(new HttpHost("es-node:9200", 9200, "http"))
.setBulkRequestConsumer((requests, context) -> {
for (ActionRequest request : requests) {
IndexRequest idx = (IndexRequest) request;
Map<String, Object> source = idx.sourceAsMap();
source.put("suggest", buildSuggestField(source.get("inputs"), source.get("weight")));
idx.source(source, XContentType.JSON);
}
})
.build());
Step 3:生成 Suggest 输入
public class PinyinUtils {
public static List<String> generateInputs(String title) {
List<String> inputs = new ArrayList<>();
// 1. 中文分词输入
inputs.add(title);
inputs.add("苹果手机");
// 2. 完整拼音
inputs.add("pingguoshouji");
inputs.add("iphone15pro");
// 3. 首字母
inputs.add("pgs");
inputs.add("i15p");
// 4. 分词拼音
inputs.add("pingguo");
inputs.add("shouji");
return inputs;
}
}
可结合 IK 分词器提升分词质量。
Step 4:写入 Elasticsearch
PUT /products-suggest/_doc/1001
{
"title": "iPhone 15 Pro",
"suggest": {
"input": [
"iPhone 15 Pro", "苹果手机", "pingguoshouji", "iphone15pro", "pgs", "i15p"
],
"weight": 50
}
}
- 使用
upsert模式,支持更新; - 可结合
routing控制数据局部性。
Step 5:缓存同步(可选)
建议数据变更后,主动刷新缓存:
redisClient.publish("suggestion:updated", "1001");
缓存服务监听 channel,删除旧缓存或预加载:
PSUBSCRIBE suggestion:*
四、删除与下架处理
1. 逻辑删除(status=offline)
- Flink 过滤掉
status=offline的记录; - 不再写入 suggest;
- 可定时任务清理 ES 中已下架的 suggest 文档。
2. 物理删除
- 监听
op=delete事件; - 调用
DELETE /products-suggest/_doc/1001删除 suggest 文档。
五、性能优化建议 ✅
| 场景 | 建议 |
|---|---|
| 批量写入 | Flink 使用 bulk 批量写入 ES |
| 幂等处理 | 使用 doc_as_upsert 避免重复 |
| 背压控制 | Flink 设置 parallelism 和 buffer |
| 热点 key | 对 inputs 做 rebalance 避免倾斜 |
| 监控延迟 | 记录 ts_ms 到 ES 写入时间差 |
六、容错与可靠性保障
| 机制 | 说明 |
|---|---|
| Kafka 持久化 | 事件持久存储,支持重放 |
| Flink Checkpoint | 精确一次(exactly-once)语义 |
| ES 写入重试 | 失败时重试 3 次,记录死信队列 |
| 幂等写入 | 使用商品 ID 作为 _id |
| 监控告警 | 监控 Kafka Lag、Flink Checkpoint 失败 |
七、监控指标设计
| 指标 | 采集方式 |
|---|---|
| Kafka Lag | kafka_consumer_lag |
| E2E 延迟 | now() - ts_ms |
| Flink Checkpoint 间隔 | Flink Metrics |
| ES Bulk Rejection | elasticsearch_bulk_rejections |
| Suggest 文档数 | GET /products-suggest/_count |
八、扩展建议
| 场景 | 建议方案 |
|---|---|
| 多源聚合 | 合并商品、文章、用户等多源 suggest |
| 运营词注入 | 通过 Kafka 发送 {"type": "promotion", "word": "618大促"} |
| 个性化 suggest | 基于用户行为生成个性化输入 |
| A/B 测试 | 不同用户组写入不同 suggest 权重 |
| 冷启动填充 | 首次启动时全量导入历史数据 |
九、总结:实时 suggest 数据流 checklist ✅
| 项目 | 是否完成 |
|---|---|
| CDC 捕获变更 | ✅ |
| Kafka 传输 | ✅ |
| Flink 流处理 | ✅ |
| 生成拼音/首字母 | ✅ |
| 写入 ES suggest 字段 | ✅ |
| 缓存同步 | ✅ |
| 下架自动移除 | ✅ |
| 监控与告警 | ✅ |
| 幂等与重试 | ✅ |

被折叠的 条评论
为什么被折叠?



