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 业务永久)无法分别设置。分开的代价是多一套集群,收益是故障隔离与容量可独立规划。