当整个复制集群宕机时,必须首先启动一个节点,以便其他节点知道要加入哪个集群副本。
该首次启动决策基于 grastate.dat,这是一个存储在集群数据目录中的小型复制状态文件。最重要的字段是:
seqno- 该节点已知的最后一个事务编号safe_to_bootstrap- 该节点是否被标记为在干净关闭后可以安全地首先启动
干净关闭后 grastate.dat 的示例内容:
# saved replication state
version: 2.1
uuid: <cluster-uuid>
seqno: 12345
safe_to_bootstrap: 1
在此示例中:
seqno: 12345表示该节点知道的事务序列号最多到 12345safe_to_bootstrap: 1表示该节点被标记为可以安全地首先启动
如果整个集群已干净关闭,请启动最后停止的节点。实际上,这通常是具有:
- 最先进的
seqno safe_to_bootstrap: 1
的节点。首先启动该节点。这会告诉 Manticore 从该节点启动一个新的集群副本。之后,正常启动其余节点,以便它们可以重新加入。
在干净的完整集群关闭后使用此方法。
- Bash
- Systemd
searchd --new-clustermanticore_new_cluster如果其他节点在没有必要干净关闭状态的情况下首先启动,启动将被拒绝,以保护集群不从旧副本恢复。
如果所有节点崩溃或未干净关闭,grastate.dat 可能不再适合用于正常引导选择。在这种情况下,找到具有最新数据的节点,通常是具有最大 seqno 的节点,并使用 --new-cluster-force 启动它。这会覆盖正常保护,强制集群从选定节点启动。
在崩溃或未干净的完整集群关闭后使用此方法。
- Bash
- Systemd
searchd --new-cluster-forcemanticore_new_cluster --force如果复制节点或整个集群变得不可用,正确的恢复程序取决于仍有多少节点可以访问以及关闭是干净的还是突然的。
复制集群应被视为一个逻辑系统,而不是一组无关的服务器。这为您提供了多主写入和一致的数据,但也意味着您必须谨慎恢复法定人数。特别是,在确定缺失的节点确实已消失之前,不要运行恢复存活侧写入的手动恢复命令。该命令在本文后面显示为 SET CLUSTER <name> GLOBAL 'pc.bootstrap' = 1。如果过早运行它,可能会导致脑裂,并最终形成两个独立的集群。
在下面的示例中,除非另有说明,否则假设一个包含节点 A、B 和 C 的集群。
首先确定您处于哪种情况:
- 至少有一个节点仍在在线吗?
- 节点是干净停止,还是发生了崩溃?干净停止意味着
searchd正常关闭并在退出前有时间保存其复制状态。崩溃、断电或kill -9不是干净停止。 - 集群的存活部分是否仍具有法定人数?法定人数意味着足够多的节点仍能相互看到,以安全地保持可写集群。
- 如果所有节点都已关闭,应首先启动哪个节点以恢复集群?
有用的检查:
SHOW STATUS LIKE 'cluster_<name>_status'SHOW STATUS LIKE 'cluster_<name>_size'SHOW STATUS LIKE 'cluster_<name>_node_state'- 如果所有节点都已关闭,请检查
grastate.dat,这是存储在集群数据目录中的小型复制状态文件。特别注意seqno和safe_to_bootstrap:在干净关闭后,通常首选启动的节点是具有最先进seqno和safe_to_bootstrap: 1的节点。有关完整的引导程序,请参阅 重新启动集群。
干净关闭后 grastate.dat 可能的样子示例:
# saved replication state
version: 2.1
uuid: <cluster-uuid>
seqno: 12345
safe_to_bootstrap: 1
在此示例中:
seqno: 12345表示此节点知道到序列号 12345 的事务safe_to_bootstrap: 1表示此节点被标记为可以首先启动
在干净关闭的所有节点都关闭的恢复中,这通常是您使用 --new-cluster 首先启动的节点类型,以恢复集群。
恢复后,等待重新启动的节点报告 cluster_<name>_status=primary 和 cluster_<name>_node_state=synced 后,再将其视为完全可写。您可以使用 SHOW STATUS LIKE 'cluster_<name>_status' 和 SHOW STATUS LIKE 'cluster_<name>_node_state' 检查这一点。在本地测试中,重新启动的节点有时会在达到 synced/primary 之前短暂显示 cluster_<name>_node_state=joining 和 cluster_<name>_status=disconnected。
如果节点 A 正常关闭,节点 B 和 C 会继续处理写入。您可以使用 SHOW STATUS LIKE 'cluster_<name>_status' 和 SHOW STATUS LIKE 'cluster_<name>_size' 确认这些节点上的集群仍然健康。
当节点 A 重新启动时,它会自动重新加入集群。在同步完成之前,不要向该节点发送写入。检查 SHOW STATUS LIKE 'cluster_<name>_status' 和 SHOW STATUS LIKE 'cluster_<name>_node_state',并等待 primary / synced。
如果供体节点 B 或 C 仍然在它们的复制缓存中拥有节点 A 错过的所有事务,节点 A 可以使用增量状态传输 (IST) 进行追赶。IST 代表增量状态传输。这意味着节点仅接收它错过的事务,因此恢复通常更快且更轻量。否则,它将需要快照状态传输 (SST)。SST 代表快照状态传输。这意味着从另一个节点复制表文件,而不是仅仅重放缺失的事务。SST 更重:通常更慢,移动更多数据,并且可能在大型集群上使恢复更具破坏性。
如果节点 A 和 B 被干净关闭,节点 C 保持在线,节点 C 可以继续接受写入。如果您想确认它现在是唯一活动的节点,请在节点 C 上检查 SHOW STATUS LIKE 'cluster_<name>_status' 和 SHOW STATUS LIKE 'cluster_<name>_size'。
当节点 A 和 B 重新启动时,它们会自动重新加入并从节点 C 同步。在它们重新加入时,检查 SHOW STATUS LIKE 'cluster_<name>_status'、SHOW STATUS LIKE 'cluster_<name>_node_state' 和 SHOW STATUS LIKE 'cluster_<name>_size'。等待所有节点显示 primary / synced 并且集群大小符合预期后,再将恢复视为完成。
如果所有节点都正常关闭,集群完全离线,必须以特殊方式重新启动,以便成为集群的第一个运行节点。
在干净关闭时,每个节点将其最后一个事务号写入 grastate.dat。最后关闭的节点是首先启动的最安全节点:
- 它具有最先进
seqno - 它具有
safe_to_bootstrap: 1
使用 --new-cluster 启动该节点。这会告诉 Manticore 从此节点开始一个新的集群副本。如果您通过 Linux 上的 systemd 运行 Manticore,请使用 manticore_new_cluster。它会为您以 --new-cluster 模式启动 Manticore。
之后,正常启动其余节点并让它们重新加入。通过 SHOW STATUS LIKE 'cluster_<name>_status'、SHOW STATUS LIKE 'cluster_<name>_node_state' 和 SHOW STATUS LIKE 'cluster_<name>_size' 验证恢复。
如果您首先引导一个较不先进的节点,一个更先进的节点可能会稍后加入它并从较旧状态接收完整的 SST,这可能会丢弃仅存在于更先进节点上的事务。这就是为什么 safe_to_bootstrap: 1 的节点应是您的首选。
如果节点 A 因崩溃或网络问题消失,节点 B 和 C 会首先尝试重新连接到它。如果失败,它们会将其从集群中移除,重新计算法定人数,并继续作为主集群运行。
在本地测试中,对等节点的移除并非立即发生:存活的节点会在几秒钟内保持旧的集群大小,之后才会丢弃失败的对等节点并切换到较小的主集群。
当节点 A 重新启动时,它会自动重新加入,并以与干净的一节点关闭后相同的方式进行同步。同样,使用 SHOW STATUS LIKE 'cluster_<name>_status'、SHOW STATUS LIKE 'cluster_<name>_node_state' 和 SHOW STATUS LIKE 'cluster_<name>_size' 来确认恢复已完成。
如果节点 A 和 B 都丢失,只剩节点 C 在运行,那么在三个节点的集群中,节点 C 将不再拥有法定人数。它会切换到 non-primary 并拒绝写入。
写入错误是明确的:
ERROR 1064 (42000): cluster '<name>' is not ready, not primary state (synced)
如果节点 A 和 B 仅临时断开连接但仍能互相看到,它们可能会继续接受写入,而节点 C 保持隔离。如果需要查看哪一侧仍可写入,请在每侧使用 SHOW STATUS LIKE 'cluster_<name>_status'。
如果节点 A 和 B 确实崩溃,且节点 C 是唯一希望继续使用的存活副本,请在节点 C 上运行此命令,使其重新变为可写入状态:
如果您已确认其他节点确实离线,请运行:
- SQL
- JSON
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1POST /cli -d "
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1
"重要提示:
- 仅在确认其他节点无法到达后运行此命令
- 仅在必须存活的一侧运行
- 引导后,该节点可以再次接受写入,其他节点之后可以从它重新加入
如果所有节点都崩溃,grastate.dat 通常不再适合用于正常引导选择。在本地测试中,所有节点显示:
seqno: -1safe_to_bootstrap: 0
在这种情况下,选择数据最新的节点,并使用--new-cluster-force启动它。这会强制Manticore从该节点启动一个新的集群副本,即使通常的干净关闭元数据不可靠。如果你在Linux上通过systemd运行Manticore,请使用manticore_new_cluster --force。它会为你以--new-cluster-force模式启动Manticore。
然后正常启动其余节点并让它们重新加入。使用 SHOW STATUS LIKE 'cluster_<name>_status'、SHOW STATUS LIKE 'cluster_<name>_node_state' 和 SHOW STATUS LIKE 'cluster_<name>_size' 验证恢复。
偶数大小的集群最容易出现脑裂风险。例如,想象四个节点分裂成两个隔离的对,分布在两个数据中心。每边恰好有原始成员的一半,因此两边都没有法定人数,两边都停止接受写入。
如果在连接恢复前必须恢复写入,请仅选择分裂的一侧,并在该侧运行相同的恢复命令,使该侧重新变为可写入。在执行此操作之前,请在两侧检查 SHOW STATUS LIKE 'cluster_<name>_status',以确定哪一侧当前为非主集群。
选择应保持为可写集群的一侧,然后运行:
- SQL
- JSON
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1POST /cli -d "
SET CLUSTER posts GLOBAL 'pc.bootstrap' = 1
"永远不要在两侧都执行该语句。如果这样做,您将创建两个独立的主集群,当网络恢复时它们不会自动合并。
在本地测试中,突然失去四节点集群的一半会重现相同的 non-primary 行为,相同的恢复命令使一个存活的一半重新变为 primary。
使用默认配置,Manticore 正在等待您的连接:
- 为 MySQL 客户端开放端口 9306
- 为 HTTP/HTTPS 连接开放端口 9308
- 为 HTTP/HTTPS 以及来自其他 Manticore 节点和基于 Manticore 二进制 API 的客户端开放端口 9312
- SQL
- HTTP
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- docker
mysql -h0 -P9306HTTP 是无状态协议,因此不需要任何特殊的连接阶段:
curl -s "http://localhost:9308/search"require_once __DIR__ . '/vendor/autoload.php';
$config = ['host'=>'127.0.0.1','port'=>9308];
$client = new \Manticoresearch\Client($config);import manticoresearch
config = manticoresearch.Configuration(
host = "http://127.0.0.1:9308"
)
client = manticoresearch.ApiClient(config)
indexApi = manticoresearch.IndexApi(client)
searchApi = manticoresearch.searchApi(client)
utilsApi = manticoresearch.UtilsApi(client)import manticoresearch
config = manticoresearch.Configuration(
host = "http://127.0.0.1:9308"
)
async with manticoresearch.ApiClient(config) as client:
indexApi = manticoresearch.IndexApi(client)
searchApi = manticoresearch.searchApi(client)
utilsApi = manticoresearch.UtilsApi(client)var Manticoresearch = require('manticoresearch');
var client= new Manticoresearch.ApiClient()
client.basePath="http://127.0.0.1:9308";
indexApi = new Manticoresearch.IndexApi(client);
searchApi = new Manticoresearch.SearchApi(client);
utilsApi = new Manticoresearch.UtilsApi(client);import com.manticoresearch.client.ApiClient;
import com.manticoresearch.client.ApiException;
import com.manticoresearch.client.Configuration;
import com.manticoresearch.client.model.*;
import com.manticoresearch.client.api.IndexApi;
import com.manticoresearch.client.api.UtilsApi;
import com.manticoresearch.client.api.SearchApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("http://127.0.0.1:9308");
IndexApi indexApi = new IndexApi(client);
SearchApi searchApi = new UtilsApi(client);
UtilsApi utilsApi = new UtilsApi(client);using ManticoreSearch.Client;
using ManticoreSearch.Api;
using ManticoreSearch.Model;
string basePath = "http://127.0.0.1:9308";
IndexApi indexApi = new IndexApi(basePath);
SearchApi searchApi = new UtilsApi(basePath);
UtilsApi utilsApi = new UtilsApi(basePath);use std::sync::Arc;
use manticoresearch::{
apis::{
{configuration::Configuration,IndexApi,IndexApiClient,SearchApi,SearchApiClient,UtilsApi,UtilsApiClient}
},
};
async fn maticore_connect {
let configuration = Configuration {
base_path: "http://127.0.0.1:9308".to_owned(),
..Default::default(),
};
let api_config = Arc::new(configuration);
let utils_api = UtilsApiClient::new(api_config.clone());
let index_api = IndexApiClient::new(api_config.clone());
let search_api = SearchApiClient::new(api_config.clone());运行 Manticore 容器并使用内置的 MySQL 客户端连接到节点。
docker run --name manticore -d manticoresearch/manticore && docker exec -it manticore mysqlManticore Search 通过使用 MySQL 协议实现了 SQL 接口,允许使用任何 MySQL 库或连接器以及许多 MySQL 客户端连接到 Manticore Search,并像使用 MySQL 服务器一样操作它,而非直接操作 Manticore。
然而,SQL 方言有所不同,仅实现了 MySQL 中可用的 SQL 命令或函数的一个子集。此外,还存在 Manticore Search 特有的子句和函数,例如用于全文搜索的 MATCH() 子句。
Manticore Search 支持通过 MySQL 协议的 预处理语句。也可以使用客户端预处理语句。需要注意的是,Manticore 实现了多值 (MVA) 和 float_vector 数据类型,这些在 MySQL 或实现预处理语句的库中没有等价物。在这些情况下,值必须在原始查询中以逗号分隔的数字列表形式构造。
一些 MySQL 客户端/连接器需要用户/密码和/或数据库名称的值。由于 Manticore Search 没有数据库的概念,也没有实现用户访问控制,这些值可以任意设置,因为 Manticore 会简单地忽略它们。
SQL 接口的默认端口是 9306,且默认启用。
你可以在配置文件的 searchd 部分,使用 listen 指令配置 MySQL 端口,格式如下:
searchd {
...
listen = 127.0.0.1:9306:mysql
...
}
请记住,Manticore 没有用户认证功能,所以请确保 MySQL 端口不会被网络外的人访问。
可以使用单独的 MySQL 端口执行“VIP”连接。连接到此端口时,将绕过线程池,总是创建一个新的专用线程。这在严重过载的情况下非常有用,避免服务器停滞或阻止通过常规端口的连接。
searchd {
...
listen = 127.0.0.1:9306:mysql
listen = 127.0.0.1:9307:mysql_vip
...
}
连接 Manticore 最简单的方法是使用标准的 MySQL 客户端:
mysql -P9306 -h0
MySQL 协议支持SSL 加密。可以在同一个 mysql 监听端口上建立安全连接。
MySQL 连接支持压缩,客户默认即可使用。客户端只需指定连接应使用压缩即可。
下面是一个使用 MySQL 客户端的示例:
mysql -P9306 -h0 -C
压缩可用于安全连接和非安全连接。
官方 MySQL 连接器可用于连接 Manticore Search ,但可能需要在 DSN 字符串中传递某些设置,因为连接器可能会尝试执行 Manticore 尚未实现的某些 SQL 命令。
JDBC 6.x 及以上版本的连接器要求 Manticore Search 版本为 2.8.2 或更高,且 DSN 字符串应包含以下选项:
jdbc:mysql://IP:PORT/DB/?characterEncoding=utf8&maxAllowedPacket=512000&serverTimezone=XXX
默认情况下,Manticore Search 会向连接器报告自己的版本,但这可能导致一些问题。为解决该问题,应在配置文件的 searchd 部分设置 mysql_version_string 指令,将版本设为低于 5.1.1 的版本:
searchd {
...
mysql_version_string = 5.0.37
...
}
.NET MySQL 连接器默认使用连接池。为了正确获取 SHOW META 的统计信息,应将查询与 SHOW META 命令作为单个多语句发送(SELECT ...;SHOW META)。如果启用连接池,需在连接字符串中添加选项 Allow Batch=True 以允许多语句:
Server=127.0.0.1;Port=9306;Database=somevalue;Uid=somevalue;Pwd=;Allow Batch=True;
可以通过 ODBC 访问 Manticore。建议在 ODBC 字符串中设置 charset=UTF8。部分 ODBC 驱动不喜欢 Manticore 服务器报告的版本,因为它们将其视为非常旧的 MySQL 服务器。该版本信息可通过 mysql_version_string 选项覆盖。
基于 MySQL 的 Manticore SQL 支持 C 风格注释语法。所有从开启符 /* 到关闭符 */ 的内容都会被忽略。注释可以跨多行,不能嵌套,且不应被记录。MySQL 特有的 /*! ... */ 注释目前也被忽略。(注释支持主要是为了更好地兼容 mysqldump 生成的转储文件,而非增强 Manticore 与 MySQL 之间的一般查询互操作性。)
SELECT /*! SQL_CALC_FOUND_ROWS */ col1 FROM table1 WHERE ...
Manticore支持通过MySQL协议的预处理语句。
在数据库中,预处理语句是一种运行查询的方式,其中SQL代码与输入数据分开。而不是在查询中直接包含值,您使用占位符编写一次查询,然后单独提供值。
一些客户端库(例如,sqlx)仅以这种方式与数据库通信。
-
安全性(防止SQL注入): 这是最重要的原因。SQL注入是一种常见的网络漏洞,攻击者会将恶意SQL插入查询中。预处理语句确保用户输入被严格视为数据,而不是可执行代码,从而防止其被解释为SQL命令的一部分。
-
可读性与可维护性: 预处理语句通过将SQL逻辑与输入数据分离来提高代码清晰度,使代码更易于阅读和维护。
-
性能: 数据库可以缓存查询计划。如果相同的参数化查询多次运行但值不同,数据库可以重用缓存的执行计划,而不是每次解析和优化查询,这可以提高性能。
- 准备查询: 将带有占位符(如
?或?VEC?)的 SQL 语句发送到 Manticore。Manticore 会解析并编译该语句,存储它,并返回一个唯一 ID,可用于稍后执行该语句。 - 绑定参数: 分别发送占位符的值。Manticore 将这些值严格视为数据(而非 SQL 代码),并将其安全地插入到存储的语句中。
- 执行查询: Manticore 使用预编译的计划和提供的参数值来执行该语句。
- SQL
- PHP
INSERT INTO table (id, floatvec) VALUES (?, (?VEC?))$stmt = $mysqli->prepare("INSERT INTO tbl (id, str, floatvec) VALUES (0, ?, (?VEC?))");
$str = "I'm a string";
$vec = "0.1,0.2,0.3";
$stmt->bind_param("ss", $str, $vec);
$stmt->execute();这将执行:
INSERT INTO tbl (id, str, floatvec) VALUES (0, 'I\'m a string', (0.1,0.2,0.3))?(问号):表示单个参数(整数、浮点数或字符串)。字符串值会自动处理——例如,特殊字符如单引号会被转义,值会被引号括起来。布尔值可以作为字符串传递('true','false')。?VEC?:表示作为字符串提供的数字值列表(例如,1,2,3或0.3,15E+3, 20 ,INF)。仅允许数字、逗号和空格。验证后,值会严格按照提供的形式插入(无需额外转义或引号)。
在标准 Manticore SQL 语法中,MVA 或 float vectors 会以括号括起的值列表形式书写,例如 (1, 2, 3)。
使用预处理语句时,请直接在查询中包含括号,并仅使用 ?VEC? 占位符表示括号内的值。例如:
$stmt = $mysqli->prepare("INSERT INTO tbl (id, floatvec) VALUES (0, (?VEC?))");
$vec = "0.1,0.2,0.3";
$stmt->bind_param("s", $vec);
- 每个预处理语句仅允许一个 SQL 语句。多查询(例如,
SELECT ...; SHOW META)不被支持。如果需要运行多个语句,请为每个语句分别准备,并在同一会话中按顺序执行它们。 - 某些驱动程序会将数值参数作为 DOUBLE 发送(例如,Node.js 的
mysql2)。如果需要严格的整数行为(例如,拒绝负数 ID),请将整数作为字符串发送,或使用特定于驱动程序的整数类型(例如,BigInt)。 - Rust sqlx: 在读取结果集行时,请使用列索引而非列名。结果集中存在列名,但 sqlx 不使用它们进行映射。例如,使用
row.try_get(0)?而非row.try_get("id")?。