ElasticSearch是一款Lucene开发的全文检索框架;在全文检索领域,Lucene其实已经是一款功能强大,性能好的搜索引擎库了,但是它复杂难懂的API,以及必须使用Java开发来将它集成到你的应用中,和很复杂的配置与使用都让人对它望而生畏;
因此ElasticSearch(es)就在Lucene的基础上做了很多的优化与补充应运而生;它不仅是一款开源免费的搜索引擎官方下载链接,而且在实现了lucene所有的索引和搜索功能外,还通过简单的RESTful API让使用变得更简单且易上手;
es优于lucene的核心特点在于:
- 分布式的实时文件存储,每个字段都可以被索引并被搜索; - 分布式的实时分析搜索引擎 - 可实现集群,能够扩展上百台服务器,处理PB级别的结构化/非结构化数据 - 更重要的一点是上手简单;安装即可使用;
ES作为这样一款在全文检索领域使用简单,功能强大的框架;被很多的公司和项目使用到;在处理大数据的检索中可以说是功能相当的牛逼;为了更好的学习ES,就要了解ES的索引库,了解它的索引库的创建,管理,查询;官方也提供了一个辅助管理工具Kibana,启动es后,再启动kibana就可以通过http://localhost:5601默认路径访问该工具;在里面通过Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便);
ES启动:bin/elasticsearch.bat
ES验证:http://localhost:9200
出现以下画面说明启动成功!
Kibana启动;bin\kibana.bat
访问:http://localhost:5601
RESTful单独看,这是一个面向资源的架构风格;可以理解就是使用url来暴露资源,但是不暴露对资源的操作;而资源操作(CRUD)的描述使用http的动词(GET,POST,PUT,DELETE)来表示;
简单理解:左边是常规方式;右边是RESTful风格的访问方式;
GET /rest/api/getDogs -> GET /rest/api/dogs 获取所有小狗狗
GET /rest/api/addDogs -> POST /rest/api/dogs 添加一个小狗狗
POST /rest/api/editDogs/12 -> PUT /rest/api/dogs/12 修改一个小狗狗
POST /rest/api/deleteDogs/12 -> DELETE /rest/api/dogs/12 删除一个小狗
RESTful的一个重点就是使用url暴露资源,利用http本身的语法语义来描述资源的操作;RESTful API就是使用了这两个特点,在Kibana管理ES索引库的时候使用这样的方式来操作;
通过Kibana可以使用RESTful API对索引库做管理;而在java中通过导入相关的jar包,也可以直接使用ES,管理它的索引库;
相关jar包:
<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>
Kibana操作:
POST index/type/id
index:索引库,类比数据库,这是文档存储的地方
type:类型,类比表格,代表对象的类
id:文档的唯一标识;不写默认自动生成
数据结构:json,在ES索引库的管理中,大量的使用到json格式;不管是文档数据的存放,还是后面的查询语句,都要使用到json格式;json格式现在也是被多数语言所支持,而且也是NOSQL领域的标准格式;
#创建表格并添加一条数据 POST crm/user/1 { "id":"2", "name":"fff", "age":23 }
执行响应结果:响应中会展示元数据(_index,_type,_id),版本号,结果等数据
{ "_index": "crm", "_type": "user", "_id": "1", "_version": 23, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": true }
-----------------------------------<<以上是kibana操作方式>>------------------------------
java中操作:这里就直接使用测试类伪数据做做测试了;
1.java操作其实与kibana操作的步骤是可以完全相同的;都必须保证es是启动状态(也就是一个服务),让后链接到ES,获得一个客户端对象;
/** * 建立与ElasticSearch的链接 * 将该方法作为一个工具方法,因为多索引库的所有增删改查都需要获取客户端对象 * @throws Exception */ public TransportClient getCreat() throws Exception { //获取客户端对象,与elasticSearch建立建立 return new PreBuiltTransportClient(Settings.EMPTY) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300)); }
2.创建索引库并添加数据;对比kibana中的操作,创建库的时候要指定index,type,id;因此在java中也要定义;kibana中数据采用json格式,而json格式在java中最好的替代就是map;
/** * 创建索引库并添加数据 * @throws Exception */ @Test public void creat() throws Exception { /* * 准备数据用来建库的时候使用;因为es中的操作是使用json数据操作;在kibana中操作时数据也是json格式; * 因此在java中就使用map来代替这样的数据格式 */ Map<String,Object> map = new HashMap<String, Object>(); map.put("id", 1);//这里的数据应该是数据库中查询的;该id和文档对象id要保持一致 map.put("name", "zs"); map.put("age", 23); map.put("adr", "cq"); //1.获得客户端对象 TransportClient client = getCreat(); //2.使用客户端对象创建索引库 String index = "crm";//索引库的名称,类似于数据库 String type = "user";//类似数据库中的表格 String id = "1";//文档id //获得一个索引库的请求建造者 IndexRequestBuilder indexRequestBuilder = client.prepareIndex(index, type, id); //3.使用方法发送这个请求就可以创建索引库;get方法就等于是kibana中的:POST,GET,PUT,DELETE;只是这里统一都使用get代替; //底层会做判断,判断是添加还是修改还是查询还是删除; IndexResponse indexResponse = indexRequestBuilder.setSource(map).get(); /** * 结果: * IndexResponse[index=crm,type=user,id=1,version=1,result=created, *shards={"_shards":{"total":2,"successful":1,"failed":0}}] */ System.out.println(indexResponse); //最后应该关闭客户端链接 client.close(); }
Kibana操作:
简单查询:
#查询数据 GET crm/user/1
响应结果:
{ "_index": "crm", "_type": "user", "_id": "1", "_version": 25, "found": true, "_source": { "id": "2", "name": "fff", "age": 23 } }
-----------------------------------<<以上是kibana操作方式>>------------------------------
java操作:1.获取链接对象;2.获取请求;3.发送请求
/** * 查询索引库 * kibana的操作: GET crm/user/1 * @throws Exception */ @Test public void getIndex() throws Exception { /** * 根据添加的操作方式;查询应该也是使用客户端对象来查询 */ GetRequestBuilder getRequestBuilder = getCreat().prepareGet("crm", "user", 1+""); //执行请求;也就是发送这个请求,使用get方法 GetResponse getResponse = getRequestBuilder.get(); /* * 注意这里得到的GetResponse 对象不能直接获得结果;在kibana中查询索引库得到的数据结果是包含 * 索引库信息和元数据的;而我们查询需要的数据是元数据;所以这里还需要getSource; */ System.out.println(getResponse.getSource());//{name=zs, id=1, age=23, adr=cq} }
Kibana操作:
删除索引库文档;文档存在与不存在响应结果不同;
#删除数据 DELETE crm/user/1
文档存在:
{ "found": true, "_index": "crm", "_type": "user", "_id": "1", "_version": 26, "result": "deleted", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
文档不存在:
{ "found": false, "_index": "crm", "_type": "user", "_id": "1", "_version": 1, "result": "not_found", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
-----------------------------------<<以上是kibana操作方式>>------------------------------
java操作:1.获取客户端对象;2.获得删除请求;3.执行请求获得响应;
/** * 删除索引库 * kibana删除:DELETE crm/user/1 * @throws Exception */ @Test public void deleteIndex() throws Exception { //获得客户端链接 TransportClient client = getCreat(); //使用客户端对象删除索引库 DeleteRequestBuilder deleteRequestBuilder = client.prepareDelete("crm", "user",1+""); //执行请求 DeleteResponse deleteResponse = deleteRequestBuilder.get(); /** * DeleteResponse[index=crm,type=user,id=1,version=2, * result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}] */ System.out.println(deleteResponse); //关闭客户端链接 client.close(); }
Kibana操作:
Kibana中修改文档数据的原理是先删除然后添加新的文档;因此Kibana中数据的更新分为全部更新和局部更新;数据如果不存在就创建
全部更新
数据存在的情况:
#修改数据;先删除后修改;数据不存在就创建 PUT crm/user/1 { "id":"2", "name":"xs", "age":23 }
响应结果:
{ "_index": "crm", "_type": "user", "_id": "1", "_version": 5, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": false }
数据不存在响应结果:注意看响应中的result;
{ "_index": "crm", "_type": "user", "_id": "1", "_version": 7, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": true }
局部更新
局部更新要使用到doc;doc中定义要修改的数据;它实现的效果虽然是可以只写要修改的数据,原有的数据不会被覆盖,但是底层其实也是先删除再新增的,只是在删除前保留了原有的数据;
#局部更新 POST crm/user/1/_update { "doc":{ "age":"13" } }
数据存在情况响应结果:
{ "_index": "crm", "_type": "user", "_id": "1", "_version": 8, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
数据不存在响应:会报异常;这是和全部更新的区别之一
{ "error": { "root_cause": [ { "type": "document_missing_exception", "reason": "[user][1]: document missing", "index_uuid": "9dBKouYZRGqWNlDI0Ug-PQ", "shard": "3", "index": "crm" } ], "type": "document_missing_exception", "reason": "[user][1]: document missing", "index_uuid": "9dBKouYZRGqWNlDI0Ug-PQ", "shard": "3", "index": "crm" }, "status": 404 }
-----------------------------------<<以上是kibana操作方式>>------------------------------
java操作:
java操作和Kibana有点区别是,java中没有局部更新和全部更新的区别,并且java中如果索引库不存在那么直接操作失败
/** * 修改索引库 * kibana: PUT crm/user/i {json数据} * 原理是先删除再添加;并且是全部修改 * @throws Exception */ @Test public void updateIndex() throws Exception { //获得客户端对象 TransportClient client = getCreat(); //准备数据 Map<String,Object> map = new HashMap<String, Object>(); map.put("id", 1); map.put("name", "ls"); map.put("age", 23); map.put("adr", "sc"); //获得请求对象 UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate("crm", "user", 1+""); /** * 这里要注意:1.与kibana不同的是,java中的索引库修改全部修改和局部修改是合并的; * 2.这里要使用setDoc设置数据; * 3.如果索引库不存在修改操作会失败;kibana客户端中则会自动创建索引库 */ UpdateResponse updateResponse = updateRequestBuilder.setDoc(map).get(); /** * 结果 * UpdateResponse[index=crm,type=user,id=1,version=4 * ,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}] */ System.out.println(updateResponse); //关闭客户端链接 client.close(); }
Kibana操作
Kibana批量操作要使用到_bulk
#批量的操作 POST _bulk { "create": { "_index": "itsource", "_type": "blog", "_id": "123" }} { "title": "我发布的博客" } { "index": { "_index": "itsource", "_type": "blog" ,"_id": "2"}} { "title": "我的第二博客" }
java操作
java中要操作数据批量存放到索引库中;
/** 1. 批量操作 2. 批量添加,kibana客户端: POST _bulk 3. @throws Exception */ @Test public void bulkIndex() throws Exception { //获得客户端链接 TransportClient client = getCreat(); //获得请求对象 BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); //执行批量操作;使用循环 for (int i = 0; i <20; i++) { //多个数据 Map<String,Object> map = new HashMap<String, Object>(); map.put("id", i);//这里的数据应该是数据库中查询的;该id和文档对象id要保持一致 map.put("name", "ls"); map.put("age", 2+i); map.put("adr", "sc"); BulkResponse response = bulkRequestBuilder .add(client.prepareIndex("crm", "user", i+"").setSource(map)) .get(); } //关闭客户端链接 client.close(); }
上面的操作都是ES索引库的基本增删改查的操作;重点当然还是在查询上,DSL(Domain Specific Language特定领域语言)正是ES提供一个查询语言;可以把它当作是和sql语句类似的语言;
最简单的查询所有:
Kibana操作:关键字_search;可以查询到crm索引库中所有的文档数据;
#查询所有 GET crm/_search
响应结果:注意我们查询需要的数据的数据结构;真正需要的数据其实是在hitis下的hitis中,而实际的数据还要在_source中找到;这也是java操作时获取数据要注意的结构;
{ "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 19, "max_score": 1, "hits": [ { "_index": "crm", "_type": "user", "_id": "0", "_score": 1, "_source": { "name": "ls", "id": 0, "age": 2, "adr": "sc" } }
-----------------------------------<<以上是kibana操作方式>>------------------------------
java操作
java操作查询索引库所有数据比较简单;就是注意获取主要数据的方法;
结合Kibana中查询获得数据结构分析到真实数据的位置,层层获取
/** * 查询所有索引库 * kibana:GET crm/_search * @throws Exception */ @Test public void getAllIndex() throws Exception { /** * 根据添加的操作方式;查询应该也是使用客户端对象来查询 */ SearchRequestBuilder searchRequestBuilder = getCreat().prepareSearch("crm"); SearchResponse searchResponse = searchRequestBuilder.get(); //获得总条数 System.out.println("查询总条数:"+searchResponse.getHits().getTotalHits()); /** * 根据kibana中操作获得的数据结果来获得数据;最终得到的这个hits是一个数组 * 从数组中可以得到想要的元数据 */ SearchHit[] hits = searchResponse.getHits().getHits(); for (SearchHit searchHit : hits) { System.out.println(searchHit.getSource()); } }
综合查询
DSL查询中有很多的关键字可以用来定义查询条件;还有DSL过滤,这就类似于筛选,sql中的where一样的效果;再简单一些的理解就如某东上的一个商品搜索的功能,就是全文检索很好的体现;
这样一个搜索中就可以基本概括所有的查询关键;
现在模拟几个简单的数据做一个综合查询;
查询数据中名字为ls,地址为cq,年龄在10-20之间的数据,并且分页显示每页两条,按年龄降序;
这里就涉及到单词查询,范围查询,分页,排序等;
Kibana的实现:
query关键字中主要是做条件查询;将一些条件定义到里面;过滤条件也可以定义到里面去;查询的方式有很多种,DSL这种语句还是比较灵活的;写法不止这一种,查询结果都是相同的;
#使用DSL查询并添加过滤条件 GET crm/user/_search { "query": { "bool": { "must": [ {"term": {"name": "ls"}} ], "filter": [{"term": {"adr": "sc"}}, {"range": { "age": { "gte": 10, "lte": 20 } }} ] } }, "from": 0, "size": 2, "sort": [ { "age": { "order": "desc" } } ] }
响应结果:两条数据,因为做了分页,并且年龄降序;
"hits": { "total": 11, "max_score": null, "hits": [ { "_index": "crm", "_type": "user", "_id": "18", "_score": null, "_source": { "name": "ls", "id": 18, "age": 20, "adr": "sc" }, "sort": [ 20 ] }, { "_index": "crm", "_type": "user", "_id": "17", "_score": null, "_source": { "name": "ls", "id": 17, "age": 19, "adr": "sc" }, "sort": [ 19 ] } ] }
java操作
可以完全根据Kibana的操作语句的结构方式,一层一层的写java;
需要注意的就是定义query的时候:
1. setQuery(QueryBuilder)方法需要的参数 是一个接口因此要使用实现类;
2. 使用实现类BoolQueryBuilder 因为根据kibana,query中定义bool
3. bool中定义must和filter;这里因为QueryBuilder中没有关于这两个对象的设置;所以要使用BoolQueryBuilder接收变量
4. must和filter中要设置单词查询,过滤条件,范围:这里add(QueryBuilder) 方法中应该是该接口,所以使用了接口的实现类
分页和排序是于query相同层的,所以单独设置
完整代码:
/** * 高级查询 * 查询数据中名字为ls,地址为sc,年龄在10-20之间的数据,并且分页显示每页两条,按年龄降序 * 方式:还是按照kibana中的步骤和所需对象一步一步的获取 * @throws Exception */ @Test public void queryIndex() throws Exception { //获取客户端 TransportClient client = getCreat(); //获得查询search的请求 SearchRequestBuilder searchRequestBuilder = client.prepareSearch("crm"); //获得query /** * 1. setQuery(QueryBuilder)方法需要的参数 是一个接口因此要使用实现类; * 2.使用实现类BoolQueryBuilder 因为根据kibana,query中定义bool * 3.bool中定义must和filter;这里因为QueryBuilder中没有关于这两个对象的设置;所以要使用BoolQueryBuilder接收变量 * 4.must和filter中要设置单词查询,过滤条件,范围:这里add(QueryBuilder) 方法中应该是该接口,所以使用了接口的实现类 */ /*BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();*/ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//获取方式二 //must List<QueryBuilder> must = boolQueryBuilder.must(); must.add(new TermQueryBuilder("name", "ls"));//条件name是ls //过滤 List<QueryBuilder> filter = boolQueryBuilder.filter(); filter.add(new TermQueryBuilder("adr", "sc"));//过滤条件1 filter.add(new RangeQueryBuilder("age").gte("10").lte("20"));//范围条件的设置 SearchRequestBuilder query = searchRequestBuilder.setQuery(boolQueryBuilder); //分页 searchRequestBuilder.setSize(4);//每页两条 searchRequestBuilder.setFrom(0);//开始位置 //排序; SortOrder order = SortOrder.DESC;//降序 searchRequestBuilder.addSort("age", order);//根据年龄降序 //执行请求 SearchResponse searchResponse = searchRequestBuilder.get(); //获得总条数 System.out.println("查询总条数:"+searchResponse.getHits().getTotalHits()); SearchHit[] hits = searchResponse.getHits().getHits(); for (SearchHit searchHit : hits) { System.out.println(searchHit.getSource()); } }
总结:java和Kibana的操作其实都是相互对应的;在java操作中重要的一个开始点都是从使用链接对象获取对应的操作的请求对象开始;因为RESTful API的操作风格,所以java也是这样;需要什么操作就获取对应的请求对象;然后再发送请求,有数据加数据,有条件设置条件,根据源码也可以一步一步的操作;
以上是ES的API的简单测试;具体的使用项目中再说;不过上手还是比较容易,java中的操作步骤都是大同小异的,重点还是在查询;不多说,大家都知道查询的重要性;