源和 發表於 2024-11-1 11:51:37

Rust整合Elasticsearch的详细过程(收藏)

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>全文搜索Elasticsearch是什么</li><li>Elastic Stack是什么</li><li>Elasticsearch能做什么</li><li>Elasticsearch 索引</li><li>Docker安装Elasticsearch、Kibana、IK</li><ul class="second_class_ul"><li>安装IK分词器</li><li>离线部署Elasticsearch、Kibana</li><li>加载镜像</li><li>命令部署Elasticsearch、Kibana</li><li>部署Kibana</li><li>访问Elasticsearch、Kibana</li></ul><li>分词原理</li><ul class="second_class_ul"></ul><li>分词流程</li><ul class="second_class_ul"><li>扩展词库</li><li>停用词库</li><li>使用</li><li>编辑扩展词库</li><li>添加分词</li><li>编辑停用词库</li><li>测试分词</li></ul><li>分词作用</li><ul class="second_class_ul"></ul><li>IK分词模式</li><ul class="second_class_ul"></ul><li>DSL 索引操作</li><ul class="second_class_ul"><li>查询索引库</li><li>删除索引库</li></ul><li>DSL文档操作</li><ul class="second_class_ul"><li>添加文档</li><li>查询文档</li><li>更新文档</li><li>删除文档</li></ul><li>使用流程</li><ul class="second_class_ul"></ul><li>分析数据结构</li><ul class="second_class_ul"></ul><li>添加索引库</li><ul class="second_class_ul"></ul><li>查询索引库是否存在</li><ul class="second_class_ul"></ul><li>Rust客户端操作文档</li><ul class="second_class_ul"></ul><li>Rust客户端操作搜索</li><ul class="second_class_ul"></ul><li>全文搜索</li><ul class="second_class_ul"></ul><li>多字段查询</li><ul class="second_class_ul"></ul><li>根据范围查询(range)</li><ul class="second_class_ul"></ul><li>根据词条精确查询(term)</li><ul class="second_class_ul"></ul><li>根据地理距离查询</li><ul class="second_class_ul"></ul><li>根据指定矩形范围查询</li><ul class="second_class_ul"></ul><li>复合查询</li><ul class="second_class_ul"></ul><li>关联度计算方法</li><ul class="second_class_ul"></ul><li>boolean query 布尔查询</li><ul class="second_class_ul"></ul><li>搜索结果处理</li><ul class="second_class_ul"></ul><li>高亮处理</li><ul class="second_class_ul"></ul><li>数据聚合</li><ul class="second_class_ul"><li>桶(Buket)</li></ul><li>自动补全</li><ul class="second_class_ul"><li>拼音补全</li><li>添加文档</li><li>根据关键字查询补全</li></ul></ul></div><p class="maodian"></p><h2>全文搜索Elasticsearch是什么</h2>
<p>Lucene:Java实现的搜索引擎类库</p>
<ul><li>易扩展</li><li>高性能</li><li>仅限Java开发</li><li>不支持水平扩展</li></ul>
<p>Elasticsearch:基于Lucene开发的分布式搜索和分析引擎</p>
<ul><li>支持分布式、水平扩展</li><li>提高RestfulAPI,可被任何语言调用</li></ul>
<p class="maodian"></p><h2>Elastic Stack是什么</h2>
<p>ELK(Elastic Stack):Elasticsearch结合Kibana、Logstash、Beats实现日志数据分析、实时监控</p>
<ul><li><code>Elasticsearch</code>:负责存储、搜索、分析数据</li><li><code>Kibana</code>:数据可视化</li><li><code>Logstash</code>、<code>Beats</code>:数据抓取(一般用Debezium、Flink、RisingWave&hellip;)</li></ul>
<p class="maodian"></p><h2>Elasticsearch能做什么</h2>
<p>实时数据分析:支持对实时数据进行索引和分析,可快速处理大量的日志、指标和事件数据<br />实时监控:对系统指标、业务数据和用户行为进行实时监控<br />电商搜索:为电商平台提供商品搜索功能,帮助用户快速找到所需的商品<br />知识库搜索:为企业内部的文档、知识库和业务数据提供搜索功能,提高员工的工作效率</p>
<p class="maodian"></p><h2>Elasticsearch 索引</h2>
<blockquote><p>传统数据库使用正向索引,依据id构建B+树,根据索引id查快,对于非索引文档如商品描述查需要全表扫描</p></blockquote>
<p>倒排索引:将文档分为词条和id进行存储,先查文档获取id,再根据id查数据库</p>
<ul><li>文档(Document):每条数据就是一个Json文档</li><li>词条(Term):文档按语义分成的词语</li></ul>
<p>索引(Index):相同类型文档的集合<br />映射(Mapping):索引中的文档约束信息<br />字段(Fielf):Json文档中的字段<br />DSL:Json风格的请求语句,用来实现CRUD</p>
<p class="maodian"></p><h2>Docker安装Elasticsearch、Kibana、IK</h2>
<p>1、先创建自定义网络</p>
<blockquote><p>使用默认<code>bridge</code>只能通过ip通信,这里加入了自定义网络,自定义网络可以自动解析容器名</p></blockquote>
<ul><li>docker network ls查看已有网络</li><li>创建自定义网络docker network create pub-network</li><li>手动连接网络docker network connect pub-network container_name_or_id</li><li>删除网络docker network rm network_name_or_idid</li></ul>
<p>2、创建文件夹</p>
<div class="jb51code"><pre class="brush:bash;">mkdir -p /opt/es/data
mkdir -p /opt/es/plugins
mkdir -p /opt/es/logs</pre></div>
<p>3、授权</p>
<div class="jb51code"><pre class="brush:bash;">chmod -R 777 /opt/es/data
chmod -R 777 /opt/es/logs</pre></div>
<p class="maodian"></p><h3>安装IK分词器</h3>
<p>由于ES对中文分词无法理解语义,需要IK插件<br />https://release.infinilabs.com/analysis-ik/stable/</p>
<p>Elasticsearch、Kibana、IK所有版本保持一致,解压后使用shell工具将整个文件夹上传到<code>/opt/es/plugins</code></p>
<p class="maodian"></p><h3>离线部署Elasticsearch、Kibana</h3>
<p>在能访问的地方拉取镜像</p>
<div class="jb51code"><pre class="brush:bash;">docker pull elasticsearch:8.15.2
docker pull kibana:8.15.2</pre></div>
<p>这里使用wsl,<code>wsl</code>进入wsl,然后进入win的D盘</p>
<div class="jb51code"><pre class="brush:bash;">cd /mnt/d</pre></div>
<p>打包镜像,这个文件可以在win D盘找到</p>
<div class="jb51code"><pre class="brush:bash;">docker save elasticsearch:8.15.2 &gt; elasticsearch.tar
docker save kibana:8.15.2 &gt; kibana.tar</pre></div>
<p>使用shell工具如Windterm上传文件</p>
<p class="maodian"></p><h3>加载镜像</h3>
<div class="jb51code"><pre class="brush:bash;">docker load -i elasticsearch.tar
docker load -i kibana.tar</pre></div>
<p>查看镜像</p>
<div class="jb51code"><pre class="brush:bash;">docker images</pre></div>
<p>然后命令部署或者docker-compose部署即可</p>
<p class="maodian"></p><h3>命令部署Elasticsearch、Kibana</h3>
<p>部署Elasticsearch</p>
<div class="jb51code"><pre class="brush:bash;">docker run -d \
--name es \
--network pub-network \
--restart always \
-p 9200:9200 \
-p 9300:9300 \
-e "xpack.security.enabled=false" \
-e "discovery.type=single-node" \
-e "http.cors.enabled=true" \
-e "http.cors.allow-origin:*" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-v /opt/es/data:/usr/share/elasticsearch/data \
-v /opt/es/plugins:/usr/share/elasticsearch/plugins \
-v /opt/es/logs:/usr/share/elasticsearch/logs \
--privileged=true \
elasticsearch:8.15.2</pre></div>
<p><code>xpack.security.enabled=false</code>禁用密码登录</p>
<p>如果要使用token: <code>-e &quot;xpack.security.enrollment.enabled=true&quot; \</code></p>
<p>docker部署一般用于开发,不要为难自己,使用token会有很多问题,生产环境再开,使用SSl需要证书</p>
<p class="maodian"></p><h3>部署Kibana</h3>
<div class="jb51code"><pre class="brush:bash;">docker run -d \
--name kibana \
--network pub-network \
--restart always \
-p 5601:5601 \
-e CSP_STRICT=false \
-e I18N_LOCALE=zh-CN \
kibana:8.15.2</pre></div>
<p>报错kibana 服务器尚未准备就绪,是因为配置了<code>ELASTICSEARCH_HOSTS</code></p>
<p>docker-compose部署Elasticsearch、Kibana</p>
<div class="jb51code"><pre class="brush:bash;">es:
    image: elasticsearch:8.15.2
    container_name: es
    network_mode: pub-network
    restart: always
    ports:
      # 9200:对外暴露的端口
      - 9200:9200
      # 9300:节点间通信端口
      - 9300:9300
    environment:
      # 禁用密码登录
      xpack.security.enabled: 'false'
      # 单节点运行
      discovery.type: single-node
      # 允许跨域
      http.cors.enabled: 'true'
      # 允许所有访问
      http.cors.allow-origin: '*'
      # 堆内存大小
      ES_JAVA_OPTS: '-Xms512m -Xmx512m'
    volumes:
      # 数据挂载
      - /opt/es/data:/usr/share/elasticsearch/data
      # 插件挂载
      - /opt/es/plugins:/usr/share/elasticsearch/plugins
      # 日志挂载
      - /opt/es/logs:/usr/share/elasticsearch/logs
    # 允许root用户运行
    privileged: true
kibana:
    image: kibana:8.15.2
    container_name: kibana
    network_mode: pub-network
    restart: always
    ports:
      - 5601:5601
    environment:
      # 禁用安全检查
      CSP_STRICT: 'false'
      # 设置中文
      I18N_LOCALE: zh-CN
networks:
pub-network:
    name: pub-network</pre></div>
<p>部署</p>
<div class="jb51code"><pre class="brush:bash;">docker-compose up -d</pre></div>
<p>删除Elasticsearch、Kibana</p>
<div class="jb51code"><pre class="brush:bash;">docker rm -f es
docker rm -f kibana</pre></div>
<p>开启安全配置(可选,如果要用密码和token)</p>
<p>es8开始需要密码访问,kibana通过token访问</p>
<div class="jb51code"><pre class="brush:bash;"># 生成密码
docker exec -it es /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
# 生成kibana访问token
docker exec -it es /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana</pre></div>
<p class="maodian"></p><h3>访问Elasticsearch、Kibana</h3>
<p>Elasticsearch:<code>127.0.0.1:9200</code>,看到以下界面就部署成功了</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292212.png" /></p>
<p>Kibana:<code>127.0.0.1:5601</code>看到以下界面就部署成功了</p>
<p>访问:<code>http://127.0.0.1:9200/.kibana</code>跨域查看有没有发现可视化工具kibana</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292213.png" /></p>
<p>我们选择手动配置,使用<code>http://es:9200</code>,我们没有配置ssl只能用http,容器名为<code>es</code></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292214.png" /></p>
<p>在终端运行命令查看日志中的验证码</p>
<div class="jb51code"><pre class="brush:bash;">docker logs kibana</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292215.png" /></p>
<p>使用</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292316.png" /></p>
<div class="jb51code"><pre class="brush:plain;">GET /_analyze
{
"analyzer": "ik_max_word",
"text": "好好学习天天向上"
}</pre></div>
<p>如果一个字为一个词条,就说明分词插件IK没装好,重新安装后重启容器<code>docker restart es</code></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292317.png" /></p>
<p class="maodian"></p><h2>分词原理</h2>
<p>依据字典进行分词</p>
<p>对于一些新词语,如铝合金键盘被称为&ldquo;铝坨坨&rdquo;,词典中没有这个词语,会将其逐字分词</p>
<p style="text-align:center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202411/2024110111292318.png" /></p>
<p class="maodian"></p><h2>分词流程</h2>
<ul><li>1、<code>character filters</code>:字符过滤器,进行原始处理,如转换编码、去停用词、转小写</li><li>2、<code>tokenizer</code>:分词器,将文本流进行分词为词条</li><li>3、<code>tokenizer filter</code>:将词条进行进一步处理,如同义词处理、拼音处理</li></ul>
<p class="maodian"></p><h3>扩展词库</h3>
<p>在IK插件<code>config/IKAnalyzer.cfg.xml</code>中添加</p>
<div class="jb51code"><pre class="brush:xml;">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
&lt;properties&gt;
        &lt;comment&gt;IK Analyzer 扩展配置&lt;/comment&gt;
        &lt;!--用户可以在这里配置自己的扩展字典 --&gt;
        &lt;entry key="ext_dict"&gt;ext.dic&lt;/entry&gt;
       &lt;!--用户可以在这里配置自己的扩展停止词字典--&gt;
        &lt;entry key="ext_stopwords"&gt;stopword.dic&lt;/entry&gt;
        &lt;!--用户可以在这里配置远程扩展字典 --&gt;
        &lt;!-- &lt;entry key="remote_ext_dict"&gt;words_location&lt;/entry&gt; --&gt;
        &lt;!--用户可以在这里配置远程扩展停止词字典--&gt;
        &lt;!-- &lt;entry key="remote_ext_stopwords"&gt;words_location&lt;/entry&gt; --&gt;
&lt;/properties&gt;</pre></div>
<p class="maodian"></p><h3>停用词库</h3>
<p>例如敏感词</p>
<div class="jb51code"><pre class="brush:xml;">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
&lt;properties&gt;
        &lt;comment&gt;IK Analyzer 扩展配置&lt;/comment&gt;
        &lt;!--用户可以在这里配置自己的扩展字典 --&gt;
        &lt;entry key="ext_stopwords"&gt;stopword.dic&lt;/entry&gt;
&lt;/properties&gt;</pre></div>
<p class="maodian"></p><h3>使用</h3>
<blockquote><p>生产使用可以用AI、ELP进行分词</p></blockquote>
<p>修改配置,添加扩展词库和停用词库</p>
<div class="jb51code"><pre class="brush:bash;">vim /opt/es/plugins/elasticsearch-analysis-ik-8.15.2/config/IKAnalyzer.cfg.xml</pre></div>
<p>这里新建一个词库</p>
<div class="jb51code"><pre class="brush:bash;">touch /opt/es/plugins/elasticsearch-analysis-ik-8.15.2/config/ext.dic</pre></div>
<p class="maodian"></p><h3>编辑扩展词库</h3>
<div class="jb51code"><pre class="brush:bash;">vim /opt/es/plugins/elasticsearch-analysis-ik-8.15.2/config/ext.dic</pre></div>
<p class="maodian"></p><h3>添加分词</h3>
<p><code>铝坨坨</code></p>
<p class="maodian"></p><h3>编辑停用词库</h3>
<div class="jb51code"><pre class="brush:bash;">vim /opt/es/plugins/elasticsearch-analysis-ik-8.15.2/config/stopword.dic</pre></div>
<p>添加</p>
<p><code>的</code></p>
<p>重启ES</p>
<div class="jb51code"><pre class="brush:bash;">docker restart es</pre></div>
<p class="maodian"></p><h3>测试分词</h3>
<div class="jb51code"><pre class="brush:bash;">GET /_analyze{"analyzer": "ik_max_word","text": "重重的铝坨坨"}</pre></div>
<p>可以看到扩展词库的&ldquo;铝坨坨&rdquo;被分词识别出来了,&ldquo;的&rdquo;没有被分词</p>
<p style="text-align:center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202411/2024110111292319.png" /></p>
<p class="maodian"></p><h2>分词作用</h2>
<ul><li>创建倒排索引时对文档分词</li><li>用户搜索时对输入的内容分词</li></ul>
<p class="maodian"></p><h2>IK分词模式</h2>
<ul><li>ik_smart:智能切分,粗粒度</li><li>ik_max_word:最细切分,细粒度</li></ul>
<p class="maodian"></p><h2>DSL 索引操作</h2>
<ul><li>仅允许GET, PUT, DELETE, HEAD</li><li>mapping:对索引库中文档的约束,常见的属性有<ul><li>type:字段数据类型<ul><li>字符串:text(可分词的文本)、keyword(不分词的精确值,合在一起有意义的词,如国家、品牌)</li><li>数值:long、integer、short、byte、double、float</li><li>布尔:boolean</li><li>日期:date</li><li>对象:object</li></ul></li><li>index:是否创建倒排索引,默认true</li><li>analyzer:使用哪种分词器</li><li>properties:字段的子字段</li></ul></li></ul>
<p>添加索引库,每次写入操作版本都会+1,如添加(POST)、更新(PUT)</p>
<blockquote><p>索引库mgr</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">PUT /mgr
{
"mappings": {
    "properties": {
      "info": {
      "type": "text",
      "analyzer": "ik_smart"
      },
      "email": {
      "type": "keyword",
      "index": false
      },
      "name": {
      "type": "object",
      "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
      }
      }
    }
}
}</pre></div>
<p class="maodian"></p><h3>查询索引库</h3>
<div class="jb51code"><pre class="brush:bash;">GET /mgr</pre></div>
<p>更新索引库(索引库<strong>禁止修改</strong>,因为索引库建立倒排索引后无法修改,只能<strong>添加</strong>新字段)</p>
<div class="jb51code"><pre class="brush:plain;">PUT /mgr/_mapping
{
"properties":{
    "age":{
      "type":"integer"
    }
}
}</pre></div>
<p class="maodian"></p><h3>删除索引库</h3>
<div class="jb51code"><pre class="brush:bash;">DELETE /mgr</pre></div>
<p class="maodian"></p><h2>DSL文档操作</h2>
<p class="maodian"></p><p class="maodian"></p><h3>添加文档</h3>
<blockquote><p>索引库mgr/文档/文档id</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">POST /mgr/_doc/1
{
"info": "铝坨坨键盘",
"email": "11111@gmail.com",
"name": {
    "firstName": "C",
    "lastName": "I"
}
}</pre></div>
<p class="maodian"></p><h3>查询文档</h3>
<div class="jb51code"><pre class="brush:bash;">GET /mgr/_doc/1</pre></div>
<p class="maodian"></p><h3>更新文档</h3>
<p>全量更新,删除旧文档,添加新文档</p>
<blockquote><p>如果文档id不存在则与添加文档功能相同</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">PUT /mgr/_doc/1
{
"info": "铝坨坨键盘",
"email": "222@gmail.com",
"name": {
    "firstName": "C",
    "lastName": "I"
}
}</pre></div>
<p>增量更新(局部更新)</p>
<blockquote><p>指定<code>_update</code>,指定文档<code>doc</code></p></blockquote>
<div class="jb51code"><pre class="brush:plain;">POST /mgr/_update/1{"doc": {    "email": "333@gmail.com"}}</pre></div>
<p class="maodian"></p><h3>删除文档</h3>
<div class="jb51code"><pre class="brush:plain;">DELETE /mgr/_doc/1</pre></div>
<p>Rust客户端操作Elasticsearch</p>
<p>添加Cargo.toml</p>
<div class="jb51code"><pre class="brush:plain;">elasticsearch = "8.15.0-alpha.1"
# 序列化和反序列化数据
serde = { version = "1.0.127", features = ["derive"] }
# 序列化JSON
serde_json = "1.0.128"
tokio = { version = "1", features = ["full"] }
# 异步锁
once_cell = "1.20.2"</pre></div>
<p>添加环境变量<code>.env</code></p>
<div class="jb51code"><pre class="brush:plain;"># 指定当前配置文件
RUN_MODE=development</pre></div>
<p>添加配置<code>settings\development.toml</code></p>
<div class="jb51code"><pre class="brush:plain;">debug = true
# 指定开发环境配置
profile = "development"

host = "127.0.0.1"</pre></div>
<p>获取配置<code>config\es.rs</code></p>
<div class="jb51code"><pre class="brush:plain;">use serde::Deserialize;
#
pub struct EsConfig {
    host: String,
    port: u16,
}
impl EsConfig {
    // 获取redis连接地址
    pub fn get_url(&amp;self) -&gt; String {
      format!("http://{host}:{port}", host = self.host, port = self.port)
    }
}</pre></div>
<p>将配置存放到AppConfig</p>
<div class="jb51code"><pre class="brush:plain;">#
pub struct AppConfig {
    pub es:EsConfig,
}
impl AppConfig {
    pub fn read(env_src: Environment) -&gt; Result&lt;Self, config::ConfigError&gt; {
      // 获取配置文件目录
      let config_dir = get_settings_dir()?;
      info!("config_dir: {:#?}", config_dir);
      // 获取配置文件环境
      let run_mode = std::env::var("RUN_MODE")
            .map(|env| Profile::from_str(&amp;env).map_err(|e| ConfigError::Message(e.to_string())))
            .unwrap_or_else(|_e| Ok(Profile::Dev))?;
      // 当前配置文件名
      let profile_filename = format!("{run_mode}.toml");
      // 获取配置
      let config = config::Config::builder()
            // 添加默认配置
            .add_source(config::File::from(config_dir.join("default.toml")))
            // 添加自定义前缀配置
            .add_source(config::File::from(config_dir.join(profile_filename)))
            // 添加环境变量
            .add_source(env_src)
            .build()?;
      info!("Successfully read config profile: {run_mode}.");
      // 反序列化
      config.try_deserialize()
    }
}
// 获取配置文件目录
pub fn get_settings_dir() -&gt; Result&lt;std::path::PathBuf, ConfigError&gt; {
    Ok(get_project_root()
      .map_err(|e| ConfigError::Message(e.to_string()))?
      .join("settings"))
}
#
mod tests {
    use crate::config::profile::Profile;
    use self::env::get_env_source;
    pub use super::*;
    #
    pub fn test_profile_to_string() {
      // 设置dev模式
      let profile: Profile = Profile::try_from("development").unwrap();
      println!("profile: {:#?}", profile);
      assert_eq!(profile, Profile::Dev)
    }
    #
    pub fn test_read_app_config_prefix() {
      // 读取配置
      let config = AppConfig::read(get_env_source("APP")).unwrap();
      println!("config: {:#?}", config);
    }
}</pre></div>
<p>将配置存放到全局<code>constant\mod.rs</code></p>
<div class="jb51code"><pre class="brush:plain;">// 环境变量前缀
pub const ENV_PREFIX: &amp;str = "APP";
// 配置
pub static CONFIG: Lazy&lt;crate::config::AppConfig&gt; = Lazy::new(||
    crate::config::AppConfig::read(get_env_source(ENV_PREFIX)).unwrap()
);</pre></div>
<p>加载配置文件<code>client\builder.rs</code></p>
<div class="jb51code"><pre class="brush:plain;">use crate::config::AppConfig;
// 传输配置文件到客户端
pub trait ClientBuilder: Sized {
    fn build_from_config(config: &amp;AppConfig) -&gt; Result&lt;Self,InfraError&gt;;
}</pre></div>
<p>Es客户端<code>client\es.rs</code></p>
<blockquote><p>InfraError为自定义错误,请修改为你想要的错误,如标准库错误</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">// 类型别名
pub type EsClient = Arc&lt;Elasticsearch&gt;;
// 加载配置文件
pub trait EsClientExt: Sized {
    fn build_from_config(config: &amp;AppConfig) -&gt; impl Future&lt;Output = Result&lt;Self, InfraError&gt;&gt;;
}
impl EsClientExt for EsClient {
    async fn build_from_config(config: &amp;AppConfig) -&gt; Result&lt;Self, InfraError&gt; {
      // 1、使用single_node方式创建client
      // let transport = Transport::single_node(&amp;config.es.get_url()).unwrap();
      // let client = Elasticsearch::new(transport);
      // Ok(Arc::new(client))
      // 2、使用builder方式创建client,可以添加多个url
      let url = config.es.get_url();
      let url_parsed = url
            .parse::&lt;elasticsearch::http::Url&gt;()
            .map_err(|_| InfraError::OtherError("url err".to_string()))?;
      let conn_pool = SingleNodeConnectionPool::new(url_parsed);
      let transport = TransportBuilder::new(conn_pool)
            .disable_proxy()
            .build()
            .map_err(|_| InfraError::OtherError("transport err".to_string()))?;
      let client = Elasticsearch::new(transport);
      Ok(Arc::new(client))
    }
}</pre></div>
<p>测试<code>client\es.rs</code>,所有请求在<code>body()</code>中定义DSL语句,通过<code>send()</code>发送</p>
<div class="jb51code"><pre class="brush:plain;">#
mod tests {
    use elasticsearch::{ cat::CatIndicesParts, DeleteParts, IndexParts, UpdateParts };
    use serde_json::json;
    use super::*;
    use crate::constant::CONFIG;
    #
    async fn test_add_document() {
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      let response = client
            .index(IndexParts::IndexId("mgr", "1"))
            .body(
                json!({
                "id": 1,
                "user": "cci",
                "post_date": "2024-01-15T00:00:00Z",
                "message": "Trying out Elasticsearch, so far so good?"
            })
            )
            .send().await;
      assert!(response.is_ok());
      let response = response.unwrap();
      assert!(response.status_code().is_success());
    }
    #
    async fn test_get_indices() {
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      let get_index_response = client
            .cat()
            .indices(CatIndicesParts::Index(&amp;["*"]))
            .send().await;
      assert!(get_index_response.is_ok());
    }
    #
    async fn test_update_document() {
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      let update_response = client
            .update(UpdateParts::IndexId("mgr", "1"))
            .body(
                json!({
                "doc": {
                  "message": "Updated message"
                }
            })
            )
            .send().await;
      assert!(update_response.is_ok());
      let update_response = update_response.unwrap();
      assert!(update_response.status_code().is_success());
    }
    #
    async fn test_delete_document() {
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      let delete_response = client.delete(DeleteParts::IndexId("mgr", "1")).send().await;
      assert!(delete_response.is_ok());
      let delete_response = delete_response.unwrap();
      assert!(delete_response.status_code().is_success());
    }
}</pre></div>
<p class="maodian"></p><h2>使用流程</h2>
<div class="jb51code"><pre class="brush:plain;">      // 1、创建client
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      // 2、定义DSL语句
      let mut body: Vec&lt;JsonBody&lt;_&gt;&gt; = Vec::with_capacity(4);
      // 添加文档
      body.push(json!({"index": {"_id": "1"}}).into());
      body.push(
            json!({
    "id": 1,
    "user": "kimchy",
    "post_date": "2009-11-15T00:00:00Z",
    "message": "Trying out Elasticsearch, so far so good?"
}).into()
      );
      // 添加文档
      body.push(json!({"index": {"_id": "2"}}).into());
      body.push(
            json!({
    "id": 2,
    "user": "forloop",
    "post_date": "2020-01-08T00:00:00Z",
    "message": "Bulk indexing with the rust client, yeah!"
}).into()
      );
      // 3、发送请求
      let response = client.bulk(BulkParts::Index("mgr")).body(body).send().await.unwrap();</pre></div>
<p>项目地址:https://github.com/VCCICCV/MGR</p>
<p class="maodian"></p><h2>分析数据结构</h2>
<p><code>mapping</code>要考虑的问题:字段名、数据类型、是否参与搜索(建立倒排索引<code>&quot;index&quot;:false</code>,默认true)、是否分词(参与搜索的字段,text分词,keyword、数据类型不分词)、分词器</p>
<ul><li>地理坐标:<ul><li>geo_point:由经度(longitude)和纬度(latitude)确定的一个点,如<code>[ 13.400544, 52.530286 ]</code></li><li>geo_shape:由多个<code>geo_point</code>组成的几何图形,如一条线<code>[, ]</code></li></ul></li><li><code>copy_to</code>:将多个字段组合为一个字段进行索引 Rust客户端操作索引库</li></ul>
<blockquote><p>生产环境不要使用<code>unwrap()</code></p></blockquote>
<p>这里演示在请求正文中操作,使用<code>send()</code></p>
<p><code>Transport</code>支持的方法<code>Method</code>:</p>
<ul><li><code>Get</code>:获取资源<code>Put</code>:创建或更新资源(全量更新)</li><li><code>Post</code>:创建或更新资源(部分更新)</li><li><code>Delete</code>:删除资源</li><li><code>Head</code>:获取头信息</li></ul>
<p><code>send()</code>请求正文需要包含的参数:</p>
<ul><li><code>method</code>:必须</li><li><code>path</code>:必须</li><li><code>headers</code>:必须</li><li><code>query_string</code>:可选</li><li><code>body</code>:可选</li><li><code>timeout</code>:可选</li></ul>
<p class="maodian"></p><h2>添加索引库</h2>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_create_index() {
      // 1、创建client
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      // 2、定义DSL语句
      let index_name = "mgr";
      let index_definition =
            json!({
            "mappings":{
                "properties":{
                  "age":{
                        "type":"integer"
                  }
                }
            }
      });
      let body = Some(serde_json::to_vec(&amp;index_definition).unwrap());
      let path = format!("/{}", index_name);
      let headers = HeaderMap::new();
      let query_string = None;
      let timeout = None;
      let method = Method::Put;
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            method,
            &amp;path,
            headers,
            query_string,
            body,
            timeout
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>你也可以将其简化</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_create_index() {
      // 1、创建client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、定义DSL
      let index_definition =
            json!({
            "mappings":{
                "properties":{
                  "age":{
                        "type":"integer"
                  }
                }
            }
      });
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Put,
            format!("/mgr").as_str(),
            HeaderMap::new(),
            None,
            Some(index_definition.to_string().as_bytes().to_vec()),
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p class="maodian"></p><h2>查询索引库是否存在</h2>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_query_index() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、定义查询 DSL 语句
      let query = json!({
      "query": {
            "match_all": {}
      }
    });
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Get,
            format!("/mgr/_search").as_str(),
            HeaderMap::new(),
            None,
            Some(query.to_string().as_bytes().to_vec()),
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>也可以不定义DSL查询</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_query_index2() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Get,
            format!("/mgr").as_str(),
            HeaderMap::new(),
            None,
            None,
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>更新索引库</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_update_index() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、定义查询 DSL 语句
      let update_content = json!({
            "properties":{
                "age":{
                "type":"integer"
                }
            }
    });
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Put,
            format!("/mgr/_mapping").as_str(),
            HeaderMap::new(),
            None,
            Some(update_content.to_string().as_bytes().to_vec()),
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>删除索引库</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_delete_index() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、发送请求
      let response = client.send::&lt;(), ()&gt;(
            Method::Delete,
            format!("/mgr").as_str(),
            HeaderMap::new(),
            None,
            None,
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p class="maodian"></p><h2>Rust客户端操作文档</h2>
<p>添加文档</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_create_doc() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、定义查询 DSL 语句
      let doc_content =
            json!({
            "id": "1",
            "user": "kimchy",
            "post_date": "2009-11-15T00:00:00Z",
            "message": "Trying out Elasticsearch, so far so good?"
      });
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Post,
            format!("/mgr/_doc/1").as_str(),
            HeaderMap::new(),
            None,
            Some(doc_content.to_string().as_bytes().to_vec()),
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>查询文档是否存在</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_get_doc() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Get,
            format!("/mgr/_doc/1").as_str(),
            HeaderMap::new(),
            None,
            None,
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>更新文档</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_update_doc() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、定义查询 DSL 语句
      let doc_content =
            json!({
            "doc": {
                "message": "Updated message"
            }
      });
      // 3、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Post,
            format!("/mgr/_update/1").as_str(),
            HeaderMap::new(),
            None,
            Some(doc_content.to_string().as_bytes().to_vec()),
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>删除文档</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_delete_doc() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2、发送请求
      let response = client.send::&lt;Vec&lt;u8&gt;, ()&gt;(
            Method::Delete,
            format!("/mgr/_doc/1").as_str(),
            HeaderMap::new(),
            None,
            None,
            None
      ).await;
      assert!(response.is_ok());
      let response = response.unwrap();
      println!("{:?}", response);
      assert_eq!(response.status_code().is_success(), true);
    }</pre></div>
<p>批量添加文档</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_bulk_add_to_mgr() {
      // 1、创建client
      let client_result = EsClient::build_from_config(&amp;CONFIG).await;
      assert!(client_result.is_ok());
      let client = client_result.unwrap();
      // 2、定义DSL语句
      let mut body: Vec&lt;JsonBody&lt;_&gt;&gt; = Vec::with_capacity(4);
      // 添加第一个操作和文档
      body.push(json!({"index": {"_id": "1"}}).into());
      body.push(
            json!({
    "id": 1,
    "user": "kimchy",
    "post_date": "2009-11-15T00:00:00Z",
    "message": "Trying out Elasticsearch, so far so good?"
}).into()
      );
      // 添加第二个操作和文档
      body.push(json!({"index": {"_id": "2"}}).into());
      body.push(
            json!({
    "id": 2,
    "user": "forloop",
    "post_date": "2020-01-08T00:00:00Z",
    "message": "Bulk indexing with the rust client, yeah!"
}).into()
      );
      // 3、发送请求
      let response = client.bulk(BulkParts::Index("mgr")).body(body).send().await.unwrap();
      assert!(response.status_code().is_success());
    }</pre></div>
<p class="maodian"></p><h2>Rust客户端操作搜索</h2>
<p>这里演示在请求体<code>body</code>中进行API调用</p>
<ul><li>查询所有:查出所有数据</li><li>全文检索查询(full text):利用分词器对内容分词,从倒排索引库中查询<ul><li>match_query</li><li>multi_match_query</li></ul></li><li>精确查询:根据精确值查询,如integer、keyword、日期<ul><li>id</li><li>range:根据值的范围查询</li><li>term:根据词条精确值查询</li></ul></li><li>地理坐标查询(geo):根据经纬度查询<ul><li>geo_distance:查询geo_point指定距离范围内的所有文档</li><li>geo_bounding_box:查询geo_point值落在某个矩形范围内的所有文档</li></ul></li><li>复合查询(compound):将上述条件组合起来</li></ul>
<p>查询所有</p>
<blockquote><p>默认10条</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_search_match_all() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "match_all": {
                }
            }
      })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p>等价于</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match_all": {}
}
}</pre></div>
<p class="maodian"></p><h2>全文搜索</h2>
<p><code>message</code>为文档中的字段</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_search_match() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "match": {
                  "message": "good"
                }
            }
      })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p>相当于</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match": {
      "message": "good"
    }
}
}</pre></div>
<p class="maodian"></p><h2>多字段查询</h2>
<p>多字段查询效率低,一般在创建时使用<code>copy_to</code>到一个字段中</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_search_multi_match() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "multi_match": {
                  "query": "good",
                  "fields": [
                        "message",
                        "user"
                        ]
                  }
                }
            })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p>相当于</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "multi_match": {
      "query": "good",
      "fields": [
      "message",
      "user"
      ]
    }
}
}</pre></div>
<p class="maodian"></p><h2>根据范围查询(range)</h2>
<p><code>gte</code>大于等于,<code>lte</code>小于等于;<code>gt</code>大于<code>lt</code>小于</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_search_range() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "range": {
                  "id": {
                        "gte": 1,
                        "lte": 1
                        }
                  }
                }
            })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p>相当于</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "range": {
      "id": {
      "gte": 1,
      "lte": 1
      }
    }
}
}</pre></div>
<p class="maodian"></p><h2>根据词条精确查询(term)</h2>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_search_term() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "term": {
                  "user": "kimchy"
                  }
                }
            })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p>相当于</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "term": {
      "user": "kimchy"
    }
}
}</pre></div>
<p class="maodian"></p><h2>根据地理距离查询</h2>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "geo_distance": {
      "distance": "100km",
      "location": "31.04, 45.12"
    }
}
}</pre></div>
<p class="maodian"></p><h2>根据指定矩形范围查询</h2>
<blockquote><p>左上经纬度与右下经纬度<br /><code>geo</code>为文档中的字段</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "geo_bounding_box": {
      "geo": {
      "top_left": {
          "lon": 124.45,
          "lat": 32.11
      },
      "bottom_right": {
          "lon": 125.12,
          "lat": 30.21
      }
      }
    }
}
}</pre></div>
<p class="maodian"></p><h2>复合查询</h2>
<p>查询时文档会对搜索词条的关联度打分<code>_score</code>,返回结果时按照降序排列</p>
<p class="maodian"></p><h2>关联度计算方法</h2>
<ul><li>TF-IDF算法(ES5.0之前)</li></ul>
<p>TF(词条频率)= 词条出现次数/文档中词条总数</p>
<p>IDF(逆文档频率)=log(文档总数/包含词条的文档总数)</p>
<p>score = &sum;(𝑖=1,𝑛)(TF*IDF):将词条频率与逆文档频率相乘再求和</p>
<ul><li>BM25算法(ES5.0之后)</li></ul>
<p>默认采用BM25算法:考虑了TF、IDF、文档长度等因素,能够平衡长短文的关联度</p>
<p style="text-align:center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202411/2024110111292320.png" /></p>
<p><code>function_score</code>修改关联度</p>
<blockquote><p>指定文档和算分函数</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "function_score": {
      "query": {
      "match": {// 查询方法
      "message": "good"
      }
      },
      "functions": [ // 算分函数
      {
          "filter": {// 只有符合过滤条件的才被计算
          "term": {// 根据词条精确查询
          "id": 1
          }
          },
          "weight": 3 // 指定加权函数
      }
      ],
      // 加权模式:相乘
      "boost_mode": "multiply"
    }
}
}</pre></div>
<ul><li><code>weight</code>:给定常量值,还可以指定以下值</li><li><code>field_value_factor</code>:用文档中的指定字段值作为函数结果</li><li><code>random_score</code>:随机生成一个值</li><li><code>script_score</code>:自定义计算公式</li><li><code>boost_mode</code>:加权模式,<code>multiply</code>与原来的<code>_score</code>相乘,还可以配置:</li><li><code>replace</code>:替换原来的<code>_score</code></li><li><code>sum</code>:求和</li><li><code>avg</code>:取平均值</li><li><code>min</code>:取最小值</li><li><code>max</code>:取最大值</li></ul>
<p>相当于</p>
<div class="jb51code"><pre class="brush:plain;">    #
    async fn test_function_score_query() {
      // 1、创建 client
      let client = EsClient::build_from_config(&amp;CONFIG).await.unwrap();
      // 2. 执行搜索
      let response = client
            .search(SearchParts::Index(&amp;["mgr"]))
            .from(0)
            .size(5)
            .body(
                json!({
            "query": {
                "function_score": {
                  "query": {
                        "match": {// 查询方法
                        "message": "good"
                        }
                  },
                  "functions": [ // 算分函数
                  {
                        "filter": {// 只有符合过滤条件的才被计算
                        "term": {// 根据词条精确查询
                        "id": 1
                        }
                        },
                        "weight": 3 // 指定加权函数
                  }
                ],
                  // 加权模式:相乘
                  "boost_mode": "multiply"
                  }
                }
            })
            )
            .send().await
            .unwrap();
      // 3. 解析响应
      let response_body = response.json::&lt;Value&gt;().await.unwrap();
      // 搜索耗时
      let took = response_body["took"].as_i64().unwrap();
      println!("took: {}ms", took);
      // 搜索结果
      for hit in response_body["hits"]["hits"].as_array().unwrap() {
            println!("{:?}", hit["_source"]);
      }
    }</pre></div>
<p class="maodian"></p><h2>boolean query 布尔查询</h2>
<p>布尔查询是一个或多个子句查询的组合,组合方式有</p>
<ul><li><code>must</code>:必须匹配每个子查询,类似于&ldquo;与&rdquo;</li><li><code>should</code>:选择性匹配子查询,类似于&ldquo;或&rdquo;</li><li><code>must_not</code>:必须不匹配,不参与算分,类似于&ldquo;非&rdquo;</li><li><code>filter</code>:必须匹配,</li></ul>
<p>查询message中包含rust,post_date不小于2020年1月1日的文档</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "bool": {
      "must": [
      {
          "match_phrase": {
            "message": "rust"
          }
      }
      ],
      "must_not": [
      {
          "range": {
            "post_date": {
            "lt": "2020-01-01T00:00:00Z"
            }
          }
      }
      ]
    }
}
}</pre></div>
<p class="maodian"></p><h2>搜索结果处理</h2>
<p>排序</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match_all": {}
},
"sort": [
    {
      "id": "desc"// ASC升序,DESC降序
    }
]
}</pre></div>
<p>地理位置排序</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match_all": {}
},
"sort": [
    {
      "_geo_distance":{
      "FIELD": {
          "lat": 40,// 纬度
          "lon": -70// 经度
      },
      "order":"asc",// 排序方式
      "unit":"km" // 单位
      }
    }
]
}</pre></div>
<p>分页</p>
<p>1、<code>from+size</code>分页查询(默认10条数据)</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match_all": {}
},
"from":1,// 分页开始位置
"size":10,// 期望获取的文档总数
"sort": [
    {
      "id": "desc"// ASC升序,DESC降序
    }
    ]
}</pre></div>
<p>深度分页问题:一般将ES作为分布式部署,当需要<code>&quot;from&quot;=990,&quot;size&quot;=10</code>查数据时:<br />1、先从每个数据分片上查询前<code>1000</code>条数据<br />2、将所有节点的结果聚合,在内存中重新排序选出前<code>1000</code>条文档<br />3、在这1000条文档中选取<code>&quot;from&quot;=990,&quot;size&quot;=10</code>的数据</p>
<blockquote><p>如果搜索页数过深,或者结果集(from+size)越大,对内存和CPU的消耗越高,因此ES设定的查询上限是<code>10000</code></p></blockquote>
<p>深度分页解决方案:</p>
<p>2、<code>search after</code>分页查询:分页时排序,从上一次的排序值开始查询下一页文档(只能向后查询)</p>
<p>3、<code>scroll</code>分页查询:将排序数据形成快照,保存在内存中(内存消耗大,官方不推荐)</p>
<p class="maodian"></p><h2>高亮处理</h2>
<p>搜索键盘时关键字高亮</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202411/2024110111292321.png" /></p>
<p><code>highlight</code>指定高亮字段</p>
<blockquote><p>默认搜索字段和高亮字段匹配才高亮</p></blockquote>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"query": {
    "match": {
      "message":"rust"// 搜索message中包含rust的文档
    }
},
"highlight":{
    "fields":{
      "message":{// 指定高亮字段
      "require_field_match":"false"// 搜索字段和高亮字段可以不匹配
      }
    }
}
}</pre></div>
<p class="maodian"></p><h2>数据聚合</h2>
<p>聚合(aggregations)可以实现对文档数据的统计、分析、运算,聚合分类:</p>
<ul><li>桶(Buket):用来对数据分组<ul><li>https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html</li><li>TermAggregation:按文档字段或词条值分组</li><li>Date Histogram:按日期阶梯分组,如一周为一组</li></ul></li><li>度量(Metric):用于计算一些值,如最大值、最小值、平均值<ul><li>https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html</li><li>Avg:求平均值</li><li>Max:求最大值</li><li>Min:求最小值</li><li>Sum:求和</li><li>Stats:同时求Max、Min、Avg、Sum等</li></ul></li><li>管道(pipeline):以其他聚合的结果作为聚合的基础<ul><li>https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html</li></ul></li></ul>
<p class="maodian"></p><h3>桶(Buket)</h3>
<p>Buket默认统计其中的文档数量<code>_count</code>,并且按照降序排序</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"size":0,// 文档大小,结果不包含文档,只包含聚合结果
"aggs": {//指定聚合
    "idAgg": {// 聚合名
      "terms": {// 精确查询
      "field":"id",// 指定字段
      "order":{
          "_count":"asc"// 按升序排序
      }
      }
    }
}
}</pre></div>
<p>度量(Metric)</p>
<div class="jb51code"><pre class="brush:plain;">GET /mgr/_search
{
"size":0,// 文档大小,结果不包含文档,只包含聚合结果
"aggs": {//指定聚合
    "idAgg": {// 聚合名
      "terms": {// 精确查询
      "field":"id",// 指定字段
      "size":20
      },
      "aggs":{// 子聚合
      "score_stats":{// 聚合名
          "max":{//聚合类型,min、max、avg等
            "field":"score"// 聚合字段
          }
      }
      }
    }
}
}</pre></div>
<p class="maodian"></p><h2>自动补全</h2>
<p class="maodian"></p><h3>拼音补全</h3>
<p>如果你想要通过拼音补全,请下载解压拼音分词器上传到<code>/opt/es/plugins</code>目录然后重启es<br />https://github.com/infinilabs/analysis-pinyin/releases</p>
<ul><li>补全字段必须是<code>completion</code>类型</li><li>拼音分词需要自定义分词器</li></ul>
<p><strong>进行拼音分词</strong>:创建索引并设置字段类型为<code>completion</code>,同时指定先分词再根据词条过滤(如果不自定义分词器,默认将每个汉字单独分为拼音,所以先分词词条再进行拼音处理),其他设置见github仓库</p>
<div class="jb51code"><pre class="brush:plain;">PUT /test
{
"settings": {// 设置
    "analysis": {
      "analyzer": {// 设置分词器
      "my_analyzer": {// 分词器名
          "filters": [
         "lowercase",// 转小写
         "stop"// 去停用词
         ],
          "tokenizer": "ik_max_word", // 分词器
          "filter": "py" // 过滤时进行拼音
      }
      }
    },
    "filter": { // 自定义tokenizer filter
      "py": { // 过滤器名称
      "type": "pinyin", // 过滤器类型,这里是pinyin
      "keep_full_pinyin": false,// 是否保留完整的拼音形式
      "keep_joined_full_pinyin": true,// 是否保留连接起来的完整拼音形式
      "keep_original": true,// 是否保留原始的文本内容
      "limit_first_letter_length": 16,// 限制拼音首字母的长度为 16
      "remove_duplicated_term": true,// 是否移除重复的词条
      "none_chinese_pinyin_tokenize": false// 不对非中文字符进行拼音分词
      }
    }
},
"mappings": {
    "properties": {
      "user": {
      "type": "completion"
      }
    }
}
}</pre></div>
<p><strong>不进行拼音分词</strong>:创建索引并设置字段类型为<code>completion</code></p>
<div class="jb51code"><pre class="brush:plain;">PUT /test
{
"mappings": {
    "properties": {
      "user": {
      "type": "completion"
      }
    }
}
}</pre></div>
<h3>添加文档</h3>
<div class="jb51code"><pre class="brush:plain;">POST /test/_doc/1
{
"id": 1,
"message": "Trying out Elasticsearch, so far so good?",
"post_date": "2009-11-15T00:00:00Z",
"user": "kimchy"
}</pre></div>
<p class="maodian"></p><h3>根据关键字查询补全</h3>
<div class="jb51code"><pre class="brush:plain;">GET /test/_search
{
"suggest": {
    "YOUR_SUGGESTION": {// 指定自动补全查询名字
    "text": "k",// 关键字前缀
    "completion": {// 自动补全类型
      "field": "user",// 补全字段
      "skip_duplicates": true,// 是否跳过重复的建议
      "size": 10 // 获取前10条结果
    }
    }
}
}</pre></div>
<p>所有代码地址:https://github.com/VCCICCV/MGR/blob/main/auth/infrastructure/src/client/es.rs</p>
<p>到此这篇关于Rust整合Elasticsearch的详细过程(收藏)的文章就介绍到这了,更多相关Rust整合Elasticsearch内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Rust 中 Deref Coercion讲解</li><li>Rust之智能指针的用法</li><li>Rust&nbsp;智能指针实现方法</li><li>rust智能指针的具体使用</li><li>详解rust 自动化测试、迭代器与闭包、智能指针、无畏并发</li><li>解读Rust的Rc&lt;T&gt;:实现多所有权的智能指针方式</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Rust整合Elasticsearch的详细过程(收藏)