ElasticSearch
ES即为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案.
Es的索引库是基于apache Lucene。
通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单
特点:
分布式的实时文件存储,每个字段都被索引并可被搜索
分布式的实时分析搜索引擎
可以扩展到上百台服务器,处理PB级结构化或非结构化数据
区别:
Solr 利用 Zookeeper 进行分布式管理,支持更多格式的数据(HTML/PDF/CSV),官方提供的功能更多在传统的搜索应用中表现好于 ES,但实时搜索效率低。
ES自身带有分布式协调管理功能,但仅支持json文件格式,本身更注重于核心功能,高级功能多有第三方插件提供,在处理实时搜索应用时效率明显高于 Solr 。
安装ES
下载es
下载地址:https://www.elastic.co/downloads/elasticsearch
下载好了只需要解压出来,只需要运行elasticsearch.bat即可
访问:http://localhost:9200/ 显示出来东西就成功了
ES交互方式
ES和所有客户端的交互都是使用JSON格式的数据.
都是基于RESETful API。
Restful是一种面向资源的架构风格,可以简单理解为:使用URL定位资源,用HTTP动词(GET,POST,DELETE,PUT)操作。
GET 用来获取资源,
POST 用来新建资源(也可以用于更新资源),
PUT 用来更新资源,
DELETE 用来删除资源。
辅助可视化工具Kibana
下载地址:https://www.elastic.co/downloads/kibana
启动Kibana : bin\kibana.bat
Discover:可视化查询分析器
Visualize:图表
Dashboard:自定义主面板
Timelion:Timelion是一个kibana时间序列展示组件
Dev Tools :控制台
Management:管理索引库
ES的使用
_index:索引库,相等于数据库库名
_type:相等于数据库下面的表名
_id:索引
_source:文档原始数据
_all:所有字段的连接字符串
- 增
PUT {index}
比如 PUT crm
post也有添加的效果
post和put都有同样的效果
不过在创建索引库的时候,必须使用put。
其原理是先删除在添加 - 改
POST {index}/{type}/{id}
{
“field”:“value”
}
post和put也由修改的效果
如果在修改只要有数据的,就会修改
只需要看result的值
created 标识为 false 因为同索引、同类型下已经存在同ID的文档
脚本更新
使用_update
ctx._source指向当前被更新的文档
注意,目前的更新操作只能一次应用在一个文档上
局部更新
POST crm/user/1/_update
{
“doc”:{
“name”:“joker”,
“age”:12
}
}
- 删
DELETE {index}/{type}/{id}
删除成功
“found” : true
失败
“found” :false
但_version依旧增加了
- 查
GET {index}/{type}/{id}
查询所有
使用 _search
查询不要看元数据
使用 _source
- 批量操作bulk API
create:当文档不存在时创建
index:创建新文档或替换已有文档
update:局部更新文档
delete:删除一个文档
比如:
注意:delete后不需要请求体,最后一行要有回车
使用_mget批量获取
在同一索引库同一类型下
使用GET _search
查询所有索引库中的所有文档 - 分页
size : 每页条数
from : 从那页开始
GET crm/user/_search?size=2&from=1 - 排序
使用sort - DSL查询
es提供的查询语言叫做DSL
DSL事宜json请求体的形式出现。
query:查询组
bool:相遇where
match:关键字匹配
must:必须有
filter:过滤条件
match_all:匹配所有
multi_match :查询允许你做 match查询的基础上同时搜索多个字段
minimum_match:至少匹配个数,默认为1
单词搜索
term:一个单词
terms:多个单词
组合条件查询
使用bool
范围查询
使用range
gt:> gte:>= lt:< lte:<=
前匹配查询
使用prefix
通配符查询
*:多个 ?:一个
- 分词与映射
下载:https://github.com/medcl/elasticsearch-analysis-ik
解压出来,放到eclipse的插件文件plugins下面。
在测试一下:
IK分词器有两种类型分别是:
ik_smart:最粗粒度的拆分
ik_max_word:将文本做最细粒度的拆分
映射:
查看映射:GET {indexName}/{typeName}/_mapping
默认映射 默认类型
Boolean: true or false “boolean”
Whole number: 123 “long”
Floating point: 123.45 “float”
String, valid date:“2018-09-15” “date”
String: “as r” “text”
字段映射的常用属性:
type:基本的数据类型
include_in_all:是否将该字段值组合到_all中
all : 虚拟字段,每个文档都有该字段,代表所有字段的组合信息,作用方便直接对
store:是否存储:默认为false
index:索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引)
analyzer:索引分词器:索引创建时使用的分词器
search_analyzer:搜索分词器:搜索该字段的值时,传入的查询内容的分词器
针对单个类型的映射:
注意:在创建索引的时候就要指定映射的类型
针对多个类型的映射:
对象及数组类型映射
映射:
注意:Lucene不理解对象,一个lucene文档包含键值对的一个扁平化。
数组
配置:
对象数组
映射:
扁平化:
全局映射:
可以通过默认方式和动态模板来实现。
默认方式:default
索引下面的都继承了_default。没写就是默认继承了的。
动态模板:dynamic_templates
最佳实践:
① 配置全局动态模板映射(覆盖默认的string映射)
② 配置自定义字段映射(由于基本类型主要用于过滤和普通查询,因此,字段映射主要对需要全文检索的字段进行配置)
③ 创建、更新和删除文档
④ 搜索 - java操作ES
准备一个项目
导包
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
连接es获取到客户端
//获取客户端
public TransportClient getClient() throws Exception {
// 启动客户端
TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
return client;
}
增加数据
//增加
@Test
public void testName() throws Exception {
//构建请求对象
IndexRequestBuilder prepareIndex = getClient().prepareIndex("crm", "user", "1");
//请求数据
Map<String,Object> map=new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "嘻嘻");
map.put("age", 12);
map.put("country", "cn");
//响应数据
IndexResponse indexResponse = prepareIndex.setSource(map).get();
System.out.println(indexResponse);
}
在去kibana 控制台,查询一下添加的数据
修改数据
//修改
@Test
public void testName2() throws Exception {
//构建请求对象
UpdateRequestBuilder prepareUpdate = getClient().prepareUpdate("crm", "user", "1");
//请求数据
Map<String,Object> map=new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "嘻嘻");
map.put("age", 20);
map.put("country", "cn");
//响应数据
UpdateResponse updateResponse = prepareUpdate.setDoc(map).get();
System.out.println(updateResponse);
}
再去控制台查询一下。结果发现age成20了
删除数据
//删除
@Test
public void testName3() throws Exception {
//构建请求对象
DeleteRequestBuilder prepareDelete = getClient().prepareDelete("crm", "user", "1");
//响应数据
DeleteResponse deleteResponse = prepareDelete.get();
System.out.println(deleteResponse);
}
回到kibana控制台查询一下,结果发现已经查询不到了
如果有数据就修改,没数据就添加
//有就修改,没有就添加
@Test
public void testName4() throws Exception {
Map<String,Object> map=new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "哈哈");
map.put("age", 21);
map.put("country", "cn");
//创建索引
IndexRequest indexRequest = new IndexRequest("crm", "user", "6").source(map);
//更新操作
UpdateRequest updateRequest = new UpdateRequest("crm", "user", "6").doc(map).upsert(indexRequest);
//响应数据
UpdateResponse updateResponse = getClient().update(updateRequest).get();
System.out.println(updateResponse);
getClient().close();
}
再去kibana控制台查询一下,就发现数据有了,在把age改成21,运行一下,就发现数据改变了
根据id查询
//根据id查询
@Test
public void testName5() throws Exception {
//构建查询对象
GetRequestBuilder prepareGet = getClient().prepareGet("crm", "user", "6");
//响应查询
GetResponse getResponse = prepareGet.get();
System.out.println(getResponse.getSource());
}
批量操作
添加
//批量添加操作
@Test
public void testName6() throws Exception {
//构建批量对象
BulkRequestBuilder prepareBulk = getClient().prepareBulk();
Map<String,Object> map=new HashMap<String, Object>();
//批量添加数据
for (int i = 0; i < 10; i++) {
map.put("id", i);
map.put("name", "哈哈");
map.put("age", 21+i);
map.put("country", "cn");
prepareBulk.add(getClient().prepareIndex("crm", "vip", i+"").setSource(map));
}
//执行
BulkResponse bulkResponse = prepareBulk.get();
if(bulkResponse.hasFailures()){
System.out.println("错了");
}
}
查询
//查询名字哈哈 中国的 年龄 [25-30] 从第一页开始 每页显示2条 根据年龄降序
@Test
public void testName7() throws Exception {
//排序对象
SortBuilder sort=new FieldSortBuilder("age");
sort.order(SortOrder.DESC);
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//must匹配
boolQueryBuilder.must(new MatchQueryBuilder("name", "哈哈"));
//构建过滤条件对象
List<QueryBuilder> filter = boolQueryBuilder.filter();
filter.add(new TermQueryBuilder("country", "cn"));
filter.add(new RangeQueryBuilder("age").gte(25).lte(30));
//构建查询对象
SearchResponse searchResponse = getClient()
.prepareSearch("crm").setTypes("vip")
.setSize(2).setFrom(0).addSort(sort).setQuery(boolQueryBuilder).get();
//得到结果
SearchHits hits = searchResponse.getHits();
//得到总条数
System.out.println("总条数:"+hits.getTotalHits());
//编辑所有查询到的数据
for (SearchHit searchHit : hits.getHits()) {
System.out.println(searchHit.getSource());
}
}