赞
踩
在做一些小项目的话,通常使用MP来查询或者条件查询,速度上还是挺快的
但是在实际开发中,我们的数据肯定是上万甚至百万级的,这时候就应该使用elasticsearch在进行查找
elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
elasticsearch是基于Restful风格实现的
Mysql采用的是正向索引,如下图所示
如果是根据id索引查询,那么相对而言效率比较快
如果是根据title查询,那么需要一行一行对比才可以得知是不是符合要求
因此,假如有一千万条数据,要查询的数据在第一千万条,那么就需要对比一千万次,效率极奇慢,用户体验非常不好
而elasticsearch采用的是倒排索引(相对而言的),如下图所示
倒排索引中有两个非常重要的概念:
- 文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息- 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
倒排索引的搜索流程如下(以搜索"华为手机"为例):
"华为手机"
进行搜索。华为
、手机
。虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
正向索引:
倒排索引:
elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
索引,也就是相同类型的文档的集合。也可以理解为是数据库中的表。**映射(mapping)**是索引中文档的字段约束信息,类似表的结构约束。
elasticsearch的安装也是基与docker来安装部署的
首先,需要创建一个网络。因为除了部署elasticsearch还需要部署kibana容器,然后使得两个容器互联
docker network create es-net
接着将镜像放置在虚拟机内,这里镜像就不提供了,自己网上找一下吧
然后加载镜像
# 导入数据
docker load -i es.tar
同样的,还有kibana
的tar包也需要这样做
接着运行下面的docker命令,部署单点的es:
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 es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
命令解释:
-e "cluster.name=es-docker-cluster"
:设置集群名称-e "http.host=0.0.0.0"
:监听的地址,可以外网访问-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小-e "discovery.type=single-node"
:非集群模式-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录--privileged
:授予逻辑卷访问权--network es-net
:加入一个名为es-net的网络中-p 9200:9200
:端口映射配置注意:
第一个端口9200是指暴露的http端口,供用户使用
第二个端口是9300是es容器各个结点之间互连的端口
部署完成之后,浏览器访问http://192.168.220.132:9200/即可查看elasticsearch的响应结果(这里的IP换自己对应的IP)
出现这个界面,说明elasticsearch安装成功。
安装完elasticsearch,还需要安装一个kibana
kibana可以给我们提供一个elasticsearch的可视化界面,方便我们学习
运行docker命令,部署kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net
:加入一个名为es-net的网络中,与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch-p 5601:5601
:端口映射配置安装成功之后,我们在浏览器访问http://192.168.220.132:5601/即可查看
出现这个界面就表示安装成功
前面提到了会将查询的内容进行分词,但是中文分词一般不是太完美,所以需要安装一个IK分词器,提高中文分词的准确度
将IK分词器解压到windows先,然后使用docker命名查看elasticsearch的plugins目录位置
docker volume inspect es-plugins
然后将IK分词器解压放到这里的目录去
接着重启容器
# 4、重启容器
docker restart es
这样就完成了
知识点:
ik_smart
:最少切分
ik_max_word
:最细切分
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
例如下面的json
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "消息",
"email": "123456@qq.com",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
对应的每个字段映射(mapping):
这里的操作都是基于kibana进行操作的
(1)新增索引
例如:
PUT /索引库名称 { "mappings": { "properties": { "字段名":{ "type": "text", "analyzer": "ik_smart" }, "字段名2":{ "type": "keyword", "index": "false" }, "字段名3":{ "properties": { "子字段": { "type": "keyword" } } }, // ...略 } } }
(2)查询索引
请求方式:GET
请求路径:/索引库名
请求参数:无
例如:
GET /索引库名
(3)修改索引
索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
(4)删除索引
语法:
请求方式:DELETE
请求路径:/索引库名
请求参数:无
格式:
DELETE /索引库名
(1)新增文档
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
(2)查询文档
GET /{索引库名称}/_doc/{id}
(3)删除文档
DELETE /{索引库名}/_doc/id值
(4)修改文档
修改有两种方式:
#全量修改
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
# 增量修改是只修改指定id匹配的文档中的部分字段。
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
下面将会根据一个案例来说明RestAPI的使用
#酒店的mapping PUT /hotel { "mappings": { "properties": { "id":{ "type": "keyword" }, "name":{ "type": "text", "analyzer": "ik_max_word", "copy_to": "all" }, "address":{ "type": "keyword", "index": false }, "price":{ "type": "integer" }, "score":{ "type": "integer" }, "brand":{ "type": "keyword", "copy_to": "all" }, "city":{ "type": "keyword" }, "starName":{ "type": "keyword" }, "business":{ "type": "keyword", "copy_to": "all" }, "location":{ "type": "geo_point" }, "pic":{ "type": "keyword", "index": false }, "all":{ "type": "text", "analyzer": "ik_max_word" } } } }
下面将会用JAVA代码添加上面的索引
注意:
- location:地理坐标,里面包含精度、纬度
- all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
首先引入依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
初始化RestHighLevelClient
public class HotelIndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.220.132:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
@Test
void createHotelIndex() throws IOException {
//1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
其中MAPPING_TEMPLATE是自己定义的一个常量,也就是上面的json
public class HotelConstants { public static final String MAPPING_TEMPLATE = "{\n" + " \"mappings\": {\n" + " \"properties\": {\n" + " \"id\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"name\":{\n" + " \"type\": \"text\", \n" + " \"analyzer\": \"ik_max_word\",\n" + " \"copy_to\": \"all\"\n" + " },\n" + " \"address\":{\n" + " \"type\": \"keyword\",\n" + " \"index\": false\n" + " },\n" + " \"price\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"score\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"brand\":{\n" + " \"type\": \"keyword\",\n" + " \"copy_to\": \"all\"\n" + " },\n" + " \"city\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"starName\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"business\":{\n" + " \"type\": \"keyword\",\n" + " \"copy_to\": \"all\"\n" + " },\n" + " \"location\":{\n" + " \"type\": \"geo_point\"\n" + " },\n" + " \"pic\":{\n" + " \"type\": \"keyword\",\n" + " \"index\": false\n" + " },\n" + " \"all\":{\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\"\n" + " }\n" + " }\n" + " }\n" + "}"; }
代码分为三步:
@Test
void deleteHotelIndex() throws IOException {
//1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
@Test
void testExitsHotelIndex() throws IOException {
//1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
//2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
初始化和操作索引一样
@Test
void testAddDocument() throws IOException {
//根据ID查询酒店数据
Hotel hotel = hotelService.getById(61083L);
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
//1. 准备request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//2. 准备JSON文档
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//3. 发送请求
client.index(request, RequestOptions.DEFAULT);
}
由于这里Hotel对象和文档有点差别,所以这里先将Hotel处理成hotelDoc再操作
@Test
void testGetDocumentById() throws IOException {
//1. 准备Request
GetRequest request = new GetRequest("hotel", "61083");
//2. 发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//3. 解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
@Test
void testDeleteDocument() throws IOException {
//1. 准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 发送请求
client.delete(request, RequestOptions.DEFAULT);
}
@Test
void testUpdateDocument() throws IOException {
//1. 准备Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
//2. 准备请求参数
request.doc(
"price", "952",
"starName", "四钻"
);
//3. 发送请求
client.update(request, RequestOptions.DEFAULT);
}
@Test void name() throws IOException { //批量查询数据 List<Hotel> hotels = hotelService.list(); //1.创建Request请求 BulkRequest request = new BulkRequest(); //2.准备参数,添加多个新增的Request for (Hotel hotel : hotels) { //转化为文档类型的HotelDoc HotelDoc hotelDoc = new HotelDoc(hotel); String json = JSON.toJSONString(hotelDoc); // 创新新增文档的Request对象 request.add(new IndexRequest("hotel") .id(hotel.getId().toString()) .source(json, XContentType.JSON)); } //3. 发送请求 client.bulk(request, RequestOptions.DEFAULT); }
利用mybatis-plus查询酒店数据
将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)
利用JavaRestClient中的BulkRequest批处理,实现批量新增文档
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。