Elasticsearch分布式搜索介绍
部分资料来自与黑马程序员公开课程
何为Elasticsearch
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:

而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

倒排索引
倒排索引中有两个非常重要的概念:
- 文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
- 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理,流程如下:
- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
如图:

倒排索引的搜索流程如下(以搜索”华为手机”为例):
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
4)拿着文档id到正向索引中查找具体文档。
如图:

虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
倒排索引就是按照关键词定义文档(数据)id
Mysql和Es概念对比
Es在存储数据(文档)时,其存储形式为json型
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 |
安装ES和Kibana
安装过程网上有很多,此处不多做赘述,利用docker容器可以很容易构建。
docker启动命令:
1
| 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
|
访问默认端口9200可以看到下面的样子

Kibana启动命令
1
| docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://es:9200 --network=es-net -p 5601:5601 kibana:7.12.1
|
注意kibana版本要和es版本保持一致,然后要在kibana中指定es端口
安装并启动kinaba后就可以打开他的可视化页面

查看分词器
使用Kibana自带的工具可以发送DSL请求
1 2 3 4 5
| POST /_analyze { "text": "我是林峰我爱玩原神", "analyzer": "chinese" }
|
得到分词结果

显然不符合我们中文的分词规则
安装IK分词器
可以选择在线安装,或者是离线下载好后自己找到es插件目录,给他塞进去
在线:
1 2 3 4 5 6 7 8 9 10
| # 进入容器内部 docker exec -it elasticsearch /bin/bash
# 在线下载并安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
#退出 exit #重启容器 docker restart elasticsearch
|
离线:
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
1
| docker volume inspect es-plugins
|
显示结果:
{
"CreatedAt": "2023-12-27T15:12:44+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
然后进入到所给的目录中,将下载并解压好的IK文件放进去即可
现在再回去验证一下分词结果
1 2 3 4 5
| POST /_analyze { "text": "林峰爱玩游戏", "analyzer": "ik_smart" }
|
ik分词器有两种模式:
ik_smart
:最少切分
ik_max_word
:最细切分
ik_smart:

ik_max_word:

分词器扩展
找到ik/config/IKAnalyzer.cfg.xml,在其中添加我们扩展的ext.dic和拦截词stopword.dic
1 2 3 4 5 6 7 8 9 10 11
| <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict">ext.dic</entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords">stopwords.dic</entry> <!--用户可以在这里配置远程扩展字典 --> <!-- <entry key="remote_ext_dict">words_location</entry> --> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties>
|
这个ext.dic和stopwords.dic需要自己配置
举个例子:
ext.dic:

stopword.dic:

配置完成后重启,然后回kibana试试
1 2 3 4 5
| POST /_analyze { "text": "我是林峰我爱玩原神爱吃奥利给", "analyzer": "ik_smart" }
|
完美
索引库操作
常见mapping类型
mapping是对索引库中文档的约束,常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
创建索引库语法
举例
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
- properties: 字段
格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| PUT /索引库名称 { "mappings": { "properties": { "字段名":{ "type": "text", "analyzer": "ik_smart" }, "字段名2":{ "type": "keyword", "index": "false" }, "字段名3":{ "properties": { "子字段": { "type": "keyword" } } }, } } }
|
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| PUT /test01 { "mappings": { "properties": { "info": { "type": "text", "analyzer": "ik_smart" }, "email": { "type": "keyword", "index": false }, "name": { "type": "object", "properties": { "firstname": { "type": "keyword" }, "lastname":{ "type": "keyword" } } } } } }
|
介绍
info:字符串类型,可分割,分词器是ik_smart,默认可搜索(index为true)
email:关键词类型,不可分割,不搜索
name:对象类型,存放字段,默认可搜索
查询,删除,修改库
索引库的操作完全符合restful风格,即获取为get,创建put(这个好像不太符合),删除 delete,索引库不允许直接修改字段,只允许添加字段
查询:
从put改为get
GET /test01
删除
DELETE /test01
修改(添加age字段)
1 2 3 4 5 6 7 8
| PUT /test01/_mapping { "properties":{ "age": { "type": "integer" } } }
|
再通过get查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| { "test01" : { "aliases" : { }, "mappings" : { "properties" : { "age" : { "type" : "integer" }, "email" : { "type" : "keyword", "index" : false }, "info" : { "type" : "text", "analyzer" : "ik_smart" }, "name" : { "properties" : { "firstname" : { "type" : "keyword" }, "lastname" : { "type" : "keyword" } } } } },
|
文档操作
添加(post)
语法:
1 2 3 4 5 6 7 8 9 10
| POST /索引库名/_doc/文档id { "字段1": "值1", "字段2": "值2", "字段3": { "子属性1": "值3", "子属性2": "值4" }, }
|
例子:
1 2 3 4 5 6 7 8 9 10
| POST /test01/_doc/1 { "age":"21", "info":"安理计科林峰", "name": { "firstname":"林", "lastname":"峰" }, "email":"827498@qq.com" }
|
一定不要漏”,“
查询(get)
GET /test01/_doc/1
结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "_index" : "test01", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 0, "_primary_term" : 1, "found" : true, "_source" : { "age" : "21", "info" : "安理计科林峰", "name" : { "firstname" : "林", "lastname" : "峰" }, "email" : "827498@qq.com" } }
|
修改(put/post)
修改有两种方式:
- 全量修改:直接覆盖原来的文档(put)
- 增量修改:修改文档中的部分字段(post)
全量修改
全量修改是覆盖原来的文档,其本质是:
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
1 2 3 4 5 6 7
| PUT /{索引库名}/_doc/文档id { "字段1": "值1", "字段2": "值2", }
|
增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
1 2 3 4 5 6
| POST /{索引库名}/_update/文档id { "doc": { "字段名": "新的值", } }
|
实例:
1 2 3 4 5 6
| post /test01/_update/1 { "doc":{ "age":25 } }
|
再查看一下结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "_index" : "test01", "_type" : "_doc", "_id" : "1", "_version" : 2, "_seq_no" : 1, "_primary_term" : 1, "found" : true, "_source" : { "age" : 25, "info" : "安理计科林峰", "name" : { "firstname" : "林", "lastname" : "峰" }, "email" : "827498@qq.com" } }
|
age成功修改,且版本号加1(自带版本控制)
ps:其实上面的没什么用,因为我们最终肯定是要用java控制DSL
RestClient操作索引库
使用RestClinet
引入es依赖
1 2 3 4 5 6 7 8 9 10 11
| <properties> <java.version>1.8</java.version> <elasticsearch.version>7.12.1</elasticsearch.version> </properties> <dependencies>
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency>
|
初始化RestClient对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class HotelTest {
private RestHighLevelClient client;
@BeforeEach void setUp(){ this.client = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.10.128:9200") )); }
@AfterEach void tearDown() throws IOException { this.client.close(); }
}
|
创建索引库
- 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
- 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
- 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
1 2 3 4 5 6 7 8 9
| @Test void creatIndex() throws IOException { CreateIndexRequest request = new CreateIndexRequest("hotel");
request.source(MAPPING_TEMPLATE, XContentType.JSON);
client.indices().create(request, RequestOptions.DEFAULT);
}
|
此处的MAPPING_TEMPLATE作为一个静态变量放在别处了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class hotel_json { 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" + " \"copy_to\": \"all\"\n" + " },\n" + " \"starName\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"business\":{\n" + " \"type\": \"keyword\"\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" + "}"; }
|
回到浏览器确认一下,发现成功创建index
删除索引库
举一反三,删除索引库无非就是把创建请求转变为删除请求,且除了库名不需要其他参数
1 2 3 4 5 6 7
| @Test void deleteIndex() throws IOException { DeleteIndexRequest request = new DeleteIndexRequest("hotel");
client.indices().delete(request,RequestOptions.DEFAULT); }
|
返回浏览器测试,删除成功

判断是否存在
使用get请求
1 2 3 4 5 6 7 8 9
| @Test void testExistsHotelIndex() throws IOException { GetIndexRequest request = new GetIndexRequest("hotel"); boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); System.err.println(exists ? "索引库已经存在!" : "索引库不存在!"); }
|
往索引库里添加数据
注意如果是索引库操作,那调用的是client的indices,如果是文档操作,则调用的是client.index
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test void AddHotelTest() throws IOException {
Hotel hotel = hotelService.getById(36934L); HotelDoc hotelDoc = new HotelDoc(hotel);
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
client.index(request, RequestOptions.DEFAULT); }
|
回网页鉴定一下,确实没问题

按id获取索引库中的值
1 2 3 4 5 6 7 8 9 10 11
| @Test void getHotelByIdTest() throws IOException {
GetRequest request = new GetRequest("hotel", "36934");
GetResponse response = client.get(request, RequestOptions.DEFAULT);
String hotel = response.getSourceAsString();
System.out.println(hotel); }
|

更新文档数据
之前有说更新文档存在两种形式,一是直接删除原来的,重新加入一条新的。二是在原来的基础上修改
此处演示第二种
调用的是client的update方法,update必然要提供要修改的键还有他的新值,这些都放在updaterequest的doc中,每两个作为一对键值对
1 2 3 4 5 6 7 8 9 10 11 12
| void updateByIdTest() throws IOException {
UpdateRequest request = new UpdateRequest("hotel", "36934");
request.doc(
"brand","666天酒店", "city","淮南" );
client.update(request,RequestOptions.DEFAULT); }
|
修改成功

删除文档
太简单,不多说了
1 2 3 4 5 6
| @Test void deleteById() throws IOException { DeleteRequest request = new DeleteRequest("hotel", "36934");
client.delete(request,RequestOptions.DEFAULT); }
|
批量操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test void testBulkRequest() throws IOException { List<Hotel> hotels = hotelService.list();
BulkRequest request = new BulkRequest(); for (Hotel hotel : hotels) { HotelDoc hotelDoc = new HotelDoc(hotel); request.add(new IndexRequest("hotel") .id(hotelDoc.getId().toString()) .source(JSON.toJSONString(hotelDoc), XContentType.JSON)); } client.bulk(request, RequestOptions.DEFAULT); }
|
DSL批量插叙指令:
GET /_hotel/_search
DSL查询语句
DSL分类
- 查询所有:查询出所有数据
- 全文检索:利用分词器将用户输入分词,然后从索引库中查询
- match_query
- multi_match_query
- 精确查找:根据精确词条查找
查询所有
1 2 3 4 5 6 7 8
| GET /indexName/_search { "query": { "match_all": { } } }
|
全文检索
match查询语法如下:
1 2 3 4 5 6 7 8
| GET /indexName/_search { "query": { "match": { "FIELD": "TEXT" } } }
|
mulit_match语法如下:
1 2 3 4 5 6 7 8 9
| GET /indexName/_search { "query": { "multi_match": { "query": "TEXT", "fields": ["FIELD1", " FIELD12"] } } }
|
精确查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:
- term:根据词条精确值查询
- range:根据值的范围查询
语法说明:
1 2 3 4 5 6 7 8 9 10 11
| GET /indexName/_search { "query": { "term": { "FIELD": { "value": "VALUE" } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| GET /indexName/_search { "query": { "range": { "FIELD": { "gte": 10, "lte": 20 } } } }
|
RestClient查询
查询所有 + 查询内容处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void testMatchAll() throws IOException {
SearchRequest request = new SearchRequest("test01");
request.source().query(QueryBuilders.matchAllQuery());
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
long value = hits.getTotalHits().value;
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) { String s = hit.getSourceAsString(); System.out.println(s); }
}
|

match查询
RestClient查询整体代码流程没有区别,不同的查询方式之间只有QueryBuilder不一样,下面再拿match举例,直接借用machall代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void testMatchAll() throws IOException {
SearchRequest request = new SearchRequest("test01");
request.source().query(QueryBuilders.matchQuery("all","林"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
long value = hits.getTotalHits().value;
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) { String s = hit.getSourceAsString(); System.out.println(s); }
}
|
精确查询
多余代码省略
1 2 3 4 5
| QueryBuilders.termQuery("gender","男");
QueryBuilders.rangeQuery("price").gte(100).lte(150);
|
复合查询
1 2 3 4 5 6 7
| BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
|
排序分页
1 2 3
| request.source().sort("age", SortOrder.ASC); int page = 2,size = 1; request.source().from((page - 1) * size).size(1);
|
高亮
1
| request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
|
数据聚合
聚合种类
聚合常见的有三类:
DSL聚合语法(bucket)
可以对数据进行一个分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| get /hotel/_search { "query": { // 限定条件 "range": { "price": { "lte": 200 } } }, "size": 0, // 显示多少条数据 "aggs": { // 聚合 "brandAgg": { "terms": { "field": "brand", // 参与聚合的字段 "order": { "_count": "desc" // 排序方式 }, "size": 10 // 显示多少bucket(桶) } } } }
|
DSL聚合语法(Metric)
结合上一个简单聚合使用,可以在每一个bucket中查找最大,最小,平均等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| get /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "order": { "scoreAgg.avg": "asc" }, "size": 10 }, "aggs": { // 在基础聚合基础上 "scoreAgg":{ "stats": { "field": "score" } } } } }
|
RestClient实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void testAggregation() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().size(0); request.source().aggregation( AggregationBuilders.terms("brandAgg") .field("brand") .size(10) );
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations(); Terms brandTerms = aggregations.get("brandAgg");
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets(); for(Terms.Bucket bucket : buckets){ String s = bucket.getKeyAsString(); System.out.println(s); }
}
|
自动补全
拼音分词器
拼音分词器的官方地址:https://github.com/medcl/elasticsearch-analysis-pinyin
下载完毕后,将其解压放入es插件的本地挂载目录中即可

演示根据拼音分词
1 2 3 4 5
| post /_analyze { "text":["林峰学分布式"], "analyzer": "pinyin" }
|
得到的部分结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| { "tokens" : [ { "token" : "lin", "start_offset" : 0, "end_offset" : 0, "type" : "word", "position" : 0 }, { "token" : "lfxfbs", "start_offset" : 0, "end_offset" : 0, "type" : "word", "position" : 0 }, { "token" : "feng", "start_offset" : 0, "end_offset" : 0, "type" : "word", "position" : 1 }, { "token" : "xue", "start_offset" : 0, "end_offset" : 0, "type" : "word", "position" : 2 }, ……
|
自定义分词器
elasticsearch中分词器(analyzer)的组成包含三部分:
- character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
- tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
- tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等
我们可以自定义这三部分,可以让这三部分分别使用不同的分词器
比如在tokenizer阶段,我们指定使用ik分词器,然后在tokenizer filter阶段,我们选用pinyin分词器,这样出来的结果和直接使用拼音分词器的不同之处在于,拼音分词器会将ik分词器的结果转换为拼音(比如苹果,直接使用拼音分词器结果是[“ping”,”guo”],而使用自定义分词器,结果为[“pingguo”])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| PUT /test { "settings": { "analysis": { "analyzer": { "my_analyzer": { "tokenizer": "ik_max_word", "filter": "py" } }, "filter": { "py": { "type": "pinyin", "keep_full_pinyin": false, "keep_joined_full_pinyin": true, "keep_original": true, "limit_first_letter_length": 16, "remove_duplicated_term": true, "none_chinese_pinyin_tokenize": false } } } } }
|
自动补全
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| PUT test { "mappings": { "properties": { "title":{ "type": "completion" } } } }
POST test/_doc { "title": ["Sony", "WH-1000XM3"] } POST test/_doc { "title": ["SK-II", "PITERA"] } POST test/_doc { "title": ["Nintendo", "switch"] }
POST /test/_search { "suggest": { "title_suggest": { "text": "s", "completion": { "field": "title", "skip_duplicates": true, "size": 10 } } } }
|
数据同步
es里存放的数据索引一般仅用作查询使用,其数据来源于数据库,当数据库中的数据遭到增删改查时,es中的数据并不会自动地同步修改,因此需要我们通过一些方法来同步修改es和数据库中的数据。
三种方法
法一:
同步调用:在修改数据库时,同步调用es的更新服务接口,这样每次修改都同时进行,三个步骤依次执行,缺点会导致业务耦合,整个业务流程被拉长,在更新完数据库后整个进程还要等待es更新,且如果其中某个业务出现问题,整个流程都会出现问题
法二:
异步调用,运用MQ信息通道,将法一调用服务接口的过程转变为在通道中发送消息,然后由es的更新服务接收消息,并更新es索引库,这样整个过程就将更新数据库和更新索引库分开了,且两者之间是异步的关系,缺点是复杂度略有提高
法三;
利用mysql自带的binlog,binlog是mysql自带的一个日志文件,对数据的增删改查都会在binlog中进行记录,然后使用canal中间件监听binlog的变化,然后再同步修改es。
推荐第二种
es集群
集群的目的:解决海量存储问题还有单点故障问题