一.Docker安装Elasticsearch
- 非Docker安装请参考我的博客:https://blog.csdn.net/weixin_43934607/article/details/100538881
安装单节点Elasticsearch
-
拉取镜像
docker pull elasticsearch:6.5.4
-
创建容器
docker create --name elasticsearch --net host -e “discovery.type=single-node” -e “network.host=192.168.56.132” elasticsearch:6.5.4
-
启动
docker start elasticsearch
-
查看日志
docker logs elasticsearch
二.ik分词器
1.下载
- 下载6.5.4 版本ik分词器 并上传到虚拟机
链接:https://pan.baidu.com/s/1tppoVxIYzOGya9AlLx-Jxw
提取码:5uax
复制这段内容后打开百度网盘手机App,操作更方便哦
2.安装方式一
- 复制分词器压缩包到elastcisearch容器内部
docker cp elasticsearch-analysis-ik-6.5.4.zip elasticsearch:/usr/share/elasticsearch/plugins/
- 进入容器
docker exec -it elasticsearch /bin/bash
- 在elasticsearch/plugins/下创建ik文件夹 并把ik分词器复制到该目录然后解压
#创建 mkdir /usr/share/elasticsearch/plugins/ik #复制 cp /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-6.5.4.zip /usr/share/elasticsearch/plugins/ik #解压 unzip /usr/share/elasticsearch/plugins/ik/elasticsearch-analysis-ik-6.5.4.zip
- 重启容器即可
docker restart elasticsearch
3.安装方式二:挂载(推荐)
- 将IK的zip压缩包解压到 /data/es-cluster/ik
mkdir /data/es-cluster/ik cd /data/es-cluster/ik unzip elasticsearch-analysis-ik-6.5.4.zip
- 重新创建容器
docker create --name es --net host \ -v /data/es-cluster/ik:/usr/share/elasticsearch/plugins/ik \ elasticsearch:6.5.4
4.增加ik分词器内容
-
进入:{plugins}/elasticsearch-analysis-ik-*/config/
-
新建一个自定的词典
- 新建文件
vi my.dic
- 增加自定的词汇
杨某某 爱中国
-
编辑:IKAnalyzer.cfg.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">custom/mydict.dic;custom/single_word_low_freq.dic</entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords">custom/ext_stopword.dic</entry> <!--用户可以在这里配置远程扩展字典 --> <!--修改这里:把location改为自定词典名--> <entry key="remote_ext_dict">my.dic</entry> <!--用户可以在这里配置远程扩展停止词字典--> <entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry> </properties>
-
重启容器 查看分词结果
GET /_analyze
{ "analyzer":ik_max_word "text":"杨某某爱中国" }
三.拼音分词器
1.下载6.5.4版本拼音拼音分词器
- 插件源码地址:https://github.com/medcl/elasticsearch-analysis-pinyin
链接:https://pan.baidu.com/s/1qAFkRWPpRCGr2UFMyOasqQ
提取码:uya6
复制这段内容后打开百度网盘手机App,操作更方便哦
2.重新创建容器
#将zip压缩包,解压到/data/es-cluster/pinyin
unzip elasticsearch-analysis-pinyin-6.5.4.zip
#重新创建容器
docker stop es
docker rm es
docker create --name es
-v /data/es-cluster/ik:/usr/share/elasticsearch/plugins/ik
-v /data/es- cluster/pinyin:/usr/share/elasticsearch/plugins/pinyin
elasticsearch:6.5.4
3.创建映射使用拼音分词器
PUT /test(索引库名)/
{
"index" : {
"analysis" : {
"analyzer" : {
"pinyin_analyzer" : {
"tokenizer" : "my_pinyin"
}
},
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"keep_separate_first_letter" : false,
"keep_full_pinyin" : true,
"keep_original" : true,
"limit_first_letter_length" : 16,
"lowercase" : true,
"remove_duplicated_term" : true
}
}
}
}
"mappings": {
"person": {
"dynamic": false,
"properties": {
"name": {
"type": "text",
"analyzer":"ik_max_word",
"fields":{
"pinyin":{
"type": "text",
"analyzer": "pinyin_analyzer"
}
}
},
"image": {
"type": "keyword",
"index":false
}
}
}
}
}
- 拼音分词器字段说明:
- 这里拼音分词器是定义的 name 的子字段pinyin,通过属性fields指定。
- my_pinyin中的参数说明:
- keep_first_letter:启用此选项时,例如:刘德华> ldh,默认值:true
- keep_separate_first_letter:启用该选项时,将保留第一个字母分开,例如:刘德华> l,d,h,默认:假的,注意:查询结果也许是太模糊,由于长期过频
- keep_full_pinyin:当启用该选项,例如:刘德华> [ liu,de,hua],默认值:true
- keep_original:当启用此选项时,也会保留原始输入,默认值:false
- limit_first_letter_length:设置first_letter结果的最大长度,默认值:16
- lowercase:小写非中文字母,默认值:true
- remove_duplicated_term:当启用此选项时,将删除重复项以保存索引,例如:de的> de,默认值:false,注意:位置相关查询可能受影响
4.测试拼音分词器
GET /test/_analyze
{
"text": ["刘德华"],
"analyzer": "pinyin_analyzer"
}
#测试分词后的结果:
{
"tokens": [
{
"token": "刘德华",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "ldh",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "liu",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "de",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 1
},
{
"token":
"hua",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 2
}
]
}
- 分词后的这些结果都可会去做匹配查询 有命中的话都会被查到
5.查询
- 插入测试数据
POST /test/person/ { "name":"刘德华" }
- 使用拼音查询
GET /test/_search { "query":{ "match":{ "name.pinyin":{ "query":"ldh" } } } }
- 返回结果
5.SpringBoot怎么使用拼音分词器
SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(
/**
* 此处的有match 改为 multiMatchQuery
*/
QueryBuilders.multiMatchQuery(keyWord, "title", "title.pinyin").operator(Operator.AND)) // match查询
.withPageable(pageRequest)
/**
* 设置高亮 此处必须把子字段pinyin 也带上 不然会出现有的时候不会高亮显示
* /
.withHighlightFields(new HighlightBuilder.Field("title"),new HighlightBuilder.Field("title.pinyin"));
.build();
四.使用反射实现高亮显示
@Service
public class SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public static final Integer ROWS = 10;
public SearchResult search(String keyWord, Integer page) {
PageRequest pageRequest = PageRequest.of(page - 1, ROWS);
//设置分页参数
SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(
QueryBuilders.matchQuery("title", keyWord).operator(Operator.AND)) // match查询
.withPageable(pageRequest)
.withHighlightFields(new HighlightBuilder.Field("title")) // 设置高亮
.build();
AggregatedPage<HouseData> housePage =this.elasticsearchTemplate.queryForPage(searchQuery, HouseData.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<T> result = new ArrayList<>();
if (response.getHits().totalHits == 0) {
return new AggregatedPageImpl<> (Collections.emptyList(), pageable, 0L);
}
for (SearchHit searchHit : response.getHits()) {
/**
* 通过反射写入数据到对象中
* 因为es中的映射类型和实体类类型是一致的 所以可以直接使用反射
* /
T obj = (T) ReflectUtils.newInstance(clazz);
try {
//写入id ,参数 true 表示是否可以读取私有属性
FieldUtils.writeField(obj, "id", searchHit.getId(), true);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
for (Map.Entry<String, Object> entry : sourceAsMap.entrySet()) {
Field field = FieldUtils.getField(clazz, entry.getKey(), true);
if (null == field) {
continue;
}
try {
FieldUtils.writeField(obj, entry.getKey(), entry.getValue(), true);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 处理高亮
for (Map.Entry<String, HighlightField> stringHighlightFieldEntry : searchHit.getHighlightFields().entrySet()) {
try {
Text[] fragments = stringHighlightFieldEntry.getValue().fragments();
StringBuilder sb = new StringBuilder();
for (Text fragment : fragments) {
sb.append(fragment.toString());
}
FieldUtils.writeField(obj, stringHighlightFieldEntry.getKey(), sb.toString(), true);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
result.add(obj);
}
return new AggregatedPageImpl<>(result, pageable, response.getHits().totalHits);
}
});
return new SearchResult(housePage.getTotalPages(), housePage.getContent());
}
}
五.批量操作_bulk
在Elasticsearch中,支持批量的插入、修改、删除操作,都是通过_bulk的api完成的。
-
请求格式如下:(请求格式不同寻常)
注意最后一行的回车。{ action: { metadata }}\n { request body }\n { action: { metadata }}\n { request body }\n ...
-
批量插入数据:
注意最后一行的回车。{"create":{"_index":"haoke","_type":"user","_id":2001}} {"id":2001,"name":"name1","age": 20,"sex": "男"} {"create":{"_index":"haoke","_type":"user","_id":2002}} {"id":2002,"name":"name2","age": 20,"sex": "男"} {"create":{"_index":"haoke","_type":"user","_id":2003}} {"id":2003,"name":"name3","age": 20,"sex": "男"}
-
批量删除:
由于delete没有请求体,所以,action的下一行直接就是下一个action。{"delete":{"_index":"haoke","_type":"user","_id":2001}} {"delete":{"_index":"haoke","_type":"user","_id":2002}} {"delete":{"_index":"haoke","_type":"user","_id":2003}}
-
其他操作就类似了。
一次请求多少性能最高?
- 整个批量请求需要被加载到接受我们请求节点的内存里,所以请求越大,给其它请求可用的内存就越小。有一个最佳的bulk请求大小。超过这个大小,性能不再提升而且可能降低。
- 最佳大小,当然并不是一个固定的数字。它完全取决于你的硬件、你文档的大小和复杂度以及索引和搜索的负载。
- 幸运的是,这个最佳点(sweetspot)还是容易找到的:试着批量索引标准的文档,随着大小的增长,当性能开始降低,说明你每个批次的大小太大了。开始的数量可以在1000~5000个文档之间,如果你的文档非常大,可以使用较小的批次。
- 通常着眼于你请求批次的物理大小是非常有用的。一千个1kB的文档和一千个1MB的文档大不相同。一个好的批次最好保持在5-15MB大小间。
六.集群系统中深度分页
和SQL使用 LIMIT 关键字返回只有一页的结果一样,Elasticsearch接受 from 和 size 参数
- size: 结果数,默认10
- from: 跳过开始的结果数,默认0
如果你想每页显示5个结果,页码从1到3,那请求如下
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
- 应该当心分页太深或者一次请求太多的结果。结果在返回前会被排序。但是记住一个搜索请求常常涉及多个分片。每个分片生成自己排好序的结果,它们接着需要集中起来排序以确保整体排序正确。
为了理解为什么深度分页是有问题的,让我们假设在一个有5个主分片的索引中搜索。
- 当我们请求结果的第一页(结果1到10)时,每个分片产生自己最顶端10个结果然后返回它们给请求节点(requesting node),它再排序这所有的50个结果以选出顶端的10个结果。
- 现在假设我们请求第1000页——结果10001到10010。工作方式都相同,不同的是每个分片都必须产生顶端的10010个结果。然后请求节点排序这50050个结果并丢弃50040