From d71e20f0d601548d303e334a6887a1e3e95807e3 Mon Sep 17 00:00:00 2001 From: tangwang Date: Thu, 12 Mar 2026 09:22:06 +0800 Subject: [PATCH] 索引同步,用于性能测试 --- docs/ES/Reindex_from_remote_注意事项.md | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ frontend/index.html | 5 +++-- frontend/static/js/app.js | 45 ++++++++++++++++++++------------------------- scripts/reindex_from_remote_tenant_170_to_0.sh | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 docs/ES/Reindex_from_remote_注意事项.md create mode 100755 scripts/reindex_from_remote_tenant_170_to_0.sh diff --git a/docs/ES/Reindex_from_remote_注意事项.md b/docs/ES/Reindex_from_remote_注意事项.md new file mode 100644 index 0000000..90cb531 --- /dev/null +++ b/docs/ES/Reindex_from_remote_注意事项.md @@ -0,0 +1,167 @@ +# Reindex from Remote 注意事项(官方文档要点) + +基于 Elasticsearch 官方文档整理的 **Reindex from remote** 要点,用于从远程 ES 集群(如 8.x)迁移数据到本机集群(如 9.x)。 + +## 官方文档入口 + +- [Reindex API (current)](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html) +- [Reindex from remote (upgrade)](https://www.elastic.co/guide/en/elasticsearch/reference/8.19/reindex-upgrade-remote.html) +- [REST API: Reindex](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reindex-indices) + +--- + +## 必须注意的事项 + +### 1. 目标集群白名单(必须) + +> If reindexing from a remote cluster into a cluster using Elastic Stack, you must **explicitly allow the remote host** using the **`reindex.remote.whitelist`** node setting on the **destination** cluster. + +- **在目标集群(本机 ES)** 的 `elasticsearch.yml` 中配置,不是源集群。 +- 格式:只写 **host:port**,多个用逗号分隔;不写协议。例如: + ```yaml + reindex.remote.whitelist: "120.76.41.98:9200" + ``` +- 修改后需重启**执行 reindex 的节点**(通常是协调节点)才能生效。 + +### 2. 目标索引需事先创建 + +- Reindex **不会**复制源索引的 **settings / mappings**。 +- 目标索引的 mapping、分片数、副本数等需在调用 `_reindex` **之前**在本机创建好。 +- 可用本项目的 `mappings/search_products.json` 在本机创建同名索引。 + +### 3. 权限要求 + +- **源集群(远程)** 用于认证的用户(如 `source.remote.username`)需要: + - 集群权限:`monitor` + - 源索引权限:`read` +- **目标集群(本机)** 执行 reindex 的用户需要: + - 目标索引:`write` + - 若需自动创建目标索引:`auto_configure` 或 `create_index` 或 `manage` + +### 4. 源文档必须开启 _source + +- Reindex 依赖文档的 `_source` 字段;若源索引禁用了 `_source`,无法 reindex。 + +### 5. 远程 Reindex 不支持 Slicing + +- 文档明确说明:**Reindexing from remote clusters does not support manual or automatic slicing.** +- 不能通过 `slices` 或 `slice` 做并行加速,只能单任务拉取。 + +### 6. 远程拉取时的缓冲区与 batch size + +- 从远程 reindex 时,目标集群使用 **on-heap buffer**,默认最大约 **100MB**。 +- 若单文档很大,需在 `source` 里调小 **`size`**(每批文档数),例如 `"size": 500` 或 `200`,避免 OOM。 +- 默认 `size` 为 1000。 + +### 7. max_docs 与 conflicts + +- 用 **`max_docs`** 可限制只迁移前 N 条(注意:与 scroll 顺序不保证严格一致,但数量正确)。 +- 若设置 **`conflicts: "proceed"`**,在遇到版本冲突时仍会继续,但可能从源多读一些文档直到成功写入 `max_docs` 条。 + +### 8. 建议在源索引为 green 时执行 + +- 官方建议在源索引状态为 green 时 reindex,否则节点宕机等可能导致失败。 +- 若使用 `wait_for_completion=false`,可通过 Task API 查进度;重试时可能需要先删掉目标索引中部分数据或设置 `conflicts=proceed`。 + +### 9. 超时(可选) + +- `source.remote.socket_timeout`、`connect_timeout` 可调大,默认约 30s;大批量或网络慢时可适当增加。 + +--- + +## 示例:从远程 tenant_170 同步 10000 条到本机 tenant_0 + +- **源**:远程 `search_products_tenant_170`(约 39731 条) +- **目标**:本机索引 `search_products_tenant_0`,只同步 **10000 条** + +### 步骤 1:在本机 ES 配置白名单 + +在**本机** ES 的 `elasticsearch.yml` 中添加(或合并到已有 `reindex.remote.whitelist`): + +```yaml +reindex.remote.whitelist: "120.76.41.98:9200" +``` + +保存后重启本机 ES(或至少重启会执行 reindex 的节点)。 + +### 步骤 2:在本机创建目标索引 + +使用本项目 mapping 创建索引 `search_products_tenant_0`(若已存在且结构一致可跳过)。例如用 API: + +```bash +# 本机 ES(按需加 -u user:pass) +curl -X PUT 'http://localhost:9200/search_products_tenant_0?pretty' \ + -H 'Content-Type: application/json' \ + -d @mappings/search_products.json +``` + +或通过项目代码:`create_index_if_not_exists(es_client, "search_products_tenant_0", load_mapping())`。 + +### 步骤 3:在本机执行 Reindex(请求发往本机 ES) + +以下请求是发给**本机 ES**(例如 `http://localhost:9200`),由本机去拉远程数据。 + +```bash +# 请求发往本机 ES(ES 9.x 将 wait_for_completion 放在 query 参数) +curl -X POST 'http://localhost:9200/_reindex?wait_for_completion=true&pretty' \ + -H 'Content-Type: application/json' \ + -d '{ + "max_docs": 10000, + "source": { + "remote": { + "host": "http://120.76.41.98:9200", + "username": "essa", + "password": "4hOaLaf41y2VuI8y" + }, + "index": "search_products_tenant_170", + "size": 500 + }, + "dest": { + "index": "search_products_tenant_0" + } +}' +``` + +说明: + +- `max_docs: 10000`:最多写入 10000 条到目标。 +- `source.remote`:远程 ES 地址与认证(仅本机连远程时使用,不会把密码发到远程)。 +- `source.index`:远程索引名。 +- `source.size`:每批从远程拉取的文档数,500 可降低大文档时本机内存压力。 +- `dest.index`:本机目标索引名。 +- `wait_for_completion: true`:同步等待完成;数据量大可改为 `false`,用返回的 `task_id` 查进度:`GET _tasks/`。 + +### 步骤 4:校验条数 + +```bash +curl -X GET 'http://localhost:9200/search_products_tenant_0/_count?pretty' \ + -H 'Content-Type: application/json' \ + -d '{"query":{"match_all":{}}}' +``` + +预期约 10000 条(若未设 `max_docs` 则会与源索引条数一致)。 + +### 一键脚本(可选) + +项目内提供了脚本,可自动创建目标索引并执行上述 reindex(默认 10000 条,目标 `search_products_tenant_0`): + +```bash +# 确保本机 .env 中 ES_HOST 指向本机 ES(如 http://localhost:9200) +chmod +x scripts/reindex_from_remote_tenant_170_to_0.sh +./scripts/reindex_from_remote_tenant_170_to_0.sh +``` + +可通过环境变量覆盖:`REMOTE_ES_HOST`、`REMOTE_ES_USER`、`REMOTE_ES_PASS`、`MAX_DOCS`、`LOCAL_ES_HOST`。详见脚本注释。 + +--- + +## 小结 + +| 项目 | 说明 | +|------|------| +| 白名单 | 在**目标(本机)** ES 的 `elasticsearch.yml` 中配置 `reindex.remote.whitelist` | +| 目标索引 | 事先在本机创建好 mapping/settings | +| 远程权限 | 源集群用户需 `monitor` + 源索引 `read` | +| 限条数 | 使用 `max_docs`,例如 10000 | +| 大批/大文档 | 适当调小 `source.size`(如 500) | +| 并行 | 远程 reindex 不支持 `slices` | diff --git a/frontend/index.html b/frontend/index.html index 3418ae1..20eceee 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -73,9 +73,10 @@
- + - +
diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js index 8bbdb88..29a0b91 100644 --- a/frontend/static/js/app.js +++ b/frontend/static/js/app.js @@ -84,8 +84,8 @@ if (document.readyState === 'loading') { // 备用初始化:如果上面的初始化失败,在 window.onload 时再试一次 window.addEventListener('load', function() { - const tenantSelect = document.getElementById('tenantSelect'); - if (tenantSelect && tenantSelect.options.length === 0) { + const tenantList = document.getElementById('tenantList'); + if (tenantList && tenantList.options.length === 0) { console.log('Retrying tenant select initialization on window.load...'); initTenantSelect(); } @@ -93,8 +93,8 @@ window.addEventListener('load', function() { // 最后尝试:延迟执行,确保所有脚本都已加载 setTimeout(function() { - const tenantSelect = document.getElementById('tenantSelect'); - if (tenantSelect && tenantSelect.options.length === 0) { + const tenantList = document.getElementById('tenantList'); + if (tenantList && tenantList.options.length === 0) { console.log('Final retry: Initializing tenant select after delay...'); if (typeof getAvailableTenantIds === 'function') { initTenantSelect(); @@ -111,11 +111,12 @@ function handleKeyPress(event) { } } -// 初始化租户下拉框 +// 初始化租户输入框(带 162/170 等候选,可自行填写任意 tenant ID) function initTenantSelect() { const tenantSelect = document.getElementById('tenantSelect'); - if (!tenantSelect) { - console.error('tenantSelect element not found'); + const tenantList = document.getElementById('tenantList'); + if (!tenantSelect || !tenantList) { + console.error('tenantSelect or tenantList element not found'); return; } @@ -128,25 +129,19 @@ function initTenantSelect() { const availableTenants = getAvailableTenantIds(); console.log('Available tenants:', availableTenants); - if (!availableTenants || availableTenants.length === 0) { - console.warn('No tenant IDs found in configuration'); - return; - } - - // 清空现有选项 - tenantSelect.innerHTML = ''; + // 清空 datalist 现有选项 + tenantList.innerHTML = ''; - // 添加选项 - availableTenants.forEach(tenantId => { - const option = document.createElement('option'); - option.value = tenantId; - option.textContent = tenantId; - tenantSelect.appendChild(option); - }); - - // 设置默认值 - if (availableTenants.length > 0) { - tenantSelect.value = availableTenants.includes('170') ? '170' : availableTenants[0]; + if (availableTenants && availableTenants.length > 0) { + availableTenants.forEach(tenantId => { + const option = document.createElement('option'); + option.value = tenantId; + tenantList.appendChild(option); + }); + // 设置默认值(仅当输入框为空时) + if (!tenantSelect.value.trim()) { + tenantSelect.value = availableTenants.includes('170') ? '170' : availableTenants[0]; + } } // 初始化分面面板 diff --git a/scripts/reindex_from_remote_tenant_170_to_0.sh b/scripts/reindex_from_remote_tenant_170_to_0.sh new file mode 100755 index 0000000..214766e --- /dev/null +++ b/scripts/reindex_from_remote_tenant_170_to_0.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# 从远程 ES 的 search_products_tenant_170 同步 10000 条到本机 search_products_tenant_0。 +# 请求发往本机 ES,由本机去拉远程数据;需在本机 elasticsearch.yml 配置 reindex.remote.whitelist。 +# +# 用法: +# ./scripts/reindex_from_remote_tenant_170_to_0.sh +# +# 环境变量(可选): +# LOCAL_ES_HOST 本机 ES 地址,用于创建索引和发送 _reindex(默认从 .env 的 ES_HOST 读取,应为本机) +# REMOTE_ES_HOST 远程 ES 地址(默认 http://120.76.41.98:9200) +# REMOTE_ES_USER 远程 ES 用户名(默认 essa) +# REMOTE_ES_PASS 远程 ES 密码(默认 4hOaLaf41y2VuI8y) +# MAX_DOCS 同步条数(默认 10000) +# + +set -e + +cd "$(dirname "$0")/.." +PROJECT_ROOT="$(pwd)" + +# 加载 .env +# shellcheck source=scripts/lib/load_env.sh +source "${PROJECT_ROOT}/scripts/lib/load_env.sh" +load_env_file "${PROJECT_ROOT}/.env" + +# 本机 ES(发 _reindex 请求的目标) +LOCAL_ES_HOST="${LOCAL_ES_HOST:-${ES_HOST:-http://localhost:9200}}" +ES_USERNAME="${ES_USERNAME:-}" +ES_PASSWORD="${ES_PASSWORD:-}" +ES_INDEX_NAMESPACE="${ES_INDEX_NAMESPACE:-}" + +# 远程 ES(数据源) +REMOTE_ES_HOST="${REMOTE_ES_HOST:-http://120.76.41.98:9200}" +REMOTE_ES_USER="${REMOTE_ES_USER:-essa}" +REMOTE_ES_PASS="${REMOTE_ES_PASS:-4hOaLaf41y2VuI8y}" + +MAX_DOCS="${MAX_DOCS:-10000}" +SOURCE_INDEX="search_products_tenant_170" +DEST_INDEX="${ES_INDEX_NAMESPACE}search_products_tenant_0" +MAPPING_FILE="${PROJECT_ROOT}/mappings/search_products.json" + +# 本机 curl 认证 +AUTH_PARAM="" +if [ -n "$ES_USERNAME" ] && [ -n "$ES_PASSWORD" ]; then + AUTH_PARAM="-u ${ES_USERNAME}:${ES_PASSWORD}" +fi + +echo "本机 ES: $LOCAL_ES_HOST" +echo "远程 ES: $REMOTE_ES_HOST" +echo "源索引: $SOURCE_INDEX" +echo "目标索引: $DEST_INDEX" +echo "同步条数: $MAX_DOCS" +echo "" + +# 1. 若目标索引不存在,则创建 +if ! curl -s $AUTH_PARAM "${LOCAL_ES_HOST}/${DEST_INDEX}" -o /dev/null -w "%{http_code}" | grep -q 200; then + echo "创建目标索引: $DEST_INDEX" + if [ ! -f "$MAPPING_FILE" ]; then + echo "错误: mapping 文件不存在: $MAPPING_FILE" + exit 1 + fi + curl -X PUT "${LOCAL_ES_HOST}/${DEST_INDEX}" \ + -H "Content-Type: application/json" \ + $AUTH_PARAM \ + -d @"${MAPPING_FILE}" \ + -w "\nHTTP: %{http_code}\n" -s | tail -1 + echo "" +else + echo "目标索引已存在: $DEST_INDEX,将写入数据(可能覆盖同 id 文档)" +fi + +# 2. Reindex from remote(JSON 中的密码用 env 传入,避免 shell 转义) +echo "执行 Reindex from remote(最多 $MAX_DOCS 条)..." +export REMOTE_ES_HOST REMOTE_ES_USER REMOTE_ES_PASS SOURCE_INDEX DEST_INDEX MAX_DOCS +# ES 9.x 将 wait_for_completion 放在 query 参数,不在 body +curl -X POST "${LOCAL_ES_HOST}/_reindex?wait_for_completion=true&pretty" \ + -H "Content-Type: application/json" \ + $AUTH_PARAM \ + -d @- <