门户网站设计,网站建设结课小论文,单页响应式网站模板,深圳校园网站建设概述
基本了解
Elasticsearch 是一个开源的分布式搜索和分析引擎#xff0c;基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能#xff0c;是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强 核心功能 全文搜索:…概述
基本了解
Elasticsearch 是一个开源的分布式搜索和分析引擎基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强 核心功能 全文搜索: 提供对文本数据的快速匹配和排名能力实时数据处理: 支持实时写入、更新、删除和搜索分布式存储: 能够将数据分片shard存储在多个节点中具有高可用性和容错性聚合分析: 支持对大数据进行复杂的统计分析如平均值、最大值、分组统计等 核心概念 索引 索引是数据的逻辑组织单位可以类比为数据库中的“数据库”。每个索引由多个文档组成类似于一本书的目录
索引是查询的入口点比如当你要查“小说类书籍”会直接到“小说书架”查找而不是其他书架 文档 文档是数据存储的基本单元相当于关系型数据库中的“行”。每个文档是以 JSON 格式存储的键值对集合这一点注意和MySQL进行区分
每本书可以看作一个文档。每本书有具体的属性比如书名、作者、出版年份、ISBN 号等
{title: The Catcher in the Rye,author: J.D. Salinger,year: 1951,genre: Fiction
} 字段 文档中的属性或键值对比如书的“标题”、“作者”、“出版年份”等可以简单理解为一本书的详细信息
注意字段的类型可以指定例如 text、keyword、integer 等并决定了如何处理这些数据。例如“标题”是 text 类型支持全文搜索而“ISBN” 是 keyword 类型只支持精确匹配 分片 分片是数据的物理存储单位。每个索引可以分成多个分片分布在不同的节点上以提升性能
类似哈利波特系列的书很多在图书馆中分别放在一楼和二楼当寻找的时候同时派人去一楼和二楼寻找这样就可以节省寻找的时间这也就对应着分片的主要的作用分片使得查询和存储可以并行处理提高系统性能分片还提供了冗余和容错能力通过副本 节点 节点是 Elasticsearch 集群中的一个实例。每个节点都有特定的角色比如主节点、数据节点
例如在图书馆中可以将每个楼层看作一个节点可以存储数据也可以帮助查询比如楼层 1 存储小说楼层 2 存储科技书籍但两层楼之间可以互相配合
不同的节点可以扮演不同角色
主节点Master Node负责管理整个图书馆的目录分片的分布情况数据节点Data Node实际存储书籍和处理搜索请求协调节点Coordinator Node负责分发和合并查询结果 集群 集群是一组相互协作的节点共同存储和处理数据
整个图书馆可以看作是一个 Elasticsearch 集群包含多个楼层节点书架索引被分布在各个楼层节点上
集群中的节点可以随时增加或减少确保高扩展性当有大批人需要搜索数据时集群可以通过多个节点的并发处理快速完成任务
ES高性能的原因 倒排索引 倒排索引是 Elasticsearch 搜索速度快的核心技术它记录的是每个单词在文档中的位置而不是逐个文档搜索所有内容。
例如有三个文档记录的内容分别是文档1: 猫喜欢鱼文档2: 狗喜欢骨头文档3: 鱼喜欢水那么倒排索引就会建立如下索引
词语 文档ID
喜欢 1, 2, 3
猫 1
狗 2
鱼 1, 3
骨头 2
水 3当查找喜欢这个单词的时候Elasticsearch就不会扫描全部的文档而是直接从倒排索引中找到包含该单词的文档列表即可
综上所述ES使用倒排索引避免了逐个扫描文档直接定位到包含目标关键词的文档查询时间随文档总量的增长几乎不变。 分布式架构 Elasticsearch 将数据分成多个 分片 存储在不同的节点上查询时会并行搜索所有分片最后合并结果返回。
场景还假设在上面文档的内容中将索引分为3个分片中分布在三个节点上
分片1: 包含文档 1~100分片2: 包含文档 101~200分片3: 包含文档 201~300
当用户查询“鱼”Elasticsearch 会同时向 3 个分片发出请求
分片1 返回包含“鱼”的文档 1分片2 无结果分片3 返回包含“鱼”的文档 3
最后将结果合并返回给用户就像超时收银的不会将所有的客户都在一个收银节点通过设置多个收银节点完成最后的收银工作 缓存机制 Elasticsearch 使用内存缓存和文件系统缓存来存储常用的查询结果如果相同的查询被多次请求Elasticsearch 会直接从缓存中返回结果而无需重新计算 查询优化和分析器 lasticsearch 会对查询请求进行优化比如避免不必要的计算、合并多个相同的查询条件等
分析器Analyzer是对文本数据的处理器通常会对字段的内容进行分词、去停用词如 the、is、小写化等操作借助分词器和索引机制让全文搜索更加精准和迅速
使用
添加与访问测试 创建索引库 POST /user/_doc
{settings: {analysis: {analyzer: {ik: {tokenizer: ik_max_word}}}},mappings: {dynamic: true,properties: {nickname: {type: text,analyzer: ik_max_word},user_id: {type: keyword,analyzer: standard},phone: {type: keyword,analyzer: standard},description: {type: text,enabled: false},avatar_id: {type: keyword,enabled: false}}}
}新增数据 POST /user/_doc/_bulk
{ index: { _id: 1 } }
{ user_id: USER4b862aaa-2df8654a-7eb4bb65-e3507f66, nickname: 昵称1, phone: 手机号1, description: 签名1, avatar_id: 头像1 }
{ index: { _id: 2 } }
{ user_id: USER14eeea5-442771b9-0262e455-e46631d1, nickname: 昵称2, phone: 手机号2, description: 签名2, avatar_id: 头像2 }
{ index: { _id: 3 } }
{ user_id: USER484a6734-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: USER6f19d074-c33891cf-23bf5a83-57189c19, 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 }搜索所有数据 ES客户端接口
参数说明
indexName指定 Elasticsearch 的索引名称docType:指定文档的类型在 Elasticsearch 7.x 中已被弃用id文档的唯一标识符用于获取、索引或删除bodyElasticsearch 的请求体通常为 JSON 格式routing路由参数如果为空则使用默认的路由规则
常用接口功能 搜索 在 Elasticsearch 集群中搜索指定的索引直到成功为止如果所有节点都未响应则抛出 ConnectionException
cpr::Response search(const std::string indexName,const std::string docType,const std::string body,const std::string routing std::string()); 获取文档 从集群中获取指定 ID 的文档如果所有节点都未响应则抛出 ConnectionException
cpr::Response get(const std::string indexName,const std::string docType,const std::string id std::string(),const std::string routing std::string()); 索引文档 在集群中新增或更新文档如果 ID 未提供Elasticsearch 将自动生成 ID如果所有节点都未响应则抛出 ConnectionException
cpr::Response index(const std::string indexName,const std::string docType,const std::string id,const std::string body,const std::string routing std::string()); 删除文档 从集群中删除指定 ID 的文档如果所有节点都未响应则抛出 ConnectionException
cpr::Response remove(const std::string indexName,const std::string docType,const std::string id,const std::string routing std::string());
基本操作 预处理 创建索引然后插入数据 搜索 通过客户端对指定内容进行搜索 二次封装
封装思路 索引创建 动态设置索引名称、索引类型添加字段、设置字段类型及分词器设置构造的核心逻辑则通过Json::Value构造对应的请求正文 数据新增 特定索引中插入文档文档格式以JSON构造灵活支持动态字段和值 数据查询 封装搜索语法 生成符合ES查询语法的JSON格式请求体支持复杂的查询条件例如多条件组合 数据删除 封装删除请求接口可以按索引或文档 ID 进行删除
具体实现
工具函数 SerializeJSON数据序列化将Value转换为字符串 // 序列化bool Serialize(const Json::Value val , std::string dst){Json::StreamWriterBuilder swb;swb.settings_[emitUTF8] true;std::unique_ptrJson::StreamWriter sw(swb.newStreamWriter());std::stringstream ss;int ret sw-write(val, ss);if (ret ! 0) {std::cout Json反序列化失败\n;return false;}dst ss.str();return true;} UnSerializeJSON数据反序列化将字符串转换为Value // 反序列化bool UnSerialize(const std::string src, Json::Value val){Json::CharReaderBuilder crb;std::unique_ptrJson::CharReader cr(crb.newCharReader());std::string err;bool ret cr-parse(src.c_str(), src.c_str() src.size(), val, err);if (ret false) {std::cout json反序列化失败: err std::endl;return false;}return true;}
创建索引 append 作用给索引字段添加映射配置字段名称字段类型分析器名称是否启用该字段实现逻辑创建字段的JSON配置然后将其添加到_properties中然后字段映射以键值对的形式进行存储
ESIndex append(const std::string key, const std::string type text, const std::string analyzer ik_max_word, bool enabled true) {Json::Value fields;fields[type] type;fields[analyzer] analyzer;if (enabled false ) fields[enabled] enabled;_properties[key] fields;return *this;} create 作用根据settings和mappings配置通过客户端发送HTTP请求创建Elasticsearch索引逻辑 设置索引的mappins配置序列化索引配置为JSON字符串通过_client调用Elasticsearch的接口创建索引
bool create(const std::string index_id default_index_id) {Json::Value mappings;mappings[dynamic] true;mappings[properties] _properties;_index[mappings] mappings;std::string body;bool ret Serialize(_index, body);if (ret false) {LOG_ERROR(索引序列化失败);return false;}LOG_DEBUG({}, body);//2. 发起搜索请求try {auto rsp _client-index(_name, _type, index_id, body);if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(创建ES索引 {} 失败响应状态码异常: {}, _name, rsp.status_code);return false;}} catch(std::exception e) {LOG_ERROR(创建ES索引 {} 失败: {}, _name, e.what());return false;}return true;} 新增数据 append 作用添加一条记录到_item中逻辑将传入的键值对添加到_item对象中
templatetypename TESInsert append(const std::string key, const T val){_item[key] val;return *this;} insert 作用将_item序列化为JSON字符串逻辑 通过序列化函数将_item转换为JSON格式的字符串body发起请求将JSON数据发送到Elasticsearch中然后检查是否发送成功 bool insert(const std::string id ) {std::string body;bool ret Serialize(_item, body);if (ret false) {LOG_ERROR(索引序列化失败);return false;}LOG_DEBUG({}, body);//2. 发起搜索请求try {auto rsp _client-index(_name, _type, id, body);if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(新增数据 {} 失败响应状态码异常: {}, body, rsp.status_code);return false;}} catch(std::exception e) {LOG_ERROR(新增数据 {} 失败: {}, body, e.what());return false;}return true;}
删除数据 remove 作用删除指定ID的文档逻辑 调用客户端的remove方法向Elasticsearch发送删除请求判断是否成功同时对错误进行捕捉防止被其他错误中断
class ESRemove {public:ESRemove(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client){}bool remove(const std::string id) {try {auto rsp _client-remove(_name, _type, id);if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(删除数据 {} 失败响应状态码异常: {}, id, rsp.status_code);return false;}} catch(std::exception e) {LOG_ERROR(删除数据 {} 失败: {}, id, e.what());return false;}return true;}private:std::string _name;std::string _type;std::shared_ptrelasticlient::Client _client;
};
数据查询
主要功能 提供接口构建多条件的Elasticsearch布尔查询将查询条件序列化为JSON格式的请求体调用Elasticsearch客户端执行查询解析查询结果并返回文档数据
class ESSearch {public:ESSearch(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client){}ESSearch append_must_not_terms(const std::string key, const std::vectorstd::string vals) {Json::Value fields;for (const auto val : vals){fields[key].append(val);}Json::Value terms;terms[terms] fields;_must_not.append(terms);return *this;}ESSearch append_should_match(const std::string key, const std::string val) {Json::Value field;field[key] val;Json::Value match;match[match] field;_should.append(match);return *this;}ESSearch append_must_term(const std::string key, const std::string val) {Json::Value field;field[key] val;Json::Value term;term[term] field;_must.append(term);return *this;}ESSearch append_must_match(const std::string key, const std::string val){Json::Value field;field[key] val;Json::Value match;match[match] field;_must.append(match);return *this;}Json::Value search(){Json::Value cond;if (_must_not.empty() false) cond[must_not] _must_not;if (_should.empty() false) cond[should] _should;if (_must.empty() false) cond[must] _must;Json::Value query;query[bool] cond;Json::Value root;root[query] query;std::string body;bool ret Serialize(root, body);if (ret false) {LOG_ERROR(索引序列化失败);return Json::Value();}LOG_DEBUG({}, body);//2. 发起搜索请求cpr::Response rsp;try {rsp _client-search(_name, _type, body);if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(检索数据 {} 失败响应状态码异常: {}, body, rsp.status_code);return Json::Value();}} catch(std::exception e) {LOG_ERROR(检索数据 {} 失败: {}, body, e.what());return Json::Value();}//3. 需要对响应正文进行反序列化LOG_DEBUG(检索响应正文: [{}], rsp.text);Json::Value json_res;ret UnSerialize(rsp.text, json_res);if (ret false) {LOG_ERROR(检索数据 {} 结果反序列化失败, rsp.text);return Json::Value();}return json_res[hits][hits];}private:std::string _name;std::string _type;Json::Value _must_not;Json::Value _should;Json::Value _must;std::shared_ptrelasticlient::Client _client;
};
#pragma once
#include elasticlient/client.h
#include cpr/cpr.h
#include json/json.h
#include iostream
#include memory
#include logger.hppnamespace mag {/// 工具函数/*** brief 将 Json::Value 数据序列化为 JSON 字符串* param val 要序列化的 Json::Value 对象* param dst 序列化后的 JSON 字符串* return true 序列化成功* return false 序列化失败*/bool Serialize(const Json::Value val, std::string dst) {Json::StreamWriterBuilder swb;swb.settings_[emitUTF8] true; // 输出为 UTF-8 编码std::unique_ptrJson::StreamWriter sw(swb.newStreamWriter());std::stringstream ss;int ret sw-write(val, ss); // 执行序列化if (ret ! 0) {std::cout Json序列化失败\n;return false;}dst ss.str(); // 将结果写入目标字符串return true;}/*** brief 将 JSON 字符串反序列化为 Json::Value 对象* param src 待解析的 JSON 字符串* param val 解析后的 Json::Value 对象* return true 反序列化成功* return false 反序列化失败*/bool UnSerialize(const std::string src, Json::Value val) {Json::CharReaderBuilder crb;std::unique_ptrJson::CharReader cr(crb.newCharReader());std::string err;bool ret cr-parse(src.c_str(), src.c_str() src.size(), val, err); // 执行解析if (!ret) {std::cout Json反序列化失败: err std::endl;return false;}return true;}/// 索引创建模块/*** brief 索引创建类用于定义和创建 Elasticsearch 索引*/class ESIndex {public:/*** brief 构造函数初始化索引创建模块* param client Elasticsearch 客户端实例* param name 索引名称* param type 文档类型默认 _doc*/ESIndex(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client) {Json::Value analysis;Json::Value analyzer;Json::Value ik;Json::Value tokenizer;tokenizer[tokenizer] ik_max_word; // 设置分词器为 ik_max_wordik[ik] tokenizer;analyzer[analyzer] ik;analysis[analysis] analyzer;_index[settings] analysis; // 设置索引的 settings}/*** brief 添加字段映射* param key 字段名称* param type 字段类型默认 text* param analyzer 分词器名称默认 ik_max_word* param enabled 是否启用字段默认 true* return ESIndex 支持链式调用*/ESIndex append(const std::string key, const std::string type text, const std::string analyzer ik_max_word, bool enabled true) {Json::Value fields;fields[type] type; // 设置字段类型fields[analyzer] analyzer; // 设置字段分词器if (!enabled) fields[enabled] enabled; // 如果禁用字段_properties[key] fields; // 添加字段到映射配置中return *this;}/*** brief 创建索引* param index_id 索引 ID默认 default_index_id* return true 创建成功* return false 创建失败*/bool create(const std::string index_id default_index_id) {Json::Value mappings;mappings[dynamic] true; // 设置动态映射mappings[properties] _properties; // 添加字段映射_index[mappings] mappings;std::string body;bool ret Serialize(_index, body); // 将索引配置序列化为 JSONif (!ret) {LOG_ERROR(索引序列化失败);return false;}LOG_DEBUG({}, body);try {auto rsp _client-index(_name, _type, index_id, body); // 调用客户端创建索引if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(创建ES索引 {} 失败响应状态码异常: {}, _name, rsp.status_code);return false;}} catch (std::exception e) {LOG_ERROR(创建ES索引 {} 失败: {}, _name, e.what());return false;}return true;}private:std::string _name; // 索引名称std::string _type; // 文档类型Json::Value _properties; // 字段映射配置Json::Value _index; // 索引完整配置std::shared_ptrelasticlient::Client _client; // 客户端实例};/// 索引插入模块/*** brief 索引插入类支持动态数据插入到 Elasticsearch 索引*/class ESInsert {public:/*** brief 构造函数初始化插入模块* param client Elasticsearch 客户端实例* param name 索引名称* param type 文档类型默认 _doc*/ESInsert(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client) {}/*** brief 添加字段和数据* tparam T 数据类型* param key 字段名称* param val 字段值* return ESInsert 支持链式调用*/templatetypename TESInsert append(const std::string key, const T val) {_item[key] val; // 动态添加字段return *this;}/*** brief 插入数据到 Elasticsearch* param id 文档 ID默认为自动生成* return true 插入成功* return false 插入失败*/bool insert(const std::string id ) {std::string body;bool ret Serialize(_item, body); // 将数据序列化为 JSONif (!ret) {LOG_ERROR(数据序列化失败);return false;}LOG_DEBUG({}, body);try {auto rsp _client-index(_name, _type, id, body); // 调用客户端插入数据if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(插入数据 {} 失败响应状态码异常: {}, body, rsp.status_code);return false;}} catch (std::exception e) {LOG_ERROR(插入数据 {} 失败: {}, body, e.what());return false;}return true;}private:std::string _name; // 索引名称std::string _type; // 文档类型Json::Value _item; // 待插入的数据std::shared_ptrelasticlient::Client _client; // 客户端实例};/// 数据删除模块/*** brief 删除模块删除 Elasticsearch 中的文档*/class ESRemove {public:/*** brief 构造函数初始化删除模块* param client Elasticsearch 客户端实例* param name 索引名称* param type 文档类型默认 _doc*/ESRemove(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client) {}/*** brief 删除指定文档* param id 文档 ID* return true 删除成功* return false 删除失败*/bool remove(const std::string id) {try {auto rsp _client-remove(_name, _type, id); // 调用客户端删除文档if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(删除数据 {} 失败响应状态码异常: {}, id, rsp.status_code);return false;}} catch (std::exception e) {LOG_ERROR(删除数据 {} 失败: {}, id, e.what());return false;}return true;}private:std::string _name; // 索引名称std::string _type; // 文档类型std::shared_ptrelasticlient::Client _client; // 客户端实例};/// 数据查找模块/*** brief 数据查找模块用于执行复杂查询*/class ESSearch {public:/*** brief 构造函数初始化查询模块* param client Elasticsearch 客户端实例* param name 索引名称* param type 文档类型默认 _doc*/ESSearch(std::shared_ptrelasticlient::Client client, const std::string name, const std::string type _doc):_name(name), _type(type), _client(client) {}/*** brief 添加 must_not 条件* param key 字段名称* param vals 排除的值列表* return ESSearch 支持链式调用*/ESSearch append_must_not_terms(const std::string key, const std::vectorstd::string vals) {Json::Value fields;for (const auto val : vals) {fields[key].append(val);}Json::Value terms;terms[terms] fields;_must_not.append(terms); // 添加到 must_not 条件return *this;}/*** brief 添加 should 条件* param key 字段名称* param val 匹配的值* return ESSearch 支持链式调用*/ESSearch append_should_match(const std::string key, const std::string val) {Json::Value field;field[key] val;Json::Value match;match[match] field;_should.append(match); // 添加到 should 条件return *this;}/*** brief 添加 must 条件精确匹配* param key 字段名称* param val 精确匹配的值* return ESSearch 支持链式调用*/ESSearch append_must_term(const std::string key, const std::string val) {Json::Value field;field[key] val;Json::Value term;term[term] field;_must.append(term); // 添加到 must 条件return *this;}/*** brief 添加 must 条件模糊匹配* param key 字段名称* param val 模糊匹配的值* return ESSearch 支持链式调用*/ESSearch append_must_match(const std::string key, const std::string val) {Json::Value field;field[key] val;Json::Value match;match[match] field;_must.append(match); // 添加到 must 条件return *this;}/*** brief 执行查询* return Json::Value 查询结果*/Json::Value search() {Json::Value cond;if (!_must_not.empty()) cond[must_not] _must_not;if (!_should.empty()) cond[should] _should;if (!_must.empty()) cond[must] _must;Json::Value query;query[bool] cond;Json::Value root;root[query] query;std::string body;bool ret Serialize(root, body); // 序列化查询条件if (!ret) {LOG_ERROR(查询条件序列化失败);return Json::Value();}LOG_DEBUG(查询请求体: {}, body);cpr::Response rsp;try {rsp _client-search(_name, _type, body); // 执行查询if (rsp.status_code 200 || rsp.status_code 300) {LOG_ERROR(查询失败响应状态码异常: {}, rsp.status_code);return Json::Value();}} catch (std::exception e) {LOG_ERROR(查询失败: {}, e.what());return Json::Value();}LOG_DEBUG(查询响应: {}, rsp.text);Json::Value json_res;ret UnSerialize(rsp.text, json_res); // 解析响应结果if (!ret) {LOG_ERROR(查询结果反序列化失败);return Json::Value();}return json_res[hits][hits]; // 返回查询结果}private:std::string _name; // 索引名称std::string _type; // 文档类型Json::Value _must_not; // must_not 条件Json::Value _should; // should 条件Json::Value _must; // must 条件std::shared_ptrelasticlient::Client _client; // 客户端实例};
}