跳到主要内容
Elasticsearch 核心概念、Kibana 测试与 C++ 客户端封装 | 极客日志
C++ 算法
Elasticsearch 核心概念、Kibana 测试与 C++ 客户端封装 综述由AI生成 Elasticsearch 是一个开源分布式搜索引擎。其核心概念如索引、类型、字段及映射配置,涵盖了安装配置流程、Kibana 可视化测试示例以及数据增删改查操作。重点展示了如何使用 C++ 结合 cpr 和 jsoncpp 库对 Elasticsearch 原生 API 进行二次封装,通过类实现索引创建、数据插入、删除及复杂查询的链式调用,降低开发成本并提升效率。
怪力乱神 发布于 2026/2/4 更新于 2026/5/29 6.4K 浏览概念简述
Elasticsearch,简称 ES,是一个开源分布式搜索引擎。其特点包括:分布式、零配置、自动发现、索引自动分片、索引副本机制、RESTful 风格接口、多数据源、自动搜索负载等。ES 类似数据库,相比传统数据库,它在搜索功能上更为实用、高效。
在搜索上与数据库的区别?
数据库的搜索策略类似二叉搜索树,但在文本搜索场景下,只能使用 LIKE 模糊匹配,效率较低。而 ES 主要做分词搜索,比如'你好,世界',会被分成:'你'、'好'、'世'、'界'、'你好'、'世界'等。
ES 核心概念
索引 :一个索引就是一个拥有几分相似特征的文档的集合,类似于 MySQL 数据库中的库。
类型 :一个类型是索引的一个逻辑上的分类/分区,类似于 MySQL 数据库中库结构下的表。
字段 :相当于 MySQL 数据库中的列,对文档数据根据不同属性进行的分类标识。
字段类型:
分类 类型 备注 字符串 text, keywordtext 会被分词生成索引;keyword 不会被分词生成索引,只能精确值搜索 整形 integer, long, short, byte- 浮点 double, float- 逻辑 booleantrue 或 false 日期 date, date_nanos'2018-01-13'或'2018-01-13 12:10:30'或者时间戳,即 1970 到现在的秒数/毫秒数 二进制 binary二进制通常只存储,不索引 范围 range-
字符串是最常用的字段类型。
提示:ES 中的类型基本上已经被弃用,通常是一个 ES 索引管理一种数据。
映射
它定义了每条数据记录(文档)中的每个字段(Field)应该是什么类型,以及应该如何被处理,对数据的处理方式和规则做一些限制。
比如该字段是否做搜索分析,比如我们在搜索好友时不会通过性别或个性签名去搜索到好友,所以这些字段不用做搜索分析。(通过 enabled 设置)
又或者在音乐软件上做搜索,那么用户想搜的不一定是歌名,也可以把歌手,用户名,歌词等等进行搜索分析,而我们可以为这些字段设置权重,把歌名做最高权重,然后依次根据需要做不同权重。(通过 boost 设置)
这就是映射的意义与重要性,如下 ES 的 映射参数:
indextrue(默认) / false 是否构建倒排索引(决定了是否分词,是否被索引)
dynamictrue(缺省)/ false 控制 mapping 的自动更新
doc_valuetrue(默认) / false 是否开启 doc_value,用于聚合和排序分析,分词字段不能使用
fielddata{"format": "disabled"} 是否为 text 类型启动 fielddata,实现排序和聚合分析针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用 doc_value
storetrue / false(默认) 是否单独设置此字段的是否存储而从 _source 字段中分离,只能搜索,不能获取值
coercetrue(默认) / false 是否开启自动数据类型转换功能,比如:字符串转数字,浮点转整型
analyzer{"analyzer":"ik"} 指定分词器,默认分词器为 standard analyzer
boost{"boost": 1.23} 字段级别的分数加权,默认值是 1.0
fields{"fields": { "raw": { "type": "text", "index": "not_analyzed" } }} 对一个字段提供多种索引模式,同一个字段的值可对应两种索引模式,一种分词索引,一种不分词索引
data_detectiontrue(默认) / false 是否自动识别日期类型
安装与配置
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
curl -s https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --no-default-keyring --keyring gnupg:/etc/apt/trusted.gpg.d/elasticsearch.gpg --import
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elasticsearch.list
sudo apt update
sudo apt-get install elasticsearch=7.17.21
sudo systemctl start elasticsearch
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/7.17.21
sudo systemctl start elasticsearch
vim /etc/elasticsearch/elasticsearch.yml
network.host: 0.0 .0 .0
http.port: 9200
cluster.initial_master_nodes: ["node-1" ]
Kibana 是 Elastic Stack 技术栈中的一个开源数据分析与可视化平台。它作为一个基于 Web 的图形界面,为用户提供了对存储在 Elasticsearch 中的数据进行探索、可视化、交互和管理的能力。
sudo apt install kibana
sudo vim /etc/kibana/kibana.yml
elasticsearch.host: "http://localhost:9200"
sudo systemctl start kibana
sudo systemctl enable kibana
sudo systemctl status kibana
测试示例 POST /user/_doc
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
} ,
"mappings" : {
"dynamic" : true ,
"properties" : {
"昵称" : { "type" : "text" , "analyzer" : "ik_max_word" } ,
"用户 ID" : { "type" : "keyword" , "analyzer" : "standard" } ,
"手机号" : { "type" : "keyword" , "analyzer" : "standard" } ,
"描述" : { "type" : "text" , "enabled" : false } ,
"头像 ID" : { "type" : "keyword" , "enabled" : false }
}
}
}
post:以请求的方式提交数据
user:索引名称(存储用户数据的库)
_doc:类型(此处为文档类型标识)
analyzer:分词器设置,ik 为中文分词器,tokenizer 用于指定分词粒度,ik_max_word 表示以最大的粒度进行分词。
mapping:表示下面要描述的映射关系
dynamic:true 表示未定义的字段会自动添加到映射并使用默认配置
type
text:是一个文本类型
keyword:是一个文本类型,但是是关键字不进行分词
analyzer
standard:默认标准分词器
ik_max_word:中文分词器
POST /user/_doc/_bulk
{
"index" : { "_id" : "1" }
}
{ "user_id" : "USER4b8a2aaa-2df8654a-7eb4bb65-e3507f66" , "nickname" : "昵称 1" , "phone" : "手机号 1" , "description" : "签名 1" , "avatar_id" : "头像 1" }
{
"index" : { "_id" : "2" }
}
{ "user_id" : "USER14eeea5-442771b9-0262e455-e4663d1d" , "nickname" : "昵称 2" , "phone" : "手机号 2" , "description" : "签名 2" , "avatar_id" : "头像 2" }
{
"index" : { "_id" : "3" }
}
{ "user_id" : "USER4b8a6734-03a124f0-996c169d-d05c1869" , "nickname" : "昵称 3" , "phone" : "手机号 3" , "description" : "签名 3" , "avatar_id" : "头像 3" }
{
"index" : { "_id" : "4" }
}
{ "user_id" : "USER186ade83-4460d4a6-8c08068f-83127b5d" , "nickname" : "昵称 4" , "phone" : "手机号 4" , "description" : "签名 4" , "avatar_id" : "头像 4" }
{
"index" : { "_id" : "5" }
}
{ "user_id" : "USER6f1d9074-c33891cf-23bf5a83-57189a19" , "nickname" : "昵称 5" , "phone" : "手机号 5" , "description" : "签名 5" , "avatar_id" : "头像 5" }
{
"index" : { "_id" : "6" }
}
{ "user_id" : "USER97605c64-9833ebb7-d0455353-35a59195" , "nickname" : "昵称 6" , "phone" : "手机号 6" , "description" : "签名 6" , "avatar_id" : "头像 6" }
注意:每条数据需按'索引声明行 + 数据行'的格式书写,且每行需单独占一行。
POST /user/_doc/_search
{
"query" : {
"match_all" : { }
}
}
GET /user/_doc/_search?pretty
{
"query" : {
"bool" : {
"must_not" : [
{
"terms" : {
"user_id.keyword" : [ "USER4b862aaa-2df8654a-7eb4bb65-e3597766" , "USER14eeeaa5-442771b9-0262e455-e4663d1d" , "USER484a6734-93a124f0-996c169d-d05c1869" ]
}
}
] ,
"should" : [
{ "match" : { "user_id" : "昵称" } } ,
{ "match" : { "nickname" : "昵称" } } ,
{ "match" : { "phone" : "昵称" } }
]
}
}
}
must_not:描述必须不包含的项,反向搜索
should:描述应该遵循的条件,表示在以下任意一个字段中出现'昵称'则匹配成功。
注意:如上过滤条件中,user_id 需要加 keyword,表示不进行分词。不加该字段默认是分词匹配,分词字段与不分词字段无法直接匹配。
客户端 API 使用
构造函数 该接口用于创建和初始化 Elasticsearch 客户端实例。
hostUrlList:传入地址列表,即 http://ip:port,如果有多个地址,可以传入列表。
timeout:为连接超时时间,通常用缺省值。
查询数据 search 接口用于在指定索引中执行搜索查询,根据查询条件返回匹配的文档列表。
创建索引和新增数据 index 接口用于在 Elasticsearch 中创建索引,或新增数据和更新数据,通过指定索引名称、文档 ID 和内容来存储数据。
删除数据 remove 接口用于从 Elasticsearch 索引中删除指定 ID 的文档,永久移除目标数据。
indexName:索引名称
docType:指定文档类型
id:用于标识一个索引内键值的唯一性
body:正文部分,用于创建索引、搜索或添加数据的 JSON 字符串
routing:路由键,用于控制文档存储到哪个分片,通常就用缺省值
返回值类型为 cpr::Response,通常会使用它的 status_code 字段(是一个状态码,用于判断函数执行是否成功)和 text 字段(返回的正文部分,是一个 Json 字符串)。
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <iostream>
int main () {
elasticlient::Client client ({"http://127.0.0.1:9200" }) ;
try {
auto ret = client.search ("/user" , "_doc" , "{\"query\": { \"match_all\":{} }}" );
std::cout << ret.status_code << std::endl;
std::cout << ret.text << std::endl;
} catch (std::exception& ex) {
std::cout << "请求失败:" << ex.what () << std::endl;
return -1 ;
}
return 0 ;
}
二次封装源码 Elasticsearch 原生 API 需要开发者手动构造复杂的 JSON 查询语句,成本高且容易出错。我们可以对它进行二次封装,通过简洁的方法链式调用完成复杂查询,大大降低使用门槛,提升开发效率。
由于索引创建、数据查询、数据删除等操作可能分布在不同服务器执行,因此将它们拆分为多个类分别实现。如下:
#include <elasticlient/client.h>
#include <json/json.h>
#include <cpr/cpr.h>
#include <iostream>
#include <vector>
#include "../spdlog/logger.hpp"
bool Serialize (const Json::Value &val, std::string &out) {
auto nsw = Json::StreamWriterBuilder ().newStreamWriter ();
std::stringstream ss;
int ret = nsw->write (val, &ss);
if (ret != 0 ) {
std::cout << "序列化失败" ;
return false ;
}
out = ss.str ();
return true ;
}
bool UnSerialize (const std::string &str, Json::Value &val) {
auto crb = Json::CharReaderBuilder ().newCharReader ();
std::string erro;
bool ret = crb->parse (str.c_str (), str.c_str () + str.size (), &val, &erro);
if (ret == false ) {
std::cout << "反序列化失败" << std::endl;
return false ;
}
return true ;
}
class ESIndex {
public :
ESIndex (const std::shared_ptr<elasticlient::Client> client, std::string name, const std::string type = "_doc" )
: _client(client), _name(name), _type(type) {
Json::Value ik;
ik["tokenizer" ] = "ik_max_word" ;
Json::Value analyzer;
analyzer["ik" ] = ik;
Json::Value analysis;
analysis["analyzer" ] = analyzer;
Json::Value settings;
settings["analysis" ] = analysis;
_index["settings" ] = settings;
}
ESIndex append (const std::string &key, const std::string &type = "text" , const std::string &analyzer = "ik_max_word" , bool enabled = true )
{
Json::Value data;
data["type" ] = type;
data["analyzer" ] = analyzer;
data["enabled" ] = enabled;
_properties[key] = data;
return *this ;
}
bool create () {
Json::Value mappings;
mappings["dynamic" ] = true ;
mappings["properties" ] = _properties;
_index["mappings" ] = mappings;
std::string body;
Serialize (_index, body);
try {
auto ret = _client->index (_name, _type, "" , body);
if (ret.status_code < 200 || ret.status_code >= 300 ) {
LOG_ERR ("创建索引 {} 失败" , _name);
return false ;
}
} catch (std::exception &e) {
LOG_ERR ("创建索引 {} 失败:{}" , _name, e.what ());
return false ;
}
return true ;
}
private :
std::string _name;
std::string _type;
Json::Value _properties;
Json::Value _index;
std::shared_ptr<elasticlient::Client> _client;
};
class ESInsert {
public :
ESInsert (const std::shared_ptr<elasticlient::Client> client, std::string name, const std::string type = "_doc" )
: _client(client), _name(name), _type(type) {}
template <typename T>
ESInsert& append (const std::string& key, const T& val) {
_item[key] = val;
return *this ;
}
bool insert (const std::string& id = "" ) {
std::string data;
bool ret = Serialize (_item, data);
if (ret == false ) return false ;
try {
auto rsp = _client->index (_name, _type, id, data);
if (rsp.status_code < 200 || rsp.status_code >= 300 ) {
LOG_ERR ("数据插入失败:{}" , rsp.status_code);
return false ;
}
} catch (std::exception& e) {
LOG_ERR ("数据插入失败:{}" , e.what ());
return false ;
}
return true ;
}
private :
std::string _name;
std::string _type;
Json::Value _item;
std::shared_ptr<elasticlient::Client> _client;
};
class ESRemove {
public :
ESRemove (const std::shared_ptr<elasticlient::Client> client, std::string name, const std::string type = "_doc" )
: _client(client), _name(name), _type(type) {}
bool remove (std::string id) {
try {
auto rsp = _client->remove (_name, _type, id);
if (rsp.status_code < 200 || rsp.status_code >= 300 ) {
LOG_ERR ("数据删除失败:{}" , rsp.status_code);
return false ;
}
} catch (std::exception& e) {
LOG_ERR ("数据删除失败:{}" , e.what ());
return false ;
}
return true ;
}
private :
std::string _name;
std::string _type;
std::shared_ptr<elasticlient::Client> _client;
};
class ESSearch {
public :
ESSearch (const std::shared_ptr<elasticlient::Client> client, std::string name, const std::string type = "_doc" )
: _client(client), _name(name), _type(type) {}
ESSearch& append_must_not_terms (const std::string& key, const std::vector<std::string>& data) {
Json::Value mnt;
for (auto x : data) mnt[key].append (x);
Json::Value terms;
terms["terms" ] = mnt;
_must_not.append (terms);
return *this ;
}
ESSearch& append_must_terms (const std::string& key, const std::string& val) {
Json::Value mt;
mt[key] = val;
Json::Value terms;
terms["terms" ] = mt;
_must.append (terms);
return *this ;
}
ESSearch& append_must_match (const std::string& key, const std::string& val) {
Json::Value mm;
mm[key] = val;
Json::Value match;
match["match" ] = mm;
_must.append (match);
return *this ;
}
ESSearch& append_should_match (const std::string& key, const std::string& val) {
Json::Value sm;
sm[key] = val;
Json::Value match;
match["match" ] = sm;
_should.append (match);
return *this ;
}
Json::Value search () {
Json::Value data;
if (!_must_not.empty ()) data["must_not" ] = _must_not;
if (!_must.empty ()) data["must" ] = _must;
if (!_should.empty ()) data["should" ] = _should;
Json::Value bl;
bl["bool" ] = data;
Json::Value query;
query["query" ] = bl;
std::string body;
bool ret = Serialize (query, body);
if (ret == false ) {
LOG_ERR ("序列化失败" );
return Json::Value ();
}
cpr::Response rsp;
try {
rsp = _client->search (_name, _type, body);
if (rsp.status_code < 200 || rsp.status_code >= 300 ) {
LOG_ERR ("搜索失败:{}" , rsp.status_code);
return Json::Value ();
}
} catch (std::exception& e) {
LOG_ERR ("搜索失败:{}" , e.what ());
return Json::Value ();
}
Json::Value val;
ret = UnSerialize (rsp.text, val);
if (ret == false ) {
LOG_ERR ("反序列化失败" );
return Json::Value ();
}
return val["hits" ]["hits" ];
}
private :
std::string _name;
std::string _type;
Json::Value _must_not;
Json::Value _must;
Json::Value _should;
std::shared_ptr<elasticlient::Client> _client;
};
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online