- ES即为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,其第一个版本于2010年2月出现在GitHub上并迅速成为最受欢迎的项目之一。
- 首先,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎。
- ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful
- API来隐藏Lucene的复杂性,从而让全文搜索变得简单。 Lucene直接通过java
- API调用,而ES把这些API调用过程进行了的封装为简单RESTful请求,让我们调用起来更加简单.
- 不过,ES的核心不在于Lucene,其特点更多的体现为: 分布式的实时文件存储,每个字段都被索引并可被搜索 分布式的实时分析搜索引擎
- 可以扩展到上百台服务器,处理PB级结构化或非结构化数据 高度集成化的服务,你的应用可以通过简单的 RESTful
- API、各种语言的客户端甚至命令行与之交互。
- 上手Elasticsearch非常容易。它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它拥有开瓶即饮的效果(安装即可使用),只需很少的学习既可在生产环境中使用。
安装es
官方下载地址:https://www.elastic.co/downloads/elasticsearch
安装之后 运行 bin/elasticsearch.bat
访问:http://localhost:9200/
安装辅助管理工具Kibana5
① Kibana5.2.2下载地址:https://www.elastic.co/downloads/kibana
② 解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES
③ 启动Kibana5 : bin\kibana.bat
④ 默认访问地址:http://localhost:5601
Discover:可视化查询分析器
Visualize:统计分析图表
Dashboard:自定义主面板(添加图表)
Timelion:Timelion是一个kibana时间序列展示组件(暂时不用)
Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便)
Management:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。
ES数据管理
ES是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在ES中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。
ES使用Javascript对象符号(JavaScript Object Notation),也就是JSON,作为文档序列化格式。
在ES中将对象转化为JSON并做索引要比在表结构中做相同的事情简单的多。
一个文档(document)不只有数据。它还包含元数据(metadata)—关于文档的信息。三个必须的元数据节点是:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0gTNxEzN1cTM2ATMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
_index:索引库,类似于关系型数据库里的“数据库”—它是我们存储和索引关联数据的地方。
_type:类型,类似于关系型数据库中表
_id: 与 _index 和 _type 组合时,就可以在ELasticsearch中唯一标识一个文档。当创建一个文档,你可以自定义 _id ,也可以让Elasticsearch帮你自动生成。
另外还包括:_uid文档唯一标识(_type#_id)
_source:文档原始数据
_all:所有字段的连接字符串
ES数据的CRUD
传统数据库和ES的对应关系:
关系数据库(MYSQL) -> 数据库DB-> 表TABLE-> 行ROW-> 列Column
Elasticsearch -> 索引库Indices -> 类型Types -> 文档Documents -> 字段Fields
创建索引文档
①使用自己的ID创建:
PUT {index}/{type}/{id}
{
"field": "value",//json格式,K-V键值对
...
}
②ES内置ID创建:
POST {index}/{type}/
{
"field": "value",
...
}
①②ES响应内容:
{
"_index": "test",
"_type": "employee",
"_id": xxxxxx,
"_version": 1, //文档版本号
"created": true //是否新增
}
查询
③ 获取指定ID的文档
GET test/employee/123
③返回的内容:
{
"_index" : "test",
"_type" : "employee",
"_id" : "123",
"_version" : 1,
"found" : true,
"_source" : {
"email": "[email protected]",
"fullName": "皮蛋",
...
"joine_date": "2016-06-01"
}
}
返回文档的部分字段:
//GET默认返回整个文档,通过
GET /itsource/employee/123?_source=fullName,email
修改
更新整个文档
同PUT {index}/{type}/{id}
在响应中,我们可以看到Elasticsearch把 _version 增加了。
{
...
"_version" : 2,
"created": false
}
删除
DELETE {index}/{type}/{id}
存在文档的返回:
{
"found" : true,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 3
}
不存在的返回:
{
"found" : false,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 4
}
搜索全部文档
GET _search
分页搜索
Elasticsearch接受 from 和 size 参数:
size : 每页条数,默认 10。
from : 跳过开始的结果数,默认 0
GET test/qqq/_search?size=5&from=10 #查询11-15
等等方法详细的可以查看官方的API
这里就不多介绍了,
【ES的一个核心 分词器】
在全文检索理论中,文档的查询是通过关键字查询文档索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本串分词。
ES中分词需要对具体字段指定分词器等细节,因此需要在文档的映射中明确指出。
对于字段要想使用分词器,需要做以下两件事情:
-
集成合适分词器-
在服务端通过插件集成分词器
-
要设置字段的分词器
通过客户端告诉es某个字段要使用某个分词
ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成分词器。这里我使用的是IK分词器
ES的IK分词器插件源码地址:https://github.com/medcl/elasticsearch-analysis-ik
并将其内容放置于ES根目录/plugins/ik
ES的文档映射(mapping)机制用于进行字段类型确认(字段是什么类型,那个分词器),将每个字段匹配为一种确定的数据类型。
文档映射用来确认字段的类型和分词器等信息.映射分三种:默认映射,全局映射,自定义映射.
当添加一个文档字段,首先是否有自定义映射规则,再判断全局映射规则,如果最后都没有的化就使用默认规则.
默认映射<全局映射<自定义映射
ES的文档映射【根据实际开发需求再确定映射规则】
下面才是正题,使用java操作ES 进行一个简单的CRUD
public class EsTest {
private String index = "test";
private String type = "emps";
/**
* 1:测试创建客户端: 使用传输客户端:
*
* @throws Exception
*/
@Test
public void testGetClient() throws Exception {
// org.elasticsearch.common.settings.Settings
/*你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,
把集群中其它机器的ip地址加到客户端中,
这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,
它会自动帮你添加,并且自动发现新加入集群的机器*/
Settings setting = Settings.builder().put("client.transport.sniff", true).build();
// 1:获取TransportClient 客户端:
TransportClient client = new PreBuiltTransportClient(setting);
TransportAddress transportAddress = new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300);
client.addTransportAddress(transportAddress);
// [email protected]95d
System.out.println(client);
// 关闭
client.close();
}
public TransportClient getClient() throws Exception {
// org.elasticsearch.common.settings.Settings
Settings setting = Settings.builder().put("client.transport.sniff", true).build();
// 1:获取TransportClient 客户端:
TransportClient client = new PreBuiltTransportClient(setting);
TransportAddress transportAddress = new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300);
client.addTransportAddress(transportAddress);
return client;
}
/**
* 添加数据
*/
@Test
public void testIndex() throws Exception {
for (int i = 1; i <= 10; i++) {
// 1:获取客户端
TransportClient client = getClient();
// 2:添加列相当于使用管理工具的 PUT {index}/{type}/{id}
IndexRequestBuilder prepareIndex = client.prepareIndex(index, type, "1" + i);
// 添加数据
Map<String, Object> map = new HashMap<>();
map.put("id", 1 + i);
map.put("name", "皮蛋" + i);
//设置到Source中
prepareIndex.setSource(map);
// 发送请求
prepareIndex.get();
// 3:关闭
client.close();
}
}
/**
* 获取数据
*
* @throws Exception
*/
@Test
public void testGet() throws Exception {
// 1:获取客户端
TransportClient client = getClient();
// 2:做获取
GetResponse getResponse = client.prepareGet(index, type, "1").get();
Map<String, Object> source = getResponse.getSource();
System.out.println(source);
// 3:关闭
client.close();
}
/**
* 删除
*
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
// 1:获取客户端
TransportClient client = getClient();
// 2:删除
client.prepareDelete(index, type, "1").get();
// 3:关闭
client.close();
}
/**
* 更新
*
* @throws Exception
*/
@Test
public void testUpdate() throws Exception {
// 1:获取客户端
TransportClient client = getClient();
// 2:更新那一条
UpdateRequestBuilder prepareUpdate = client.prepareUpdate(index, type, "12");
// 设置更新的值
Map<String, Object> map = new HashMap<>();
map.put("id", 33);
map.put("name", "xxx3");
prepareUpdate.setDoc(map);
// 发送请求
prepareUpdate.get();
// 3:关闭
client.close();
}
/**
*ES的查询是通过执行json格式的查询条件,在java中就是构造QueryBuilder对象,
ES完全支持queryDSL风格的查询方式,QueryBuilder的构建类是QueryBuilders,
filter的构建类是FilterBuilders。
"query": {
"bool": {
"must": [
{"match": {"description": "search" }}
],
"filter": {
"term": {"age": "12"}
}
}
},
"_source": ["id","name"],
"from": 20,
"size": 10,
"sort": [{"join_date": "desc"},{"age": "asc"}]
}
* 搜索
* @throws Exception
*/
@Test
public void testSearcher() throws Exception {
//获取客户端
TransportClient client = getClient();
SearchRequestBuilder builder = client.prepareSearch(INDEX);//设置查询索引库
builder.setTypes(TYPE);//设置搜索类型
//1)dsl=查询+过滤
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.matchAllQuery());
//过滤
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
rangeQuery.lte(40);//小于等于
rangeQuery.gte(20);//大于等于
boolQuery.filter(rangeQuery); //年龄来过滤
builder.setQuery(boolQuery);//设置过滤得到的数据
//2)排序
builder.addSort("age", SortOrder.ASC);
//3)分页
builder.setFrom(10).setSize(10);
//4)截取字段
builder.setFetchSource(new String[]{"name","age"}, null);
//5)查询并打印结果
SearchResponse response = builder.get();
SearchHits hits = response.getHits();//总数+命中了哪些
System.out.println("总命中数:"+hits.totalHits());
SearchHit[] hits2 = hits.getHits();//命中了哪些
System.out.println("=============命中内容===================");
for (SearchHit searchHit : hits2) {
Map<String, Object> source = searchHit.getSource();
System.out.println(source);
}
System.out.println("=============命中内容===================");
//关闭
client.close();
}
}
如有错误的地方还请各位大佬指点