【Elasticsearch】es初识,在项目架构中的用途,与mysql和kafka的配合使用,

发布于:2025-06-27 ⋅ 阅读:(20) ⋅ 点赞:(0)

ES是一个开源的高扩展的分布式全文检索引擎

在项目已有mysql增删改查的情况下,新增kafka,es流程

用户新增/修改商家(写MySQL)
↓
Kafka 生产者发送商家数据消息
↓
Kafka 消费者监听消息 → 写入 Elasticsearch
↓
前端搜索商家时 → 查询 Elasticsearch(不是 MySQL)

这个 Kafka + Elasticsearch 的写入同步 + 搜索解耦架构 是目前绝大多数中大型互联网企业的标准做法


一、为什么不能只查 MySQL而引入es查询?

传统做法的问题:

比如你在前端搜索商家名称“李大壮”,如果查的是 MySQL:

SELECT * FROM merchant WHERE name LIKE '%李大壮%' OR description LIKE '%李大壮%' LIMIT 10;

问题:

  • :LIKE + 多字段模糊查询在数据量大时性能非常差;
  • 不智能:MySQL 的模糊匹配不支持分词、不支持排序打分;
  • 不灵活:无法实现“相关度排序”“拼音搜索”“短语匹配”等搜索场景。

所以,企业通常引入 Elasticsearch(简称 ES)来替代 MySQL 作为 搜索引擎


二、为什么 MySQL 改动不直接同步到 Elasticsearch,而是先发 Kafka?

这是你最关键的问题:为什么加一层 Kafka?

原因一:解耦系统,避免写 ES 出错影响主业务

  • 如果你直接在写 MySQL 后同时写 ES,那么:

    • 如果 ES 挂了、网络抖动、接口异常等,会导致整个商家创建流程失败;
    • Kafka 相当于消息缓冲器 + 解耦层,即便 ES 挂了,Kafka 消息还能继续收集,等恢复后继续消费。

原因二:可支持异步、并发、大吞吐处理

  • Kafka 是高吞吐分布式系统,支持水平扩展(通过分区);
  • 消费者可以并行处理,批量写入 ES,系统更加稳定、可扩展。

原因三:可拓展更多下游系统,不只是 Elasticsearch

未来你可能还需要这些:

  • 同步商家数据到 Redis 缓存(做秒级展示);
  • 触发推送服务(比如新商家上线通知);
  • 同步用户画像系统、数据仓库。

如果有 Kafka,你只需要新增一个消费者;
如果没有 Kafka,你要改源代码、逻辑复杂,非常不利于维护。


三、流程总结:企业为什么这样做?

阶段 系统 作用
1 MySQL 业务主库,负责存储真实、结构化的数据
2 Kafka 消息中间件,做异步解耦、缓冲和流式处理
3 Elasticsearch 专用于搜索,支持全文检索、相关度评分等搜索功能
4 前端 直接查 ES,提升搜索速度、支持搜索功能增强

四、图示结构(简化):

      用户操作前端
           ↓
     后端写 MySQL(商家表)
           ↓
   ┌────→ Kafka ←────┐
   │                 │
  Kafka Consumer     │(可以多个系统消费)
   ↓                 ↓
ElasticSearch     Redis/OLAP等其他系统
   ↓
前端搜索

es查询使用案例

query := map[string]interface{}{
	// DSL 查询主体
	"query": map[string]interface{}{
		"bool": map[string]interface{}{ // bool 组合查询(企业中常用)
			"must": []interface{}{ // must 表示“必须匹配”的条件,全部满足才返回
				map[string]interface{}{
					"multi_match": map[string]interface{}{ // 多字段模糊匹配
						"query":  in.Keyword, // 用户输入的搜索关键词(如“口腔”)
						"fields": []string{"name", "business", "address"},
						"type":   "most_fields", 
						// type: most_fields → 各字段各自打分后相加,适合字段内容差异大;另一个选项 cross_fields 更适合字段语义接近。
					},
				},
			},
			"filter": filters, // 精准过滤器(如按省市区筛选,不能容忍模糊匹配)
			"should": []interface{}{ // 加分项,提升命中相关性
				map[string]interface{}{
					"match_phrase": map[string]interface{}{ 
						"name": in.Keyword, // 要求关键词“完整连续地”出现在 name 中才能加分(如“牙科诊所”)
					},
				},
				map[string]interface{}{
					"match_phrase": map[string]interface{}{
						"business": in.Keyword, // business 完整短语匹配
					},
				},
			},
			// 注:should 不设置 minimum_should_match 时,不满足不会过滤掉,只是不加分
		},
	},

	// 分页设置
	"from": (in.Page - 1) * in.PerPage, // 从第几条开始取数据(起始位置)
	"size": in.PerPage,                // 每页返回多少条

	// 排序设置
	"sort": []interface{}{
		map[string]interface{}{
			"order_score": "desc", 
			// 业务字段排序:order_score 是你在 merchant 映射里定义的字段,值越大代表权重越高。
			// 通常用于控制:优质商家排前面(如人工打分、运营配置)
		},
	},
}

// -----------------
// 构造查询DSL
	// DSL:Domain-Specific Language(领域特定语言)
	// 这里表示一段用于构建或解析 Elasticsearch 查询语句的逻辑
	query := map[string]interface{}{
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": []interface{}{
					map[string]interface{}{
						"multi_match": map[string]interface{}{ // multi_match 该字段使得查询支持不同类型的匹配模式
							// type 参数控制多字段匹配的方式,主要有两种:
							"query":  in.Keyword,
							"fields": []string{"name", "business", "address"},
							"type":   "most_fields", // 1.most_fields:把多个字段匹配结果 分开 算分,最后相加
							// 2.cross_fields:把多个字段 和在一起 匹配(适合同一个词,可能出现在name和business这种的情况)
							// 分数(score):Elasticsearch内部计算的相关度分值,用于衡量文档与查询的匹配程度,
							// 数值越大说明匹配越好,搜索结果排序默认会根据这个分数降序排列。
						},
					},
				},
				"filter": filters,
				// 其中should字段,主要作用是在满足上述条件下,增加相关度,缩小搜索范围。
				"should": []interface{}{ // should:表示“或”关系,满足should中条件的文档会被加分,提高其相关度排序,
					// 但文档不一定非要满足should中的条件才能匹配整个查询(除非设置minimum_should_match)。
					// 下面2map的效果:如果传入的name和business是能完全匹配的,则会获得更高的相关度评分。
					map[string]interface{}{
						"match_phrase": map[string]interface{}{ // match_phrase 是短语匹配,要求查询词,是顺序的不拆分的,否则匹配失败。
							"name": in.Keyword,
						},
					},
					map[string]interface{}{
						"match_phrase": map[string]interface{}{
							"business": in.Keyword,
						},
					},
				},
			},
		},
		"from": (in.Page - 1) * in.PerPage,
		"size": in.PerPage,
		"sort": []interface{}{ // 按照评分排序
			map[string]interface{}{"order_score": "desc"}, // 降序
		},
	}




https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到