OpenSearch 是一个社区驱动的、基于 Apache 2.0 许可证的开源搜索与分析套件。它源于 Elasticsearch 7.10.2 和 Kibana 7.10.2 的分支,由 AWS 主导并在 2021 年推出,旨在提供一个真正开源、厂商中立的搜索平台。开发者使用 OpenSearch 构建应用搜索、日志分析、可观测性平台、数据摄入管道等多种场景。
OpenSearch 的诞生源于 2021 年 Elastic 公司修改 Elasticsearch 和 Kibana 的许可证事件。Elastic 将这两款产品的开源协议从 Apache 2.0 变更为 Server Side Public License (SSPL) 和 Elastic License,这引发了开源社区的广泛讨论。
关键时间线:
| 时间 | 事件 |
|---|---|
| 2010 | Elasticsearch 项目创立,基于 Apache 2.0 许可证 |
| 2013 | Elastic 公司成立,提供商业支持 |
| 2021.01 | Elastic 宣布变更许可证为 SSPL/Elastic License |
| 2021.04 | AWS 宣布基于 Elasticsearch 7.10.2 创建 OpenSearch 分支 |
| 2021.07 | OpenSearch 1.0 正式发布 |
| 2022.05 | OpenSearch 2.0 发布,引入多项新特性 |
| 2023+ | 持续迭代,社区不断扩大 |
OpenSearch 项目由 OpenSearch Software Foundation(隶属于 Linux Foundation)托管,确保项目的长期开源性和社区中立性。这意味着:
| 考量维度 | OpenSearch | Elasticsearch (Elastic License) |
|---|---|---|
| 开源协议 | Apache 2.0(永久) | SSPL / Elastic License |
| 商业使用 | 完全自由 | 需遵守 Elastic License 限制 |
| 云服务集成 | AWS/OpenSearch Service 原生支持 | Elastic Cloud 优先 |
| 安全功能 | 开源安全插件(免费) | 部分安全功能需商业许可 |
| 社区生态 | 快速增长,AWS 主导 | 成熟,Elastic 主导 |
| 长期稳定性 | Linux Foundation 背书 | Elastic 公司控制 |
Hugo 的选型建议:如果你的团队已经在 AWS 上运行,或者对开源协议的纯粹性有要求,OpenSearch 是更稳妥的选择。Elastic 的许可证变更历史表明,依赖单一厂商的开源项目存在协议风险。对于金融等对合规性要求高的行业,Apache 2.0 的明确性更有价值。
OpenSearch 采用分布式架构,核心设计理念与 Elasticsearch 一致:
┌─────────────────────────────────────────────────────────┐
│ Client Layer │
│ (REST API / SDK / OpenSearch Dashboards) │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ Coordinating Node │
│ (接收请求、路由、聚合结果、返回给客户端) │
└─────────────────────────────────────────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ Primary │ │ │ │ Replica │ │ │ │ Primary │ │
│ │ Shard A │ │ │ │ Shard A │ │ │ │ Shard B │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ Replica │ │ │ │ Primary │ │ │ │ Replica │ │
│ │ Shard B │ │ │ │ Shard C │ │ │ │ Shard C │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
└──────────────┘ └──────────────┘ └──────────────┘
OpenSearch Dashboards 是 Kibana 的开源分支,提供:
| 组件 | 功能 | 说明 |
|---|---|---|
| OpenSearch ML Commons | 机器学习 | 支持模型训练、推理、向量搜索 |
| OpenSearch Neural Search | 神经搜索 | 基于向量的语义搜索 |
| OpenSearch SQL | SQL 接口 | 用 SQL 查询 OpenSearch |
| OpenSearch Alerting | 告警 | 基于查询的监控告警 |
| OpenSearch Index Management | 索引生命周期 | 自动 rollover、删除、快照 |
| OpenSearch Anomaly Detection | 异常检测 | 基于 ML 的时序异常发现 |
| OpenSearch Security | 安全认证 | 认证、授权、加密、审计 |
OpenSearch 基于 Apache Lucene 构建,提供企业级全文搜索能力:
除了全文搜索,OpenSearch 也擅长精确匹配和范围查询:
聚合是 OpenSearch 的强大分析工具,分为三大类:
指标聚合(Metrics Aggregations):
avg、sum、min、max、stats:基础统计percentiles、percentile_ranks:百分位分析cardinality:唯一值计数(基于 HyperLogLog)top_hits:返回每组最相关的文档桶聚合(Bucket Aggregations):
terms:按字段值分组(类似 SQL GROUP BY)date_histogram:按时间区间分组range、date_range:按自定义范围分组histogram:按数值区间分组nested、reverse_nested:嵌套文档分组管道聚合(Pipeline Aggregations):
derivative:计算相邻桶的差值moving_avg:移动平均cumulative_sum:累积求和bucket_script:桶间脚本计算OpenSearch 2.x 引入原生向量搜索支持,这是现代 RAG(检索增强生成)应用的基础设施:
// 创建支持向量字段的索引
PUT /my-knn-index
{
"settings": {
"index.knn": true
},
"mappings": {
"properties": {
"vector_field": {
"type": "knn_vector",
"dimension": 768,
"method": {
"name": "hnsw",
"space_type": "l2",
"engine": "faiss"
}
}
}
}
}
向量搜索的应用场景:
OpenSearch 的安全插件(Security Plugin)提供企业级安全能力:
Hugo 的踩坑记录:在金融系统中启用 OpenSearch 安全插件时,务必在集群初始化前就配置好 TLS 证书。如果在已有数据的集群上后开启安全插件,可能需要重新创建集群。建议生产环境使用企业 CA 签发的证书,而非自签名证书。
| 功能特性 | OpenSearch | Elasticsearch (Elastic License) |
|---|---|---|
| 全文搜索 | ✅ 完整支持 | ✅ 完整支持 |
| 聚合分析 | ✅ 完整支持 | ✅ 完整支持 |
| 向量搜索 (k-NN) | ✅ 原生支持 | ✅ 完整支持 |
| 安全插件 | ✅ 开源免费 | ⚠️ 部分功能需付费 |
| 机器学习 | ✅ ML Commons | ✅ X-Pack ML(付费) |
| 可观测性 | ✅ Observability | ✅ Elastic Observability |
| 地理空间 | ✅ 完整支持 | ✅ 完整支持 |
| SQL 查询 | ✅ OpenSearch SQL | ✅ X-Pack SQL(基础免费) |
| 跨集群复制 | ✅ CCR 插件 | ✅ CCR(付费) |
| 异步搜索 | ✅ 支持 | ✅ 支持 |
| 数据流 (Data Streams) | ✅ 支持 | ✅ 支持 |
根据第三方基准测试(如 Search Benchmark Suite):
从 Elasticsearch 迁移到 OpenSearch 的路径:
直接升级路径:
快照迁移路径:
重新索引路径:
Hugo 的项目经验:在宝付的日志平台迁移中,我们从 Elasticsearch 7.8 升级到 OpenSearch 2.11,采用滚动升级方式,整个集群(3 主节点 + 12 数据节点)的升级过程耗时约 4 小时,期间服务无中断。关键步骤:先升级主节点,再逐个滚动升级数据节点,每次升级后等待集群状态恢复为 Green。
# docker-compose.yml
version: '3'
services:
opensearch:
image: opensearchproject/opensearch:2.11.0
container_name: opensearch
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
- DISABLE_SECURITY_PLUGIN=true # 开发环境可禁用安全插件
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- opensearch-data:/usr/share/opensearch/data
ports:
- 9200:9200
- 9600:9600
networks:
- opensearch-net
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:2.11.0
container_name: opensearch-dashboards
ports:
- 5601:5601
expose:
- "5601"
environment:
- 'OPENSEARCH_HOSTS=["http://opensearch:9200"]'
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
networks:
- opensearch-net
volumes:
opensearch-data:
networks:
opensearch-net:
driver: bridge
启动命令:
docker-compose up -d
访问 OpenSearch Dashboards:http://localhost:5601
系统调优:
# /etc/sysctl.conf
vm.max_map_count=262144 # 必须设置,否则 OpenSearch 无法启动
# /etc/security/limits.conf
opensearch soft nofile 65536
opensearch hard nofile 65536
opensearch soft memlock unlimited
opensearch hard memlock unlimited
JVM 配置:
硬件建议:
使用 OpenSearch Operator 简化 K8s 部署:
apiVersion: opensearch.opster.io/v1
kind: OpenSearchCluster
metadata:
name: my-cluster
spec:
general:
serviceName: my-cluster
version: 2.11.0
dashboards:
enable: true
replicas: 1
resources:
requests:
memory: "512Mi"
cpu: "200m"
nodePools:
- component: masters
replicas: 3
diskSize: "30Gi"
resources:
requests:
memory: "4Gi"
cpu: "1000m"
- component: data
replicas: 3
diskSize: "100Gi"
resources:
requests:
memory: "8Gi"
cpu: "2000m"
文档是 OpenSearch 中存储的基本信息单元,使用 JSON 格式:
{
"_index": "products",
"_id": "1",
"_source": {
"name": "iPhone 15 Pro",
"brand": "Apple",
"category": "智能手机",
"price": 8999.00,
"stock": 150,
"tags": ["5G", "OLED", "钛金属"],
"release_date": "2023-09-15",
"specs": {
"screen": "6.1英寸",
"chip": "A17 Pro",
"storage": "256GB"
}
}
}
索引是逻辑上的文档集合,类似于关系数据库中的表:
# 创建索引
PUT /products
# 创建索引并指定设置和映射
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": { "type": "text" },
"price": { "type": "float" },
"stock": { "type": "integer" },
"release_date": { "type": "date" }
}
}
}
映射定义了索引中字段的数据类型和分析方式:
核心字段类型:
| 类型 | 说明 | 示例 |
|---|---|---|
text |
全文搜索文本,会被分词 | 文章正文、产品描述 |
keyword |
精确值,不分词 | 标签、枚举值、ID |
long/integer |
整数 | 数量、计数 |
float/double |
浮点数 | 价格、评分 |
boolean |
布尔值 | 是否上架、是否删除 |
date |
日期时间 | 创建时间、更新时间 |
object |
嵌套 JSON 对象 | 地址、规格 |
nested |
嵌套对象数组(独立查询) | 订单中的商品列表 |
geo_point |
地理坐标 | 店铺位置、配送地址 |
ip |
IP 地址 | 访问者 IP |
# 查看索引分片分配
GET /_cat/shards/products?v
# 调整副本数
PUT /products/_settings
{
"number_of_replicas": 2
}
分片设计原则:
# 动态映射(自动推断字段类型)
PUT /logs-2024
{
"mappings": {
"dynamic": true # 默认行为
}
}
# 显式映射(推荐生产环境)
PUT /logs-2024
{
"mappings": {
"dynamic": "strict", # 遇到未定义字段拒绝写入
"properties": {
"timestamp": { "type": "date", "format": "strict_date_optional_time||epoch_millis" },
"level": { "type": "keyword" },
"message": { "type": "text", "analyzer": "standard" },
"service": { "type": "keyword" },
"host": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"duration_ms": { "type": "integer" }
}
}
}
同一个字段可以同时支持全文搜索和精确匹配:
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
使用方式:
title:全文搜索("苹果手机"匹配"苹果 iPhone 手机")title.keyword:精确匹配、排序、聚合("苹果手机"只匹配"苹果手机")用于自动应用映射和设置到新创建的索引:
PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"index.lifecycle.rollover_alias": "logs"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" }
}
}
},
"priority": 500
}
# 创建 ILM 策略
PUT /_plugins/_ism/policies/logs_policy
{
"policy": {
"description": "日志索引生命周期策略",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{
"rollover": {
"min_index_age": "1d",
"min_primary_shard_size": "50gb"
}
}
],
"transitions": [
{
"state_name": "warm",
"conditions": {
"min_index_age": "7d"
}
}
]
},
{
"name": "warm",
"actions": [
{
"replica_count": {
"number_of_replicas": 0
}
}
],
"transitions": [
{
"state_name": "delete",
"conditions": {
"min_index_age": "30d"
}
}
]
},
{
"name": "delete",
"actions": [
{
"delete": {}
}
]
}
]
}
}
{
"query": {
// 查询条件
},
"from": 0, // 分页起始
"size": 10, // 每页大小
"sort": [ // 排序
{ "price": "asc" }
],
"_source": [ // 返回字段过滤
"name", "price", "brand"
],
"highlight": { // 高亮
"fields": {
"name": {}
}
}
}
Match 查询(全文搜索):
{
"query": {
"match": {
"name": "苹果手机"
}
}
}
Term 查询(精确匹配):
{
"query": {
"term": {
"brand.keyword": "Apple"
}
}
}
Range 查询(范围):
{
"query": {
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
}
Bool 查询(组合条件):
{
"query": {
"bool": {
"must": [
{ "match": { "name": "手机" } },
{ "range": { "price": { "lte": 10000 } } }
],
"filter": [
{ "term": { "brand.keyword": "Apple" } },
{ "range": { "stock": { "gt": 0 } } }
],
"must_not": [
{ "term": { "status": "discontinued" } }
]
}
}
}
重要区别:
must中的查询会参与相关性评分,filter中的查询不评分但会被缓存,性能更好。对于精确过滤条件,优先使用filter。
嵌套查询(Nested Query):
{
"query": {
"nested": {
"path": "specs",
"query": {
"bool": {
"must": [
{ "match": { "specs.key": "内存" } },
{ "match": { "specs.value": "16GB" } }
]
}
}
}
}
}
聚合搜索(Search + Aggregation):
{
"query": {
"match": { "category": "手机" }
},
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" }
},
"price_stats": {
"stats": { "field": "price" }
}
},
"size": 0 // 不返回文档,只返回聚合结果
}
按品牌统计商品数量和平均价格:
{
"aggs": {
"by_brand": {
"terms": {
"field": "brand.keyword",
"size": 10
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"max_price": {
"max": { "field": "price" }
}
}
}
}
}
按日期统计订单趋势:
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "day"
},
"aggs": {
"total_revenue": {
"sum": { "field": "amount" }
},
"order_count": {
"value_count": { "field": "order_id" }
}
}
}
}
}
计算每个品牌的价格百分位:
{
"aggs": {
"by_brand": {
"terms": { "field": "brand.keyword" },
"aggs": {
"price_percentiles": {
"percentiles": {
"field": "price",
"percents": [25, 50, 75, 95, 99]
}
}
}
}
}
}
计算销售额的环比增长:
{
"aggs": {
"monthly_sales": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
},
"aggs": {
"revenue": {
"sum": { "field": "amount" }
},
"revenue_growth": {
"derivative": {
"buckets_path": "revenue"
}
}
}
}
}
}
# 集群健康状态
GET /_cluster/health
# 节点状态
GET /_cat/nodes?v
# 索引状态
GET /_cat/indices?v
# 分片分配
GET /_cat/shards?v
# 集群统计
GET /_cluster/stats
健康状态含义:
添加新节点:
cluster.name 与现有集群一致discovery.seed_hosts 指向现有节点安全移除节点:
# 将节点排除在分片分配之外
PUT /_cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._name": "node-to-remove"
}
}
# 等待分片迁移完成后,停止该节点
创建快照仓库:
PUT /_snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mnt/backup",
"compress": true
}
}
创建快照:
PUT /_snapshot/my_backup/snapshot_2024_01_15
{
"indices": "logs-2024.01.*",
"ignore_unavailable": true,
"include_global_state": false
}
恢复快照:
POST /_snapshot/my_backup/snapshot_2024_01_15/_restore
{
"indices": "logs-2024.01.15",
"rename_pattern": "logs-(.+)",
"rename_replacement": "restored_logs-$1"
}
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 集群状态 Yellow | 副本分片未分配 | 增加节点或减少副本数 |
| 集群状态 Red | 主分片未分配 | 检查磁盘空间、节点状态;可能需要从快照恢复 |
| 写入拒绝 | 429 Too Many Requests |
增加刷新间隔、扩大队列、增加节点 |
| 查询慢 | 高延迟 | 优化查询、增加副本、使用 filter 替代 must |
| 内存不足 | OOM 错误 | 增加堆内存、优化聚合查询、减少分片数 |
| 磁盘不足 | 分片分配失败 | 删除旧索引、扩容磁盘、配置 ILM |
生产环境必须启用安全插件,配置 opensearch.yml:
plugins.security.ssl.transport.pemcert_filepath: transport-cert.pem
plugins.security.ssl.transport.pemkey_filepath: transport-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: root-ca.pem
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: http-cert.pem
plugins.security.ssl.http.pemkey_filepath: http-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: root-ca.pem
plugins.security.allow_default_init_securityindex: true
plugins.security.authcz.admin_dn:
- CN=admin,OU=SSL,O=Test,L=Test,C=DE
plugins.security.nodes_dn:
- CN=node,OU=SSL,O=Test,L=Test,C=DE
# 创建角色
PUT /_plugins/_security/api/roles/logs_reader
{
"cluster_permissions": [
"cluster_composite_ops"
],
"index_permissions": [{
"index_patterns": [
"logs-*"
],
"allowed_actions": [
"read",
"search",
"data_access"
]
}]
}
# 创建用户并绑定角色
PUT /_plugins/_security/api/internalusers/analyst
{
"password": "secure_password",
"backend_roles": ["logs_reader"],
"attributes": {
"department": "运维"
}
}
# 启用审计日志
PUT /_plugins/_security/api/audit/config
{
"enabled": true,
"audit": {
"ignore_users": ["kibanaserver"],
"ignore_requests": [],
"disabled_rest_categories": [],
"disabled_transport_categories": []
}
}
批量写入(Bulk API):
POST /_bulk
{ "index" : { "_index" : "products", "_id" : "1" } }
{ "name" : "iPhone 15", "price" : 8999 }
{ "index" : { "_index" : "products", "_id" : "2" } }
{ "name" : "MacBook Pro", "price" : 14999 }
批量写入最佳实践:
refresh_interval(如 30s)减少刷新频率-1(禁用自动刷新)索引模板优化:
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"refresh_interval": "30s",
"translog.durability": "async",
"translog.sync_interval": "30s"
}
}
1. 使用 Filter 替代 Must(不评分,可缓存):
{
"query": {
"bool": {
"must": [
{ "match": { "title": "搜索关键词" } }
],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "create_time": { "gte": "2024-01-01" } } }
]
}
}
}
2. 避免深度分页,使用 Search After:
{
"query": { "match_all": {} },
"sort": [
{ "create_time": "desc" },
{ "_id": "asc" }
],
"search_after": [1704067200000, "doc_12345"],
"size": 10
}
3. 控制返回字段:
{
"query": { "match_all": {} },
"_source": ["title", "summary", "author"],
"size": 10
}
| 优化项 | 建议 |
|---|---|
| 分片数 | 单节点不超过 600 个分片;单分片 20-50GB |
| 副本数 | 写入密集:0-1;查询密集:1-2;高可用:≥1 |
| 堆内存 | 物理内存的 50%,不超过 32GB |
| 文件系统缓存 | 剩余物理内存给 OS 缓存索引文件 |
| 磁盘 | SSD 必须;避免 NAS/SAN |
| 网络 | 万兆网络;避免跨 AZ/数据中心 |
| 工具 | 用途 |
|---|---|
| OpenSearch Dashboards | 数据可视化、探索、告警 |
| OpenSearch CLI | 命令行管理工具 |
| Data Prepper | 数据摄入和转换(类似 Logstash) |
| OpenSearch Benchmark | 性能测试工具 |
Python:
from opensearchpy import OpenSearch
client = OpenSearch(
hosts=[{'host': 'localhost', 'port': 9200}],
http_auth=('admin', 'admin'),
use_ssl=True,
verify_certs=False
)
# 搜索
response = client.search(
index="products",
body={
"query": {
"match": {"name": "手机"}
}
}
)
Java:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200))
);
SearchRequest request = new SearchRequest("products");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("name", "手机"));
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
logstash-output-opensearch 插件输出到 OpenSearch这是 OpenSearch 最常见的使用场景:
应用/服务器 → Filebeat/Fluent Bit → Kafka(可选)→ OpenSearch → Dashboards
典型配置:
logs-2024.01.15为电商、内容平台提供搜索能力:
核心功能:
结合 OpenSearch 的聚合能力进行时序数据分析:
构建语义搜索和知识库问答系统:
用户查询 → Embedding 模型 → 向量检索 → Top-K 文档 → LLM → 生成回答
关键组件:
| 工具 | 链接 | 说明 |
|---|---|---|
| OpenSearch CLI | https://github.com/opensearch-project/opensearch-cli | 命令行工具 |
| Data Prepper | https://github.com/opensearch-project/data-prepper | 数据摄入管道 |
| OpenSearch Benchmark | https://github.com/opensearch-project/opensearch-benchmark | 性能测试 |
版本说明:本文基于 OpenSearch 2.x 版本编写。部分 API 在 1.x 和 2.x 之间可能存在差异,建议生产环境使用前查阅对应版本的官方文档。
最后更新:2024 年