45.4 产品 (Elasticsearch / Solr / OpenSearch)

一句话

三者底层都是 Lucene,差别在分布式外壳、生态与许可证:Elasticsearch 生态最全但许可证有约束、OpenSearch 是 Apache 2.0 的分叉、Solr 更老但全文与大数据集成成熟——选型先看许可证与托管方式,再看功能。

一张表看全

维度 Elasticsearch OpenSearch Solr
底层 Lucene Lucene Lucene
许可证 Elastic License 2.0 / SSPL(非 OSI) Apache 2.0 Apache 2.0
来源 Elastic 自研 AWS 从 ES 7.10 分叉(2021) Apache 项目(比 ES 更早)
分布式 原生(分片/副本/协调节点) 同 ES 架构 SolrCloud(ZooKeeper 协调)
查询 DSL JSON DSL + SQL 兼容 ES DSL + SQL/PPL JSON/Solr Query Parser + SQL
聚合 强(bucket/metric/pipeline) 强(与 ES 类似) 强(facet)
向量检索 kNN(dense_vector) kNN(同源) dense vector + ANN
日志场景 事实标准(ELK) 常与 OpenSearch Dashboards 搭配 少
托管 Elastic Cloud、各家云 AWS OpenSearch Service 云厂商较少
运维复杂度 中高(分片/合并/JVM 堆) 与 ES 接近 中(ZooKeeper 多一层)

🔑 许可证是硬约束:SSPL/Elastic License 不是 OSI 认可的开源协议,把它打包进商业产品、或提供托管服务,会触发限制。很多公司选 OpenSearch 不是为了功能,而是为了“能合法地二次分发/托管”。

代码:按约束做选择

package main

import "fmt"

type need struct {
	name     string
	licenseOK bool // 能否接受非 OSI 许可证
	managed   string // 想要哪家的托管
	vector    bool   // 需要向量检索
	logs      bool   // 主要做日志分析
	small     bool   // 规模小、想省运维
}

var scenarios = []need{
	{"自建日志平台(ELK)", true, "", false, true, false},
	{"卖 SaaS,要嵌入搜索", false, "", false, false, false},
	{"已在 AWS,要全托管", true, "aws", true, true, false},
	{"小团队,先跑起来", true, "", false, false, true},
	{"已有 Solr 的大数据集成", true, "", false, false, false},
}

func recommend(n need) (string, string) {
	switch {
	case n.managed == "aws" && n.vector:
		return "OpenSearch", "AWS 托管 + kNN,许可证无约束"
	case n.vector && !n.licenseOK:
		return "OpenSearch", "Apache 2.0 + kNN;ES 的许可证可能不合规"
	case !n.licenseOK:
		return "OpenSearch", "要二次分发/托管,必须 Apache 2.0"
	case n.logs:
		return "Elasticsearch", "ELK 生态最成熟(Beats/Logstash/Kibana)"
	case n.small:
		return "Elasticsearch", "单节点/小集群上手最快,文档最多"
	default:
		return "Solr", "全文与大数据集成成熟,Apache 2.0"
	}
}

func main() {
	fmt.Printf("%-28s %-8s %-10s %-8s %-8s %-8s %s\n", "场景", "接受非OSI", "托管", "向量", "日志", "小规模", "推荐")
	for _, n := range scenarios {
		p, why := recommend(n)
		fmt.Printf("%-28s %-8v %-10s %-8v %-8v %-8v %s(%s)\n",
			n.name, n.licenseOK, n.managed, n.vector, n.logs, n.small, p, why)
	}
}

本机实测(macOS,Apple M1,Go 1.27):

场景                           接受非OSI   托管         向量       日志       小规模      推荐
自建日志平台(ELK)                  true                false    true     false    Elasticsearch(ELK 生态最成熟(Beats/Logstash/Kibana))
卖 SaaS,要嵌入搜索                 false               false    false    false    OpenSearch(要二次分发/托管,必须 Apache 2.0)
已在 AWS,要全托管                  true     aws        true     true     false    OpenSearch(AWS 托管 + kNN,许可证无约束)
小团队,先跑起来                     true                false    false    true     Elasticsearch(单节点/小集群上手最快,文档最多)
已有 Solr 的大数据集成               true                false    false    false    Solr(全文与大数据集成成熟,Apache 2.0)

读数:

场景 推荐 关键理由
自建 ELK Elasticsearch 生态与工具链最完整
卖 SaaS 嵌入搜索 OpenSearch 许可证允许分发/托管
AWS 全托管 + 向量 OpenSearch 托管服务 + kNN,许可证干净
小团队起步 Elasticsearch 上手最快,文档最多
已有 Solr 集成 Solr 迁移成本大于收益

🔑 迁移判断只有一句:“现有系统的检索是不是瓶颈?” 不是瓶颈,换搜索引擎只会增加运维面;是瓶颈,再看“许可证/托管/向量”三个硬约束,而不是看 benchmark。

落地:Elasticsearch 的四个动作(未验证)

⚠️ 本机未安装 Elasticsearch/OpenSearch,以下命令未在真实集群验证,版本与参数以官方文档为准。

# 1) 指名 + 可抄:单节点开发模式(8.x)
docker run -d --name es -p 9200:9200 \
  -e discovery.type=single-node -e ES_JAVA_OPTS="-Xms1g -Xmx1g" \
  docker.elastic.co/elasticsearch/elasticsearch:8.15.0

# 2) 可验:集群健康与节点信息
curl -s localhost:9200/_cluster/health?pretty
# 预期:status 为 green(单节点)/yellow(有副本未分配)

# 3) 可抄 + 可验:建索引 + 写入 + 查询
curl -s -XPUT localhost:9200/items -H 'Content-Type: application/json' -d '
{"settings":{"number_of_shards":1,"number_of_replicas":0,"refresh_interval":"1s"},
 "mappings":{"properties":{"title":{"type":"text"},"price":{"type":"double"}}}}'
curl -s -XPOST localhost:9200/items/_doc/1 -H 'Content-Type: application/json' -d '
{"title":"手机 屏幕 保护膜","price":29.9}'
curl -s "localhost:9200/items/_search?q=title:屏幕&pretty"
# 预期:hits.total.value = 1

# 4) 可退:删索引(或停容器)
curl -s -XDELETE localhost:9200/items
步骤 判据 失败时看什么
启动 9200 可访问,health 返回 JSON 内存不足(-Xms 太大)、vm.max_map_count
建索引 返回 acknowledged: true mapping 字段类型写错
写入后立刻查 1s 内能搜到(refresh) 是否把 refresh_interval 设太大
回退 删除索引返回 acknowledged: true 索引名写错、权限不足

反模式

反模式 后果
把 ES 当主库 无事务、写入非实时持久,丢数据无法恢复
开自动 mapping 且字段爆炸 mapping 膨胀到几万个字段,集群拒绝写入(mapping explosion)
分片数按“节点数”定 扩容后要 reindex(45.3)
用 from+size 做深分页 协调节点内存爆(深分页要 search_after)
忽略许可证约束就嵌入产品 法务风险,可能要求开源或付费
把日志与业务检索混在一个集群 日志写入把业务查询拖慢,且保留策略冲突

自检

Q1 为什么很多公司从 Elasticsearch 迁到 OpenSearch,而不是反过来?

▸ 答案:主要原因是许可证:ES 7.11 起改为 Elastic License/SSPL,不再是 OSI 开源;如果公司要把搜索能力嵌入自己的产品、或提供托管服务,就可能违反条款。OpenSearch 是 ES 7.10 的分叉、Apache 2.0,能自由分发。功能上两者高度同源,迁移成本主要在插件与 API 细节。

Q2 为什么“自动 mapping”在生产上很危险?

▸ 答案:因为 ES 会为每个新出现的字段自动建 mapping,字段名带动态部分(如 attr_123、user_张三)时,字段数会无限增长;mapping 是集群状态的一部分,字段过多会让集群状态膨胀、写入变慢甚至拒绝写入(默认上限 1000 字段/索引)。生产上应关闭动态 mapping 或用 dynamic: strict,明确声明字段。

Q3 日志检索与业务检索为什么建议分开集群?

▸ 答案:两者的负载特征相反:日志是“写入量巨大、查询少、保留期短”,业务检索是“写入中等、查询频繁、要求低延迟”。混在一起会让日志写入的段合并与 IO 拖慢业务查询,而且保留策略(日志 7 天 vs 业务永久)无法分别设置。分开的代价是多一套集群,收益是故障隔离与容量可独立规划。

关联