微服务-Elasticsearch01

一、初始elasticsearch

Elasticsearch的官方网站:https://www.elastic.co/cn/elasticsearch/

1.1 认识和安装

Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分,完整的技术栈包含:

  • Elasticsearch:用于数据存储、计算和搜索;
  • logstash/Beats:用于数据收集;
  • Kibana:用于数据可视化;

整套技术栈被称为ELK,经常用来做日志收集、系统监控和状态分析等;

整套技术栈的核心就是用来存储、搜索、计算的Elasticsearch。
Kibana是elastic公司提供是用于操作Elasticsearch的可视化控制台,其功能包括:

  1. 对Elasticsearch数据的搜索、展示
  2. 对Elasticsearch数据的统计、聚合,并形成图形报表、图形
  3. 对Elasticsearch的集群状态监控
  4. 提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示

1.1.1 安装elasticsearch

方式一:直接通过Docker命令安装单机版本的elasticsearch:

shell 复制代码
docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1

方式二:通过直接导入镜像tar包

  1. 将镜像tar包复制到虚拟机目录下
  2. 执行docker命令
shell 复制代码
# 拉取镜像
docker load -i /home/yueyue/es.tar

# 查看镜像是否拉取成功
docker images

# 启动容器
 docker run -d \
   --name es \
   -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
   -e "discovery.type=single-node" \
   -v es-data:/usr/share/elasticsearch/data \
   -v es-plugins:/usr/share/elasticsearch/plugins \
   --privileged \
   --network yue-net \
   -p 9200:9200 \
   -p 9300:9300 \
   elasticsearch:7.12.1

# 查看容器是否启动成功
docker ps
  1. 安装成功后,访问:http://192.168.19.128:9200/,即可看到响应的Elasticsearch服务的基本信息:

1.1.2 安装Kibana

方式一:直接通过Docker命令,即可部署Kiana

shell 复制代码
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1

方式二:通过导入镜像kibana.tar

  1. 复制tar包到虚拟机

  2. 执行docker命令

shell 复制代码
# 拉取镜像
docker load -i /home/yueyue/kiana.tar

# 查看镜像是否拉取成功
docker images

# 启动容器
 docker run -d \
 --name kibana \
 -e ELASTICSEARCH_HOSTS=http://es:9200 \
 --network=yue-net \
 -p 5601:5601  \
 kibana:7.12.1


# 查看容器是否启动成功
docker ps
  1. 安装成功,访问http://192.168.19.128:5601/app/home#/,即可看到控制台页面:

选择Explore on my own之后,进入主页面:

然后选中Dev tools,进入开发工具页面:

1.2 倒排索引

倒排索引的概念是基于MySQL这样的正向索引而言的。

1.2.1 正向索引

使用名为tb_goods表讲解一下正向索引:

其中的id字段已经创建了索引,由于索引底层采用了B+树结构,因此根据id搜索的速度会非常快,但其他字段(如title),只在叶子节点上存在;

要根据title搜索时,只能遍历树中的每个叶子节点,判断title数据是否复合要求,sql语句:

sql 复制代码
select * from tb_goods where title like '%手机%';

搜索的大概流程:

说明:

  1. 检查到搜索条件为like '%手机%',需要找到title中包含手机的数据;
  2. 逐条遍历每行数据(每个叶子节点),比如第1次拿到id为1的数据;
  3. 判断数据中的title字段值是否符合条件;
  4. 如果符合则放入结果集,不符合则丢弃;
  5. 回到步骤1;

总结,根据id精确匹配时,可以走索引,查询效率高,而搜索条件为模糊匹配时,由于索无法生效,导致从索引查询退化为全表扫描,效率很差。
正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配,而倒排索引恰好可以解决根据部分词条模糊匹配问题。

1.2.2 倒排索引

倒排索引有两个重要概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档,例如一个网页、一个商品信息;
  • 词条(Term):对文档(每一条数据)数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条,例如我是中国人,就可分为我、是、中国人、中国、国人这样的几个词条;

创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:

  1. 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
  2. 创建表,每行数据包括词条、词条所在文档id、位置等信息;
  3. 因为词条唯一性,可以给词条创建正向索引;

此时形成的这张以词条为索引的表,就是倒排索引表,两者对比:
正向索引:

倒排索引:

倒排索引的搜索流程 (以搜索"华为手机"为例):

流程描述:

  1. 用户输入条件"华为手机"进行搜索;
  2. 对用户输入条件分词,得到词条:华为、手机;
  3. 拿着词条在倒排索引中查找(由于词条有索引、查询效率很高),即可得到包含词条的文档id:1、2、3;
  4. 拿着文档id正向索引中查询具体文档即可(由于id也有索引,查询效率也很高);

倒排索引+正向索引:

倒排索引负责快速定位"哪些文档包含关键词"(空间换时间)

正向索引负责快速获取"文档的完整内容"(通常用B+树)

1.2.3 正向和倒排

  • 正向索引是最传统的,根据id索引的方式,但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引,与正向索引相反,先找到用户搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档,是根据词条找文档的过程

正向索引:

  • 优点:
    • 可以给多个字段创建索引;
    • 根据索引字段搜索、排序速度非常快;
  • 缺点:
    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描;

倒排索引:

  • 优点:
    • 根据词条搜索、模糊搜索时,速度非常快;
  • 缺点:
    • 只能给词条创建索引,而不是字段;
    • 无法根据字段做排序

1.3 基础概念

elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。

1.3.1 文档和字段

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据、一个订单信息。

文档数据会被序列化为json格式后存储在elasticsearch中:

json 复制代码
{
    "id": 1,
    "title": "小米手机",
    "price": 3499
}
{
    "id": 2,
    "title": "华为手机",
    "price": 4999
}
{
    "id": 3,
    "title": "华为小米充电器",
    "price": 49
}
{
    "id": 4,
    "title": "小米手环",
    "price": 299
}

因此,原来数据库中的一行数据就是ES中的一个JSON文档,而数据库中每行数据都包含很多列,这些就转换为JSON文档中的字段(Field)

1.3.2 索引和映射

随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单文档等;

所有文档都散乱存放显然非常混乱,也不方便管理,因此,要将类型相同的文档集中在一起管理,成为索引(Index)
商品索引: 所有商品文档,组织在一起;

json 复制代码
{
    "id": 1,
    "title": "小米手机",
    "price": 3499
}

{
    "id": 2,
    "title": "华为手机",
    "price": 4999
}

{
    "id": 3,
    "title": "三星手机",
    "price": 3999
}

用户索引: 所有用户文档,组织在一起;

json 复制代码
{
    "id": 101,
    "name": "张三",
    "age": 21
}

{
    "id": 102,
    "name": "李四",
    "age": 24
}

{
    "id": 103,
    "name": "麻子",
    "age": 18
}

订单索引: 所有订单文档,组织在一起;

json 复制代码
{
    "id": 10,
    "userId": 101,
    "goodsId": 1,
    "totalFee": 294
}

{
    "id": 11,
    "userId": 102,
    "goodsId": 2,
    "totalFee": 328
}

1.3.3 mysql与elasticsearch

mysqlelasticsearch做对比:

MySQL Elasticsearch 说明
Table Index 索引(index),就是文档的集合,类似数据库的表(table)
Row Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
Column Field 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
Schema Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQL DSL DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD


学习了elasticsearch就不再需要mysql了?

并不是,两者之间各有优势:

  • mysql:擅长事务类型操作,可以确保数据的安全和一致性;
  • Elasticsearch:擅长海量数据的搜索、分析、计算

在企业中,两者结合使用:

  • 对安全性要求高的写操作,使用MySQL实现;
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现;
  • 两者再基于某种方式,实现数据的同步,保证一致性;

1.4 IK分词器

Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK分词器就是一个中文分词算法

1.4.1 安装IK分词器

方式一: 在线安装

  1. 执行一个docker命令即可:
shell 复制代码
docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
  1. 重启es容器:
docker 复制代码

方式二: 离线安装

  1. 查看之前安装的elasticsearch容器的plugins数据据目录:
shell 复制代码
docker volume inspect es-plugins

结果如下:

json 复制代码
[
    {
        "CreatedAt": "2024-11-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

可以看到elasticsearch的插件挂载到了/var/lib/docker/volumes/es-plugins/_data目录,需要把IK分词器上传到这个目录。

  1. 找到离线的7.12.1版本的ik分词器压缩文件,需要解压,将其对应的文件复制ik文件夹中;

  2. 将ik目录复制到虚拟机上;

  3. 将ik目录上传至虚拟机的/var/lib/docker/volumes/es-plugins/_data目录中;

执行命令:

shell 复制代码
docker cp /home/yueyue/ik es:/usr/share/elasticsearch/plugins/
  1. 重启容器
shell 复制代码
docker restart es

1.4.2 使用IK分词器

IK分词器包含两种模式:

  • ik_smart:智能语义切分;
  • ik_max_word:最细粒度切分;

KibanaDevTools上来测试分词器:

首先测试Elasticsearch官方提供的标准分词器,标准分词器智能1字1词条,无法正确对中文做分词:

json 复制代码
POST /_analyze
{
  "analyzer": "standard",
  "text": "月月程序员学习java太棒了"
}

结果如下:

json 复制代码
{
  "tokens" : [
    {
      "token" : "月",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "月",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "程",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "序",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "学",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "习",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "太",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<IDEOGRAPHIC>",
      "position" : 8
    },
    {
      "token" : "棒",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "<IDEOGRAPHIC>",
      "position" : 9
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "<IDEOGRAPHIC>",
      "position" : 10
    }
  ]
}

测试IK分词器:

json 复制代码
POST /_analyze
{
  "analyzer": "ik_smart",
  "text": "月月程序员学习java太棒了"
}

结果如下:

json 复制代码
{
  "tokens" : [
    {
      "token" : "月月",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "学习",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "太棒了",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 4
    }
  ]
}

1.4.3 拓展词典

随着互联网的发展,"造词运动"也越来越频繁,出现很多的词语,在原有的词汇列表中并不存在,比如"泰传智播客","码农"等,IK分词器无法对这些词汇进行分词:

需要正确分词,IK分词器的词库需要不断的更新,IK分词器提供了拓展词汇的功能;

  1. 打开IK分词器config目录
  2. 在IKAnalyzer.cfg.xml配置文件内容添加:
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">ext.dic</entry>
</properties>
  1. 在IK分词器的config目录新建一个 ext.dic,里面写入常用的特殊的词语,可以参考config目录下复制一个配置文件进行修改;
plain 复制代码
传智播客
泰裤辣
  1. 重启elasticsearch;
shell 复制代码
docker restart es

# 查看 日志
docker logs -f elasticsearch
  1. 再次测试;
json 复制代码
POST /_analyze
{
  "analyzer": "ik_max_word",
  "text": "传智播客开设大学,真的泰裤辣!"
}

结果(传智播客泰酷辣都正确分词):

json 复制代码
{
  "tokens" : [
    {
      "token" : "传智播客",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "开设",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "大学",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "真的",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "泰裤辣",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 4
    }
  ]
}

1.4.4 总结

分词器的作用是什么?

  • 创建倒排索引时,对文档分词
  • 用户搜索时,对输入的内容分词

IK分词器有几种模式?

  • ik_smart:智能切分,粗粒度;
  • ik_max_word:最细切分,细粒度;

IK分词器如何拓展词条?如何停用词条?

  • 利用config目录IKAnalyzer.cfg.xml文件添加拓展词典和停用词典
  • 词典(xxx.dic文件)中添加拓展词条或者停用词条;

二、索引库操作

Index就是类似数据库表,Mapping映射就类似表的结构,要向es中存储数据,必须先创建Index和Mapping。

2.1 Mapping映射属性

Mapping是对索引库中文档的约束,常见的Mapping属性包括:

  • type:字段数据类型,常见的简单类型:
    • 字符串text(可分词的文本)、keyword(精确值、例如:品牌、国家、IP地址);
    • 数值longintegershortbytedoublefloat
    • 布尔boolean
    • 日期date
    • 对象object
  • index是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

示例:json文档:

json 复制代码
{
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

对应的每个字段的映射(Mapping):

字段名 字段类型 类型说明 是否参与搜索 是否参与分词 分词器
age integer 整数 x \[\] ------
weight float 浮点数 x \[\] ------
isMarried boolean 布尔 x \[\] ------
info text 字符串,但需要分词 x x IK
email keyword 字符串,但是不分词 \[\] \[\] ------
score float 只看数组中元素类型 x \[\] ------
name
firstName keyword 字符串,但是不分词 x \[\] ------
lastName keyword 字符串,但是不分词 x \[\] ------

2.2 索引库的CRUD

由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用json风格

直接基于Kibana的DevTools来编写请求做测试,有语法提示。

2.2.1 创建索引库和映射

基本语法:

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式:

json 复制代码
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

示例:

json 复制代码
PUT /yueyue
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "false"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

返回结果:

2.2.2 查询索引库

基本语法:

  • 请求方式:GET
  • 请求路径:/索引库名
  • 请求参数:无

格式:

json 复制代码
GET /索引库名

示例:

json 复制代码
GET /yueyue

返回结果:

2.2.3 修改索引库

倒排索引结构虽然不复杂,但一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但允许添加新的字段mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或更新索引库的基础属性
语法说明:

json 复制代码
PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

示例:

json 复制代码
PUT /yueyue/_mapping
{
  "properties": {
    "age":{
      "type": "integer"
    }
  }
}

返回结果:

2.2.4 删除索引库

语法:

  • 请求方式:DELETE
  • 请求路径:/索引库名
  • 请求参数:无

格式:

json 复制代码
DELETE /索引库名

示例:

json 复制代码
DELETE /yueyue

返回结果:

再执行查询索引库,返回结果:

2.2.5 总结

索引库操作有哪些?

  • 创建索引库:PUT/索引库名
  • 查询索引库:GET/索引库名
  • 删除索引库:DELETE/索引库名
  • 修改索引库,添加字段:PUT/索引库名/_mapping

可以看到,对索引库的操作基本遵循的Restful的风格,因此API接口非常统一,方便记忆。

三、文档操作

有了索引库,可以向索引库(类似mysql中的表)中添加数据了。

Elasticsearch中的数据,其实就是json风格的文档,操作文档自然保护等几种常见操作。

3.1 新增文档

语法:

json 复制代码
POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
}

示例:

json 复制代码
POST /yueyue/_doc/1
{
    "info": "yueyue程序员",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

返回结果:

3.2 查询文档

根据rest风格,新增是post,查询是get,查询一般都需要条件;
语法:

json 复制代码
GET /{索引库名称}/_doc/{id}

示例:

json 复制代码
GET /yueyue/_doc/1

返回结果:

3.3 删除文档

删除使用DELETE请求,需要根据id进行删除:

javascript 复制代码
DELETE /{索引库名}/_doc/id值

示例:

javascript 复制代码
DELETE /yueyue/_doc/1

结果:

3.4 修改文档

修改有两种方式:

  • 全量修改:直接覆盖原来的文档;
  • 局部修改:修改文档中的部分字段

3.4.1 全量修改

全量修改是覆盖原来的文档,其本质是两步操作:

  • 根据指定的id删除文档;
  • 新增一个相同id的文档;

注意: 如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作。
语法:

json 复制代码
PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

示例:

json 复制代码
PUT /yueyue/_doc/1
{
    "info": "月月程序员高级Java程序员",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

由于id为1的文档已经被删除,所以第一次执行时,得到的反馈时created:

如果执行第2次时,得到的反馈时updated:

3.4.2 局部修改

局部修改是只修改指定id匹配的文档中的部分字段。
语法:

json 复制代码
POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

示例:

json 复制代码
POST /yueyue/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

执行结果:

3.5 批处理

批处理采用POST请求,基本语法如下:

json 复制代码
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

其中:

  • index代表新增操作
    • _index:指定索引库名
    • _id指定要操作的文档id
    • { "field1" : "value1" }:则是要新增的文档内容
  • delete代表删除操作
    • _index:指定索引库名
    • _id指定要操作的文档id
  • update代表更新操作
    • _index:指定索引库名
    • _id指定要操作的文档id
    • { "doc" : {"field2" : "value2"} }:要更新的文档字段

示例:批量新增:

json 复制代码
POST /_bulk
{"index": {"_index":"yueyue", "_id": "3"}}
{"info": "程序员C++工程是", "email": "ww@itcast.cn", "name":{"firstName": "五", "lastName":"王"}}
{"index": {"_index":"yueyue", "_id": "4"}}
{"info": "程序员前端工程师", "email": "zhangsan@itcast.cn", "name":{"firstName": "三", "lastName":"张"}}

返回结果:

示例:批量删除:

json 复制代码
POST /_bulk
{"delete":{"_index":"yueyue", "_id": "3"}}
{"delete":{"_index":"yueyue", "_id": "4"}}

返回结果:

3.6 总结

文档操作有哪些?

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
    • 局部修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

四、RestAPI

ES官方提供了各种不同语言的客户端,用来操作ES,这些客户端的本质就是组装DSL语句,通过http请求发送给ES。

官方文档地址:https://www.elastic.co/docs/reference/elasticsearch-clients

由于ES目前最新版本是8.8,提供了全新版本呢的客户端,老版本的客户端已经被标为过时的,我们采用7.12版本;

选择7.12版本,HightLevelLevelRestClient版本:

4.1 初始化RestClient

elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成该对象的初始化,建立与elasticsearch的连接,主要分为三个部分:

  1. item-service模块中引入esRestHighLevelClient依赖
xml 复制代码
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
  1. 因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本
xml 复制代码
  <properties>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <elasticsearch.version>7.12.1</elasticsearch.version>
  </properties>
  1. 初始化RestHighLevelClient
    初始化的代码如下:
java 复制代码
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.150.101:9200")
));

为了单元测试方便,创建一个测试类IndexTest,然后将初始化代码编写在@BeforeEach方法中:

java 复制代码
package com.hmall.item.es;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class IndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.150.101:9200")
        ));
    }

    @Test
    void testConnect() {
        System.out.println(client);
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

4.2 创建索引库

由于要实现对商品搜索,所以需要将商品添加到Elasticsearch中,不过需要根据搜索业务的需求来设定索引库结构,而不是一次把MySQL数据写入Elasticsearch。

4.2.1 Mapping映射

搜索页面的效果:

实现搜索功能需要的字段包括三大部分

  • 搜索过滤字段
    • 分类
    • 品牌
    • 价格
  • 排序字段
    • 默认:按照更新时间降序排序
    • 销量
    • 价格
  • 展示字段
    • 商品id:用于点击后跳转
    • 图片地址
    • 是否是广告推广商品
    • 名称
    • 价格
    • 评价数量
    • 销量

对应的商品表结构如下,索引库无关字段已经划掉:

结合数据库表结构,以上字段对应的mapping映射属性如下:

字段名 字段类型 类型说明 是否参与搜索 是否参与分词 分词器
id long 长整数 x \[\] ------
name text 字符串,参与分词搜索 x x IK
price integer 以分为单位,所以是整数 x \[\] ------
stock integer 字符串,但需要分词 x \[\] ------
image keyword 字符串,但是不分词 \[\] \[\] ------
category keyword 字符串,但是不分词 x \[\] ------
brand keyword 字符串,但是不分词 x \[\] ------
sold integer 销量,整数 x \[\] ------
commentCount integer 评价,整数 \[\] \[\] ------
isAD boolean 布尔类型 x \[\] ------
updateTime Date 更新时间 x \[\] ------

因此,最终的索引库文档结构:

json 复制代码
PUT /items
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "price":{
        "type": "integer"
      },
      "stock":{
        "type": "integer"
      },
      "image":{
        "type": "keyword",
        "index": false
      },
      "category":{
        "type": "keyword"
      },
      "brand":{
        "type": "keyword"
      },
      "sold":{
        "type": "integer"
      },
      "commentCount":{
        "type": "integer",
        "index": false
      },
      "isAD":{
        "type": "boolean"
      },
      "updateTime":{
        "type": "date"
      }
    }
  }
}

4.2.2 创建索引

创建索引库的API:

代码分为三步:

    1. 创建Request对象
    • 因为是创建索引库的操作,因此RequestCreateIndexRequest
    1. 添加请求参数
    • 其实就是Json格式Mapping映射参数,因为json字符串很长,是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更优雅;
    1. 发送请求
    • client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法,例如创建索引、删除索引、判断索引是否存在等;
      item-service中的IndexTest测试类中,具体代码如下:
java 复制代码
    /**
     * 创建索引库(注意导入的包出现问题)
     * @throws IOException
     */
    @Test
    void testCreateIndex() throws IOException {
        // 删除已存在的索引
        GetIndexRequest getRequest = new GetIndexRequest("items");
        if (client.indices().exists(getRequest, RequestOptions.DEFAULT)) {
            DeleteIndexRequest deleteRequest = new DeleteIndexRequest("items");
            client.indices().delete(deleteRequest, RequestOptions.DEFAULT);
            System.out.println("已删除现有索引");
        }

        // 1.创建Request对象
        CreateIndexRequest request = new CreateIndexRequest("items");
        // 2.准备请求参数 - 使用正确的格式
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        // 3.发送请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println("索引创建是否成功:" + response.isAcknowledged());
    }

    static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": { \"type\": \"keyword\" },\n" +
            "      \"name\": { \"type\": \"text\", \"analyzer\": \"ik_max_word\" },\n" +
            "      \"price\": { \"type\": \"integer\" },\n" +
            "      \"stock\": { \"type\": \"integer\" },\n" +
            "      \"image\": { \"type\": \"keyword\", \"index\": false },\n" +
            "      \"category\": { \"type\": \"keyword\" },\n" +
            "      \"brand\": { \"type\": \"keyword\" },\n" +
            "      \"sold\": { \"type\": \"integer\" },\n" +
            "      \"commentCount\": { \"type\": \"integer\" },\n" +
            "      \"isAD\": { \"type\": \"boolean\" },\n" +
            "      \"updateTime\": { \"type\": \"date\" }\n" +
            "    }\n" +
            "  }\n" +
            "}";

4.3 删除索引库

删除索引库的请求非常简单:

json 复制代码
DELETE /hotel

与创建索引库相比:

  • 请求方式从PUT变为DELTE
  • 请求路径不变
  • 无请求参数

代码的差异,体现在Request对象上,流程如下:

  1. 创建request对象。这次是DeleteIndexRequest对象;
  2. 准备参数。这里是无参,因此省略;
  3. 发送请求。改用delete方法;

item-service中的IndexTest测试类中,编写单元测试,实现删除索引:

java 复制代码
@Test
void testDeleteIndex() throws IOException {
    // 1.创建Request对象
    DeleteIndexRequest request = new DeleteIndexRequest("items");
    // 2.发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

4.4 判断索引库是否存在

判断索引库是否存在,本质就是查询,对应的请求语句是:

json 复制代码
GET /items

因此与删除的Java代码流程是类似的,流程如下:

  • 1)创建Request对象。这次是GetIndexRequest对象
  • 2)准备参数。这里是无参,直接省略
  • 3)发送请求。改用exists方法
java 复制代码
@Test
void testExistsIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("items");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

4.5 总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:

  • 初始化RestHighLevelClient
  • 创建XxxIndexRequestXXXCreateGetDelete
  • 准备请求参数Create时需要,其它是无参,可以省略)
  • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxxcreateexistsdelete

五、RestClient操作文档

索引库准备好,就可以操作文档(数据记录)了,为了与索引库操作分离,再创建一个测试类:

  • 初始化RestHighLevelClient;
  • 商品数据在数据库中,需要利用IHotelService去查询,所以注入这个接口;
java 复制代码
package com.hmall.item.es;

import com.hmall.item.service.IItemService;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest(properties = "spring.profiles.active=local")
public class DocumentTest {

    private RestHighLevelClient client;
    @Autowired
    private IItemService itemService;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.19.128:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

5.1 RestClient操作文档

需要将数据库中的商品信息导入elasticsearch中。

5.1.1 实体类

索引库结构数据库结构存在差异,要定义一个索引库结构对应的实体。

hm-service模块的com.hmall.item.domain.dto包中定义一个新的DTO

java 复制代码
package com.hmall.item.domain.po;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 索引库结构对应的实体类
 */
@Data
@ApiModel(description = "索引库实体")
public class ItemDoc{

    @ApiModelProperty("商品id")
    private String id;

    @ApiModelProperty("商品名称")
    private String name;

    @ApiModelProperty("价格(分)")
    private Integer price;

    @ApiModelProperty("商品图片")
    private String image;

    @ApiModelProperty("类目名称")
    private String category;

    @ApiModelProperty("品牌名称")
    private String brand;

    @ApiModelProperty("销量")
    private Integer sold;

    @ApiModelProperty("评论数")
    private Integer commentCount;

    @ApiModelProperty("是否是推广广告,true/false")
    private Boolean isAD;

    @ApiModelProperty("更新时间")
    private LocalDateTime updateTime;
}

5.1.2 API语法

新增文档的请求语法如下:

json 复制代码
POST /{索引库名}/_doc/1
{
    "name": "Jack",
    "age": 21
}

对应的JavaAPI如下:

可以看到与索引库操作的API非常类似,同样是三步走:

  • 1)创建Request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程
  • 2)准备请求参数,本例中就是Json文档
  • 3)发送请求
    变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()了。

5.1.3 完整代码

导入商品数据,除了参考API模板"三步走"以外,还需要做几点准备工作:

  • 商品数据来自于数据库,我们需要先查询出来,得到Item对象
  • Item对象需要转为ItemDoc对象
  • ItemDTO需要序列化为json格式

代码整体步骤如下:

  • 1)根据id查询商品数据Item
  • 2)将Item封装为ItemDoc
  • 3)将ItemDoc序列化为JSON
  • 4)创建IndexRequest,指定索引库名id
  • 5)准备请求参数,也就是JSON文档
  • 6)发送请求

item-serviceDocumentTest测试类中,编写单元测试:

java 复制代码
@Test
    void testAddDocument() throws IOException {
        // 1.根据id查询商品数据
        Item item = itemService.getById(100002644680L);
        // 2.转换为文档类型
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
        // 3.将ItemDTO转json
        String doc = JSONUtil.toJsonStr(itemDoc);

        // 4.准备Request对象
        IndexRequest request = new IndexRequest("items").id(itemDoc.getId().toString());
        // 5.准备Json文档
        request.source(doc, XContentType.JSON);
        // 6.发送请求
        client.index(request, RequestOptions.DEFAULT);

        System.out.println("文档添加成功,ID:" + itemDoc.getId());
    }

5.2 查询文档

根据id查询文档为例

5.2.1 语法说明

查询的请求语句如下:

json 复制代码
GET /{索引库名}/_doc/{id}

与之前的流程类似,代码大概分2步:

  • 创建Request对象
  • 准备请求参数,这里是无参,直接省略
  • 发送请求

不过查询的目的是得到结果,解析为ItemDTO,还要再加一步对结果的解析。示例代码如下:

可以看到,响应结果是一个JSON,其中文档放在一个_source属性中,因此解析就是拿到_source,反序列化为Java对象即可。

其它代码与之前类似,流程如下:

  • 1)准备Request对象。这次是查询,所以是GetRequest
  • 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
  • 3)解析结果,就是对JSON反序列化

5.2.2 完整代码

item-serviceDocumentTest测试类中,编写单元测试:

java 复制代码
 /**
     * 查询文档
     * @throws IOException
     */
    @Test
    void testGetDocumentById() throws IOException {
        // 1.准备Request对象
        GetRequest request = new GetRequest("items").id("100002644680");
        // 2.发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        // 3.获取响应结果中的source
        String json = response.getSourceAsString();

        ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
        System.out.println("itemDoc= " + itemDoc);
    }

5.3 删除文档

删除的请求语句如下:

json 复制代码
DELETE /hotel/_doc/{id}

与查询相比,仅仅是请求方式从DELETE变成GET,可以想象Java代码应该依然是2步走:

  • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
  • 2)准备参数,无参,直接省略
  • 3)发送请求。因为是删除,所以是client.delete()方法

item-serviceDocumentTest测试类中,编写单元测试:

java 复制代码
    /**
     * 删除文档
     */
    @Test
    void testDeleteDocument() throws IOException {
        // 注意:索引名是 "items",不是 "item"
        DeleteRequest request = new DeleteRequest("items", "100002644680");
        client.delete(request, RequestOptions.DEFAULT);

        System.out.println("文档删除成功,ID:100002644680");
    }

5.4 修改文档

修改文档的两种方式:

  • 全局修改: 本质是先根据id删除,再新增;
  • 局部修改: 修改文档中的指定字段值;

RestClientAPI中,全量修改新增的API完全一致,判断依据是ID

  • 如果新增时,ID已经存在,则修改
  • 如果新增时,ID不存在,则新增

主要关注局部修改的API即可

5.4.1 语法说明

局部修改的请求语法如下:

json 复制代码
POST /{索引库名}/_update/{id}
{
  "doc": {
    "字段名": "字段值",
    "字段名": "字段值"
  }
}

代码示例如图:

三步走:

  • 1)准备Request对象。这次是修改,所以是UpdateRequest
  • 2)准备参数。也就是JSON文档,里面包含要修改的字段
  • 3)更新文档。这里调用client.update()方法

5.4.2 完整代码

item-serviceDocumentTest测试类中,编写单元测试:

java 复制代码
   /**
     * 局部修改文档
     * @throws IOException
     */
    @Test
    void testUpdateDocument() throws IOException {
        // 1.准备Request
        UpdateRequest request = new UpdateRequest("items", "100002644680");
        // 2.准备请求参数
        request.doc(
                "price", 58800,
                "commentCount", 1
        );
        // 3.发送请求
        client.update(request, RequestOptions.DEFAULT);
    }

5.5 批量导入文档

在之前的案例中,我们都是操作单个文档。而数据库中的商品数据实际会达到数十万条,某些项目中可能达到数百万条

我们如果要将这些数据导入索引库,肯定不能逐条导入,而是采用批处理方案。常见的方案有:

  • 利用Logstash批量导入
    • 需要安装Logstash
    • 对数据的再加工能力较弱
    • 无需编码,但要学习编写Logstash导入配置
  • 利用JavaAPI批量导入
    • 需要编码,但基于JavaAPI,学习成本低
    • 更加灵活,可以任意对数据做再加工处理后写入索引库

学习下如何利用JavaAPI实现批量文档导入

5.5.1 语法说明

批处理与前面讲的文档的CRUD步骤基本一致:

  • 创建Request,但这次用的是BulkRequest
  • 准备请求参数
  • 发送请求,这次要用到client.bulk()方法

BulkRequest本身其实并没有请求参数,其本质就是将多个普通的CRUD请求组合在一起发送。例如:

  • 批量新增文档,就是给每个文档创建一个IndexRequest请求,然后封装到BulkRequest中,一起发出。
  • 批量删除,就是创建NDeleteRequest请求,然后封装到BulkRequest,一起发出

因此BulkRequest中提供了add方法,用以添加其它CRUD的请求:

可以看到,能添加的请求有:

  • IndexRequest,也就是新增
  • UpdateRequest,也就是修改
  • DeleteRequest,也就是删除

Bulk中添加了多个IndexRequest,就是批量新增功能了。示例:

java 复制代码
 /**
     * 批量新增
     * @throws IOException
     */
    @Test
    void testBulk() throws IOException {
        // 1.创建Request
        BulkRequest request = new BulkRequest();

        // 2.准备请求参数 - 使用有效的 JSON 格式
        request.add(new IndexRequest("items").id("1")
                .source("{\"name\":\"商品1\",\"price\":10000}", XContentType.JSON));
        request.add(new IndexRequest("items").id("2")
                .source("{\"name\":\"商品2\",\"price\":20000}", XContentType.JSON));

        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);

        System.out.println("批量添加成功");
    }

5.5.2 完整代码

当我们要导入商品数据时,由于商品数量达到``数十万,因此不可能一次性全部导入。建议采用循环遍历方式,每次导入1000条左右的数据。 item-service的DocumentTest测试类中,编写单元测试```:

java 复制代码
@Test
void testLoadItemDocs() throws IOException {
    // 分页查询商品数据
    int pageNo = 1;
    int size = 1000;
    while (true) {
        Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page<Item>(pageNo, size));
        // 非空校验
        List<Item> items = page.getRecords();
        if (CollUtils.isEmpty(items)) {
            return;
        }
        log.info("加载第{}页数据,共{}条", pageNo, items.size());
        // 1.创建Request
        BulkRequest request = new BulkRequest("items");
        // 2.准备参数,添加多个新增的Request
        for (Item item : items) {
            // 2.1.转换为文档类型ItemDTO
            ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
            // 2.2.创建新增文档的Request对象
            request.add(new IndexRequest()
                            .id(itemDoc.getId())
                            .source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON));
        }
        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);

        // 翻页
        pageNo++;
    }
}

执行命令查询所有的数据:

json 复制代码
GET /items/_search
{
  "query": {
    "match_all": {}
  },
  "size": 10
}

5.6 总结

文档操作的基本步骤

  • 初始化RestHighLevelClient
  • 创建XxxRequest
    • XXXIndexGetUpdateDeleteBulk
  • 准备参数IndexUpdateBulk时需要)
  • 发送请求
    • 调用RestHighLevelClient#.xxx()方法,xxxindexgetupdatedeletebulk
  • 解析结果(Get时需要)

六、相关高频面试题

分类 知识点 说明/对比
概念对应 MySQL → Elasticsearch Database(无对应) / Table→Index / Row→Document / Column→Field / Schema→Mapping / SQL→DSL
索引类型 正向索引 根据文档ID建立索引,适合精确匹配,类比MySQL主键索引
倒排索引 根据词条建立索引,适合全文搜索,类比MySQL全文索引
倒排索引流程 步骤1 对文档内容分词,得到词条
步骤2 建立词条→文档ID的映射关系表
步骤3 用户搜索条件分词
步骤4 词条检索获取文档ID列表
步骤5 根据文档ID到正向索引获取完整文档
Mapping属性 type 字段数据类型:text/keyword/integer/long/date/boolean
index 是否创建索引,默认true
analyzer 分词器:ik_smart/ik_max_word/standard
properties 子字段定义(用于对象类型)
字段类型 text 可分词的文本,用于全文搜索
keyword 精确值,不分词,用于过滤/排序/聚合
IK分词器 ik_smart 智能语义切分,粗粒度,词条最少化
ik_max_word 最细粒度切分,细粒度,词条最大化
索引库操作 创建 PUT /索引库名,需要mapping请求体
查询 GET /索引库名,无需请求体
删除 DELETE /索引库名,无需请求体
添加字段 PUT /索引库名/_mapping,需要新字段定义
文档操作 新增 POST /索引库名/_doc/文档id,需要JSON文档
查询 GET /索引库名/_doc/文档id,无需请求体
删除 DELETE /索引库名/_doc/文档id,无需请求体
全量修改 PUT /索引库名/_doc/文档id,需要JSON文档(id存在则修改,不存在则新增)
局部修改 POST /索引库名/_update/文档id,需要doc对象
批处理 POST /_bulk,需要多个操作JSON
RestClient索引库 创建Request CreateIndexRequest / GetIndexRequest / DeleteIndexRequest
发送请求 client.indices().create() / exists() / delete()
RestClient文档 创建Request IndexRequest / GetRequest / UpdateRequest / DeleteRequest / BulkRequest
发送请求 client.index() / get() / update() / delete() / bulk()
解析结果 GetResponse.getSourceAsString() → 反序列化
使用场景 MySQL擅长 事务操作,数据安全性和一致性
Elasticsearch擅长 海量数据的搜索、分析、计算
企业结合 MySQL写操作 + ES搜索操作 + 数据同步保证一致性
商品索引字段 id keyword,参与搜索,不参与分词
name text,参与搜索,参与分词,使用ik_max_word
price integer,参与搜索,不参与分词
stock integer,不参与搜索,不参与分词
image keyword(index=false),不参与搜索,不参与分词
category keyword,参与搜索,不参与分词
brand keyword,参与搜索,不参与分词
sold integer,参与搜索,不参与分词
commentCount integer(index=false),不参与搜索,不参与分词
isAD boolean,参与搜索,不参与分词
updateTime date,参与搜索,不参与分词
相关推荐
_codemonster7 小时前
30分钟快速搭建 Spring Cloud Alibaba 微服务实战(一)
微服务·架构·毕业设计·课程设计
Cosolar7 小时前
从零写一个 Attention Is All You Need
人工智能·面试·架构
qcx239 小时前
【系统学AI】09 Multi-Agent架构(2026版):从学术理论到工业级实践
java·人工智能·架构·multi-agent·claude agent
wb043072019 小时前
厨房质检员——从阿明的“祖传配方“到标准化质检,看测试金字塔的落地
架构·log4j
Dongwoo Jeong10 小时前
微服务架构(MSA)是如何诞生的?
微服务·云原生·架构
半旧夜夏10 小时前
【保姆级】微服务组件环境搭建(Docker Compose版)
java·linux·spring cloud·微服务·云原生·容器
张忠琳11 小时前
【kubernetes v1.21】(kubelet 1)Kubelet 核心架构与启动流程
云原生·架构·kubernetes·kubelet
用户9874092388711 小时前
超算中心 高性能计算 htc命令module use的作用
架构
liushangzaibeijing11 小时前
Superpower 使用大纲
大数据·elasticsearch·搜索引擎
AI科技星11 小时前
基于**v=c(空间光速螺旋运动)唯一第一性原理**重新完整求导证明
人工智能·线性代数·算法·机器学习·架构·概率论·学习方法