为什么要有搜索引擎?
在传统的数据库中,如果想实现搜索一般我们会用like方式,但遇到不是完全匹配或有差异的的就搜索不到了。
而且传统的数据库实现搜索不靠谱,性能差、效率低。
什么是Elasticsearch?
Elasticsearch是一个基于Lucene的搜索服务器,他提供了一个分布式多用户能力的全文搜索引擎,基于RESTful Web接口。
Elasticsearch是用Java开发的,隐藏了复杂性,开箱即用,并设计用于云计算,能够达到实时搜索,稳定、可靠、快速、安装方便(插件可不方便),不仅能对海量规模的数据完成分布式索引和检索,还能提供数据聚合分析。
Lucene就是一个jar包里面有着大量各种建立倒排索引,以及进行搜索的代码、算法等。
什么是全文检索?
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立好的索引进行查找,并将查询结果反馈给用户的检索方式,这个过程类似于通过字段中的检索字表查字的过程。
也就是对全文数据进行词、字、段落等更深层次的编辑、加工,将非结构化的数据一部分信息提取出来,重新组装使得其有一定的结构,然后对此结构的数据进行搜索。
什么是倒排索引?
根据分词器对全文数据进行分词,对分完后的词进行标识,这个词在哪里出现过,出现了几次,再次查找的时候就根据位置去获取完整的数据。
倒排索引包括:文档的列表、数量,及词条在每个文档中出现的次数,出现的位置,每个文档的长度,所有文档的平均长度。
分词器
分词器就是从一段文本切分出一个一个的词条,并对词条进行标准化处理。
分词步骤
预处理:过滤掉HTML标签,特殊符号转换等
分词:按照分词器的算法进行分词
标准化:对分词后的词进行统一归档处理
Elasticsearch内置分词器
Standard分词器(默认):会将词汇单元转换为小写形式,并去掉停用词和标点符号,支持中文采用的方式为单字切分。
Simple分词器:首先会通过非字母字符来分割文本信息,然后将词汇单元统一为小写形式,该分词器会去掉数字类型的字符。
Whitespace分词器:只是去除空格,对字符没有lowcase化,不支持中文,并不对生成的词汇单元进行其他的标准化处理。
Language分词器:特定语言的分词器,不支持中文。
IK分词器(不是内置的分词器):对中文切分较好,通常会使用这个。
特点
1.大型分布式集群(数百台机器),处理PB级的数据
2.开箱即用,非常简单,操作也不复杂
3.数据库的功能面对很多领域是不够用的(事务、联机事务型操作、特殊的功能、全文检索、同义词处理、相关度排名、海量数据近实时处理等),Elasticsearch作为数据库的一个补充,提供了数据库所不能提供的很多功能。
Elasticsearch的麻烦在于插件,Elasticsearch的部分插件跟不上Elasticsearch的版本。
4.近实时,从写入数据到数据可以被搜索到有一个延迟(大约1秒)。基于Elasticsearch执行搜索和分析可以达到秒级。
Elasticsearch核心概念
Cluster(集群):集群包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定,只要配置文件内的集群名称一致,内部会自动的去寻找,会自动组成集群。
Node(节点):集群中的单个节点,节点也有默认名称(默认是随机分配的),默认节点会加入一个名为elasticsearch的集群,如果直接启动一堆节点,他们会自动组成一个elasticsearch集群,一个节点也可以组成集群。
数据库 | Elasticsearch | Elasticsearch描述 |
database(库) | index (索引) | 一个索引通常代表一类数据 |
table(表) | type(类型) | Elasticsearch在index中建立type,通过mapping进行映射 |
column (列) | field(字段) |
|
row(行) | document(文档) | Elasticsearch存储是文档形的,相当于数据库的行 |
constraint(约束) | mapping(映射) | 创建index时,可以预先定义好字段的类型和相关属性,这样就可以自动进行类型转换,如:字符串转日期 |
SQL(数据操作语句) | Query DSL(数据操作语句) | GET/PUT/POST/DELETE 对应数据库 select/update/delete |
index(索引) | indexed(索引) | Elasticsearc默认是加上索引,触发特殊指定不建立索引 |
架构
Gateway层:是Elasticsearch用来存储索引文件的一个文件系统,主要用来对数据进行长持久化以及整个集群重启后可以通过Gateway重新恢复数据,且支持很多类型,例如:本地磁盘、共享存储、hadoop的hdfs分布式存储、亚马逊的S3。
Dstributed Lucene Directory:Gateway上层就是Lucene的分布式框架,Lucene是做检索的,但是他是一个单机的搜索引擎,像Elasticsearch这种分布式搜索引擎系统,虽然底层使用Lucene,但是需要再每个节点上都运行Lucene进行相应的索引、查询、以及更新等操作,就需要做成一个分布式的运行框架来满足需求。
Dstributed Lucene Directory以上的是ES一些模块。
Index Module:索引模块,对数据建立索引,也就是倒排索引。
Search Module:搜索模块,对数据进行查询搜索。
Mapping:数据映射与解析模块,就是数据的每个字段可以根据建立的表结构进行映射、解析,如果没有建立表结构,Elasticsearch会根据数据类型推测出数据结构后,自己生成一个Mapping,然后根据这个Mapping去解析数据。
River:此模块在Elasticsearch-2.0应该是被取消了,就是可以通过一些自定义的脚本将传统的数据库等数据源通过格式化转换后同步到Elasticsearch集群。
Discovery:Elasticsearch是一个额集群有很多节点,很多节点需要互相发现对方,然后组成一个集群,包括选举主节点,这些Elasticsearch都是用Discovery模块,默认是使用Zen,也可以是使用EC2。
Script:Elasticsearch查询还可以支持多种基本语言,包括mvel、js、python等。
Transport:协议层,支持的也比较多,Thrift、Memcached、Http等,默认是Http。
JMX:java的一个远程监控管理框架,Elasticsearch是使用java实现的。
RESTful:接口层,官方推荐使用RESTful接口,直接发送http情况,方便后续的nginx做代理、分发,包括权限的管理是,通过http就很方便做了,如果使用java客户端是直接调用Api,在做负载均衡及权限管理还是不太好做。
内部架构解析
Master节点
主要职责是和集群操作相关的内容,如创建或删除索引,跟跟踪哪些节点是集群的衣服,决定哪些分片分配给相关的节点,稳定的主节点对集群的监控非常重要。
节点对等
每个节点都能接收请求
每个节点接收到请求后都能把该请求路由到有相关的数据的其他节点上。
接收原始请求的节点负责采集数据并返回给客户端
文档数据路由原理
1.文档路由到分片上,一个索引由多个分片构成,当增删改一个文档时,Elasticsearch就需要决定存储在哪个分片上。
2.路由算法:shard=hash(routing) % number_of_pirmary_shards
1.每次增删改查的时候,都有一个路由值,默认是文档的_id值
2.对这个路由值使用哈希函数进行计算
3.计算出的值在和分片个数取余数,这个时候就确定了在哪个分片。
3. 算法决定了,主分片个数一旦确定就不能修改了。
分片和副本机制
1.index包含多个分片。
2.每个分片都是一个最小的工作单元,承载部分数据,每个分片都是一个lucene实例,有完整的建立索引和处理请求的能力。
3.增删节点时,分片会自动的在集群中负载均衡
4.主分片和副分片,每个document肯定只存在于某一个主分片及其对应的副分片,不可能存在于多个主分片中。
5.副分片是主分片的副本,负责容错,以及承担读请求负载
6.主分片的数量在创建索引的时候就固定了,副分片的数量可以随时修改。
7.主分片的默认数量是5,副分片默认是1,默认有10个分片,5个主分片,5个副分片。
8.主分片不能和自己的副分片在同一个节点(节点挂了,主和副都挂了,没有意义,做不到容错),但是可以和其他的主分片的副分片放在同一节点上。
水平扩容的过程
1.扩容后主分片和副分片都会自动负载均衡
2.扩容后每个节点上的分片会减少,那么分配给每个分片的CPU、内存、IO资源会更多,性能会提高。
3.扩容极限,如果有6个分片,扩容的极限就是6个节点,每个节点上一个分片,如果超出扩容极限,比如扩容到9个节点,那么可以增加副分片的个数。
Elasticsearch容错机制
如果master挂了,此时不是所有的主分片都是Active状态,所以此时的集群状态是red。
第一步:选举出一台服务器作为master
第二步:新选举出的master会把挂掉的主分片的某个副分片提升为主分片,此时集群的状态是yellow,因为少了一个副分片,并不是所有的副分片都是Active状态
第三步:重启故障机器,新的master会把所有的副本都复制一份到该节点上(同步一下挂了后发生的修改),此时集群的状态为green,因为所有的主分片和副分片都是Active状态。
删除文档
标记为deleted,随着数据量的增加,Elasticsearch会选择合适的时间删除。
文档增删改原理
1.发送增删改请求,可以选择任意一个节点,该节点就成为协调节点。
2.协调节点使用路由算法进行路由,然后将请求转给主分片所在的节点,该节点处理请求,并把数据同步到它的副分片。
3.协调节点对客户端返回结果。
文档查询内部原理
1.查询请求发送任意一个节点,该节点就成了协调节点,该节点使用路由算法,算出文档所在的主分片。
2.协调节点把请求转发给主分片,也可以转发给副分片(使用轮询调度算法,把请求平均的分配给主分片和副分片)。
3.处理请求的节点是把结果返回给协调节点,协调节点在返回给应用程序。
特殊情况:请求的文档还在建立索引的过程中,主分片上存在,但副分片上不存在,但请求被转发到了副分片上,这时就提示找不到文档了。
索引为什么不可变?
不需要锁,提升了并发的性能
可以一直在缓存中
节省了CPU和IO开销
版本控制 & 锁 & 数据一致性
Elasticsearch采用乐观锁来保证数据的一致性,也就是说,当用户对document进行操作时,并不需要对该document做加锁和解锁的操作,只需要制定要操作的版本即可,当版本号一致时,Elasticsearch会允许该操作顺利执行,而当版本号存在冲突时,Elasticsearch会抛出异常提示冲突了。()
Elasticsearch的版本号取值范围是1到2^63-1
内部版本控制:使用_version
外部版本控制:Elasticsearch在处理外部版本号时会与内部版本号处理有些不同,他不再是检查version是否和请求中的指定数值相同,而是检查当前的version是否比指定数值小,如果请求成功,那么外部版本号就会被存储到文档的_verison中。
为了保证_version和外部版本控制的数据一致性使用version_type=external
数据类型
字符型 | text | 长文本,在建立索引前会将这些文本进行分词,转化成分词组合,建立索引,text类型不能排序和聚合。(分词使用) |
keyword | 不会进行分词,可以用来检索过滤、排序和聚合。 只能用本身进行检索 | |
string | 在5.0时已移除 | |
数值类型 | long |
|
integer |
| |
short |
| |
byte |
| |
double |
| |
float |
| |
日期类型 | date |
|
布尔类型 | boolean |
|
二进制类型 | binary |
|
数组类型 | Array | 字符型数组: [ "one", "two" ] 整型数组:[ 1, 2 ] 数组型数组:[ 1, [ 2, 3 ]] 等价于[ 1, 2, 3 ] 对象数组:[ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }] |
对象类型 | _ object _ | 用于单个json对象 |
嵌套类型 | _ nested _ | 用于json数组 |
|
|
|
地理坐标类型 | geo-point | 经度 |
geo-shape | 类似多边形的复杂形状 | |
Ipv4 | _ ip _ | 用于表示Ipv4地址 |
Completion类型 | _ completion _ | 提供自动补全建议 |
Toekn count类型 | _ token_count _ | 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少 |
Mapper-murmur 3 | _ murmur 3 _ | 计算index的hash值 |
附加类型 | mapper-attachments | 可支持 _ attachments _ 索引,例如Microsoft office格式,html等 |
Mapping字段说明
{ "state": "open", "settings": { "index": { "creation_date": "1546288211675", "number_of_shards": "5", "number_of_replicas": "1", "uuid": "lUe3u9JvROO4edTro7SDRA", "version": { "created": "5020299" }, "provided_name": "commodity" } }, "mappings": { "vegetables": { "properties": { "mid": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } }, "title": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } }, "content": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } } } }, "phone": { "properties": { "mid": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } }, "title": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } }, "content": { "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } } } } } }, "aliases": [ ], "primary_terms": { "0": 1, "1": 1, "2": 1, "3": 1, "4": 1 }, "in_sync_allocations": { "0": [ "rcxcuP1kQrelWd935T_xPw" ], "1": [ "7mpsfo23RHCUX9w6Rz1jrA" ], "2": [ "Pj4yx7sjTnqAdwWkgPNZVQ" ], "3": [ "KoHCxlZ7SDOepUjqJzT83g" ], "4": [ "Tz6Xavw1Sv6zHNbbmZixCQ" ] } }
|
"store":"false" 是否单独设置此字段的是否存储而从_source字段中分离,默认是false,只能搜索不能取值
"index": "true" 分词,不分词为false
"analyzer":"ik" 指定分词器,默认是standard analyzer
"boost":"1.23" 字段级别的分数加权,默认是1.0
"doc_values": "false" 对not_analyzed字段,默认都是开启,分词字段不能使用,对排序和聚合能提升比较明显的性能,节约内存
"fielddata":{ "format": "disabled"} 针对分词字段,参与排序或聚合时能提升性能,不分词字段统一建议使用doc_value
"fields": {"keyword": {"ignore_above": 256, "type": "keyword"}} 字段属性
"ignore_above":256 超过256个字符的文本会被忽略,不被索引
"include_in_all":true 设置是否词字段包含在_all字段中,默认是true,除非设置no
"index_options":"docs" 4个可选参数,docs(索引文档号),freqs(文档号+词频),positions(文档号+词频+位置,通常用在距离查询),offsets(文档号+词频+位置+偏移量,通常用在高亮字段),分词默认是position,其他的默认为docs
"null_value":"NULL" 缺失字段的初始值,只有string可以使用,分词字段的null值也会被分词
"position_increament_gap":"0" 影响距离查询或近似查询,可以设置在多值字段的数据上火的分词字段上,查询时可用slop间隔,默认值是100
"search_analyer":"ik" 设置搜索时的分词器
"similarity":"BM25" 默认是TF/IDF算法,指定一个字段评分策略,只对字符串类型的分词有效
"term_vector":"no" 默认不存储向量信息,支持参数yes(term存储),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量)对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用。
"dynamic":"true" 动态映射,当Elasticsearch遇到mapping中没有的字段,会利用动态映射来决定字段类型,并自动对该字段添加索引。(true默认,false忽略新字段,strict抛出异常)
Java Api
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.levi.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>5.2.2</version> </dependency>
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>5.2.2</version> </dependency>
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.9.0</version> </dependency> </dependencies> </project> |
App.java
import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutionException;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequestBuilder; import org.elasticsearch.action.get.GetRequestBuilder; import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetRequestBuilder; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Requests; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.transport.client.PreBuiltTransportClient; import org.junit.After; import org.junit.Before; import org.junit.Test;
/** * 集群健康值 * ES存储数据也是按照切片的方式,默认是5个切片,而且切片都是有副本的 * ES服务默认端口是9300 * ES-Web管理端口9200 * ES的2.x系列的版本和5.x版本或以上的Api区别在于内部数个组件的整合, * 使得版本兼容不在痛苦,所以一下子2.X版本直接到5.X版本 * @author AimSpeed * @Project elasticsearch * @Package com.levi.elasticsearch * @FileName App.java * @ClassName App * @date 2018年12月31日 下午7:33:59 */ public class App {
private TransportClient transportClient; /** * 获取客户端对象 * @author AimSpeed * @throws UnknownHostException * @Title getClient void * @date 2018年12月31日 下午7:44:35 */ @Before public void getClient() throws UnknownHostException { //设置集群名称 Settings settings = Settings.builder().put("cluster.name","my-application").build();
//获取客户端对象 transportClient = new PreBuiltTransportClient(settings);
//具体要连接的集群地址,是一个可变参数,多台服务器就填写多个地址 transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.1.110"), 9300));
System.out.println(transportClient); }
|
/** * 获取客户端对象 * @author AimSpeed * @throws UnknownHostException * @Title getClient void * @date 2018年12月31日 下午7:44:35 */ @Before public void getClient() throws UnknownHostException { //设置集群名称 Settings settings = Settings.builder().put("cluster.name","my-application").build();
//获取客户端对象 transportClient = new PreBuiltTransportClient(settings);
//具体要连接的集群地址,是一个可变参数,多台服务器就填写多个地址 transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.1.110"), 9300));
System.out.println(transportClient); }
/** * 执行完之后关闭资源 * @author AimSpeed * @Title closeClient void * @date 2018年12月31日 下午8:28:47 */ @After public void closeClient() { //关闭资源 transportClient.close(); System.out.println("成功,关闭资源!"); }
/** * 创建索引 = 数据库 * 可在图形化界面查看 * @author AimSpeed * @Title createIndex void * @date 2018年12月31日 下午7:55:38 */ @Test public void createIndex() { //创建一个商品索引(商品数据库) CreateIndexRequestBuilder request = transportClient.admin().indices().prepareCreate("commodity");
System.out.println(request.get().isAcknowledged()); }
/** * 删除索引 = 数据库 * 可在图形化界面查看 * @author AimSpeed * @Title delIndex void * @date 2018年12月31日 下午8:26:01 */ @Test public void delIndex() { DeleteIndexRequestBuilder request = transportClient.admin().indices().prepareDelete("commodity"); System.out.println(request.get().isAcknowledged()); }
//========================================================= |
/** * 以json的方式插入文档 * @author AimSpeed * @Title createDocumentByJson void * @date 2018年12月31日 下午8:41:27 */ @Test public void createDocumentByJson() { //数据 String json = "{" + "\"mid\":\"A\"," + "\"title\":\"00年诺基亚\"," + "\"content\":\"耐砸\"" + "}";
//插入数据 //index(索引 = 库) type(类型 = 表)
//手动插入Id //IndexResponse response = transportClient.prepareIndex("commodity", "phone", "1") //.setSource(json).execute().actionGet();
//自动生成内部Id IndexResponse response = transportClient.prepareIndex("commodity", "phone") .setSource(json).execute().actionGet();
System.out.println("位于的索引(库):" + response.getIndex()); System.out.println("位于的类型(表):" + response.getType()); System.out.println("版本:" + response.getVersion()); System.out.println("Id:" + response.getId()); System.out.println("结果:" + response.getResult()); }
/** * 以Map的方式插入数据 * @author AimSpeed * @Title createDocumentByMap void * @date 2018年12月31日 下午9:06:19 */ @Test public void createDocumentByMap() { //数据 Map<String, Object> map = new HashMap<String, Object>(); map.put("mid", "A"); map.put("title", "花菜"); map.put("content", "送给女的,会给打么?");
//插入数据 //index(索引 = 库) type(类型 = 表) IndexResponse response = transportClient.prepareIndex("commodity", "vegetables") .setSource(map).execute().actionGet();
System.out.println("位于的索引(库):" + response.getIndex()); System.out.println("位于的类型(表):" + response.getType()); System.out.println("版本:" + response.getVersion()); System.out.println("Id:" + response.getId()); System.out.println("结果:" + response.getResult());
} |
/** * 以Builder方式插入数据 * @author AimSpeed * @Title createDocumentByBuilder * @throws IOException void * @date 2018年12月31日 下午9:15:59 */ @Test public void createDocumentByBuilder() throws IOException { //数据 XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .field("mid","B") .field("title", "三防手机") .field("content","能抵挡洪水?") .endObject();
//插入数据,自定义ES内部ID。 IndexResponse response = transportClient.prepareIndex("commodity", "phone","2") .setSource(builder).execute().actionGet();
System.out.println("位于的索引(库):" + response.getIndex()); System.out.println("位于的类型(表):" + response.getType()); System.out.println("版本:" + response.getVersion()); System.out.println("Id:" + response.getId()); System.out.println("结果:" + response.getResult());
}
/** * 批量插入 * @author AimSpeed * @Title createBulkDocument * @throws Exception void * @date 2018年12月31日 下午10:54:11 */ @Test public void createBulkDocument() throws Exception { //批量操作对象 BulkRequestBuilder prepareBulk = transportClient.prepareBulk();
//第1条数据 prepareBulk.add(transportClient.prepareIndex("commodity", "phone") .setSource(XContentFactory.jsonBuilder() .startObject() .field("mid","E") .field("title", "6防手机") .field("content","能抵挡洪水?") .endObject()));
//第2条数据 prepareBulk.add(transportClient.prepareIndex("commodity", "phone") .setSource(XContentFactory.jsonBuilder() .startObject() .field("mid","F") .field("title", "99防手机") .field("content","能抵挡洪水?") .endObject()));
BulkResponse response = prepareBulk.get(); System.out.println("是否有失败数据:" + response.hasFailures()); }
//=========================================================
|
/** * 搜索文档(单个) * @author AimSpeed * @Title queryById void * @date 2018年12月31日 下午9:18:55 */ @Test public void queryById () { //内部ID,作为搜索索引 //GetRequestBuilder request = transportClient.prepareGet("commodity", "phone", "AWgGAaE6zc8qunjZLZcr");
//自定义插入的ID,作为搜索索引 GetRequestBuilder request = transportClient.prepareGet("commodity", "phone", "1");
System.out.println(request.get().getSourceAsString()); }
/** * 多个ID查询 * @author AimSpeed * @Title queryByMultiId void * @date 2018年12月31日 下午9:25:41 */ @Test public void queryByMultiId() { MultiGetRequestBuilder multiGetRequestBuilder = transportClient.prepareMultiGet() .add("commodity", "phone", "1")//自定义的id .add("commodity", "phone", "AWgGAaE6zc8qunjZLZcr","2");//多个id,可变参数 for (MultiGetItemResponse response : multiGetRequestBuilder.get()) { if(response.getResponse().isExists()) { System.out.println(response.getResponse().getSourceAsString()); } } }
/** * 查找所有 * @author AimSpeed * @Title queryMatch void * @date 2018年12月31日 下午9:27:30 */ @Test public void queryMatch() { //任何的Api,在使用的时候注意下传入的参数,可能是可变形参 SearchRequestBuilder request = transportClient.prepareSearch("commodity").setTypes("phone","vegetables") .setQuery(QueryBuilders.matchAllQuery());
//查询结果 SearchHits hits = request.get().getHits();
System.out.println("查询结果数量: " + hits.getTotalHits());
//打印结果 Iterator<SearchHit> iterator = hits.iterator(); while(iterator.hasNext()) { SearchHit searchHit = iterator.next(); System.out.println(searchHit.getSourceAsString()); } } |
/** * 分词查询 * @author AimSpeed * @Title queryByParticiple void * @date 2018年12月31日 下午9:36:11 */ @Test public void queryByParticiple() { //查询语句 SearchResponse response = transportClient.prepareSearch("commodity").setTypes("phone","vegetables") .setQuery(QueryBuilders.queryStringQuery("拍照")).get(); //查询结果 SearchHits hits = response.getHits();
System.out.println("查询结果数量:" + hits.getTotalHits());
//打印结果 Iterator<SearchHit> iterator = hits.iterator(); while(iterator.hasNext()) { SearchHit hit = iterator.next(); System.out.println(hit.getSourceAsString()); }
}
/** * 词条搜索 * @author AimSpeed * @Title queryByTerm void * @date 2018年12月31日 下午9:42:17 */ @Test public void queryByTerm() { //查询语句 SearchResponse response = transportClient.prepareSearch("commodity").setTypes("phone","vegetables") //会发现查询不到,词条查询就是词查询,每个字作为索引的(切分成一个个的词),除非引入分词器(ik分词器等) //.setQuery(QueryBuilders.termQuery("content", "拍照")).get(); .setQuery(QueryBuilders.termQuery("content", "拍")).get();
//查询结果对象 SearchHits searchHits = response.getHits(); System.out.println("查询结果数量:" + searchHits.getTotalHits());
//遍历打印 Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) { System.out.println(iterator.next().getSourceAsString()); }
}
/** * 通配符查询 * * 多个字符 * ? 单个字符 * @author AimSpeed * @Title queryByWildcard void * @date 2018年12月31日 下午9:49:24 */ @Test public void queryByWildcard() { //查询语句 SearchResponse response = transportClient.prepareSearch("commodity").setTypes("phone","vegetables") .setQuery(QueryBuilders.wildcardQuery("content", "玩*")).get();
//查询结果对象 SearchHits searchHits = response.getHits(); System.out.println("查询结果数量:" + searchHits.getTotalHits());
//遍历打印 Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) { System.out.println(iterator.next().getSourceAsString()); }
} |
/** * 模糊查询 * @author AimSpeed * @Title queryByLike void * @date 2018年12月31日 下午9:53:17 */ @Test public void queryByLike() { //查询语句 SearchResponse response = transportClient.prepareSearch("commodity").setTypes("phone","vegetables") .setQuery(QueryBuilders.fuzzyQuery("content", "游")).get();
//查询结果对象 SearchHits searchHits = response.getHits(); System.out.println("查询结果数量:" + searchHits.getTotalHits());
//遍历打印 Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) { System.out.println(iterator.next().getSourceAsString()); } }
//=========================================================
/** * 更新数据 * @author AimSpeed * @Title update * @throws IOException * @throws InterruptedException * @throws ExecutionException void * @date 2018年12月31日 下午10:38:21 */ @Test public void update() throws IOException, InterruptedException, ExecutionException { //创建更新数据的请求对象 UpdateRequest updateRequest = new UpdateRequest("commodity", "phone", "1");
//更新 updateRequest.doc(XContentFactory.jsonBuilder() .startObject() .field("content","更新...") .endObject());
UpdateResponse response = transportClient.update(updateRequest).get();
System.out.println("位于的索引(库):" + response.getIndex()); System.out.println("位于的类型(表):" + response.getType()); System.out.println("版本:" + response.getVersion()); System.out.println("Id:" + response.getId()); System.out.println("结果:" + response.getResult()); }
|
/** * 更新数据,如果不存在就添加数据 * @author AimSpeed * @Title upsert void * @date 2018年12月31日 下午10:38:56 */ @Test public void upsert() throws IOException, InterruptedException, ExecutionException { //没有这个文档就创建 //索引 类型 内部id(可自动生成) // IndexRequest indexRequest = new IndexRequest("commodity", "phone"); IndexRequest indexRequest = new IndexRequest("commodity", "phone", "5");
indexRequest.source(XContentFactory.jsonBuilder() .startObject() .field("mid","D") .field("title","不存在的数据,无法更新,那就添加") .endObject());
UpdateRequest updateRequest = new UpdateRequest("commodity", "phone", "6"); //更新 updateRequest.doc(XContentFactory.jsonBuilder() .startObject() .field("content","upsert() 更新...") .endObject());
//更新失败就添加 updateRequest.upsert(indexRequest);
//更新 UpdateResponse response = transportClient.update(updateRequest).get();
System.out.println("位于的索引(库):" + response.getIndex()); System.out.println("位于的类型(表):" + response.getType()); System.out.println("版本:" + response.getVersion()); System.out.println("Id:" + response.getId()); System.out.println("结果:" + response.getResult());
//注意看页面的结果id列,很有趣。
}
//=========================================================
@Test public void delDocument() { DeleteRequestBuilder request = transportClient.prepareDelete("commodity", "phone", "6"); System.out.println(request.get().getResult()); }
//========================================================= |
/** * 创建映射 = 约束 * @author AimSpeed * @throws IOException * @throws ExecutionException * @throws InterruptedException * @Title createMapping void * @date 2018年12月31日 下午10:17:53 */ @Test public void createMapping() throws IOException, InterruptedException, ExecutionException { //定义映射 = 约束 XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() .startObject() .startObject("fruits") .startObject("properties") .startObject("mid") .field("type","string") .field("store","yes") .endObject() .startObject("title") .field("type","string") .field("store","no") .endObject() .startObject("content") .field("type","string") .field("store","yes") .endObject() .endObject() .endObject() .endObject();
//添加索引 PutMappingRequest mapping = Requests.putMappingRequest("commodity2") .type("fruits").source(xContentBuilder);
//添加 PutMappingResponse putMappingResponse = transportClient.admin().indices().putMapping(mapping).get();
System.out.println(putMappingResponse.isAcknowledged());
} } |