大家好,欢迎来到IT知识分享网。
全文搜索引擎 Elastic Search
第一节 引言
当系统数据量上了10亿、100亿条的时候,我们用什么数据库好?如何解决单点故障?如何提升检索速度?如何解决统计分析问题?
传统数据库的应对解决方案
- 关系型数据库
- 非关系型数据库
另辟蹊径
于是,Elastic Search就在这种背景下诞生了
第二节 Elastic Search 介绍
1.1 ES简介
Elastic Search 简称ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
1.2 ES
与 Lucene
的关系
Lucene
只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene
非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的- ES也使用Java开发并使用
Lucene
作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API
来隐藏Lucene
的复杂性,从而让全文搜索变得简单
第二节 安装 Elastic Search
2.1 安装 Elastic Search
解压 elasticsearch-7.6.2-windows-x86_64.zip
2.2 安装 Kibana
解压 kibana-7.6.2-windows-x86_64.zip
- 修改配置
elasticsearch.hosts: ["http://localhost:9200"]
2.3 安装 IK Analyzer
- 进入
elasticsearch-7.6.2\plugins
目录 - 创建文件夹
ik
- 将
elasticsearch-analysis-ik-7.6.2.zip
拷贝至ik
目录下,然后解压
ELK = Elasticsearch Logstash Kibana => 日志采集系统
第三节 ES 结构
结构说明:
- ES中的索引(index)相当于
MySQL
中的数据库(database),但ES7
之后建议每个索引下只存放一种类型(type) - ES中的数据分片(shard)是特有的,在集群模式下,海量的数据可以分别存储在不同的分片中,每个分片都有备份数据,其备份数据称为replica,备份数据的主要作用就是在主分片数据不可用的情况下,会将备份数据(replica)作为新的主分片使用
- ES中的类型(type)相当于
MySQL
中的表(table) - ES中的文档(document)相当于
MySQL
中的表的一行数据(row) - ES中的字段(field)相当于
MySQL
中的表的字段(field)
第四节 ES 字段类型
4. 1 字符串类型
- text:一般被用于全文检索。 将当前Field进行分词。
- keyword:当前Field不会被分词。
4.2 数值类型
- long:占用8个字节
- integer:占用4个字节
- short:占用2个字节
- byte:占用1个字节
- double:占用8个字节
- float:占用4个字节
4.3 范围类型
- long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
- integer_range:同上
- double_range:同上
- float_range:同上
- date_range:同上
- ip_range:同上
4.4 其他类型
- date类型,针对时间类型指定具体的格式
- boolean类型,表达true和false
- binary类型暂时支持Base64 encode string
- 经纬度类型:geo_point:用来存储经纬度的
- ip类型:ip:可以存储IPV4或者IPV6
第五节 ES RESTful
操作
5.1 索引操作
5.1.1 创建索引
# 创建索引 请求方式只能是PUT /user表示创建一个user索引 # settings表示这个索引的一些设置 number_of_shards表示分片数量 # number_of_replicas 表示每个分片的副本数量 PUT /user {
"settings": {
"number_of_shards": 3, "number_of_replicas": 1 } }
结果:
# acknowledged表示索引创建的回执信息,也就是响应的结果 # shards_acknowledged表示索引分片创建的回执信息 # index表示索引的名称 {
"acknowledged" : true, "shards_acknowledged" : true, "index" : "user" }
5.1.2 查看索引
GET /user
结果:
# user表示索引名称 # aliases表示索引的别名 # mappings表示索引中的字段 # settings表示索引的创建信息 {
"user" : {
"aliases" : {
}, "mappings" : {
}, "settings" : {
"index" : {
"creation_date" : "34", "number_of_shards" : "3", "number_of_replicas" : "1", "uuid" : "VwShKX6-TliIeCIKttsRwA", "version" : {
"created" : "" }, "provided_name" : "user" } } } }
5.1.3 删除索引
DELETE /user
结果:
{ "acknowledged" : true }
5.2 类型操作
5.2.1 创建类型
# /user/_mappings表示设置user索引的具体数据结构,相当于定义类中的属性 # class User{ String name; String sex; int age; Date birthday;} PUT /user/_mappings { "properties": { "name": { "type": "text", "analyzer": "ik_max_word", "index": true, "store": false }, "sex": { "type": "keyword" }, "age": { "type": "integer" }, "birthday": { "type": "date", "format": "yyyy-MM-dd" } } }
结果:
{
"acknowledged" : true, "shards_acknowledged" : true, "index" : "user" }
5.2.2 查看类型
GET /user
结果:
{
"user" : {
"aliases" : {
}, "mappings" : {
"properties" : {
"age" : {
"type" : "integer" }, "birthday" : {
"type" : "date", "format" : "yyyy-MM-dd" }, "name" : {
"type" : "text", "analyzer" : "ik_max_word" }, "sex" : {
"type" : "keyword" } } }, "settings" : {
"index" : {
"creation_date" : "39", "number_of_shards" : "5", "number_of_replicas" : "1", "uuid" : "hfhUHBKWQZukfeZLlteHKQ", "version" : {
"created" : "" }, "provided_name" : "user" } } } }
5.3 文档操作
5.3.1 增加文档
POST /user/_doc {
"name": "张三", "sex": "男", "age": 20, "birthday": "2000-05-06" }
结果:
{
"_index" : "user", "_type" : "_doc", "_id" : "lfKkRoQB18W7DskkwOaq", "_version" : 1, "result" : "created", "_shards" : {
"total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
5.3.2 查看文档
GET /user/_search {
"query": {
"match_all": {
} } }
结果:
{
"took" : 1, "timed_out" : false, "_shards" : {
"total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : {
"total" : {
"value" : 1, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ {
"_index" : "user", "_type" : "_doc", "_id" : "lfKkRoQB18W7DskkwOaq", "_score" : 1.0, "_source" : {
"name" : "张三", "sex" : "男", "age" : 20, "birthday" : "2000-05-06" } } ] } }
5.3.3 修改文档
# 修改语法: /索引/_doc/数据ID # 这种修改需要注意,不是只修改一个属性,而是对整个文档进行修改,可以理解为是用一个新的文档去替换原来的文档完成修改。如果新的文档 # 只有1个属性,那么替换后的文档也只有1个属性。 PUT /user/_doc/lfKkRoQB18W7DskkwOaq {
"sex": "女" }
结果:
{
"_index" : "user", "_type" : "_doc", "_id" : "lfKkRoQB18W7DskkwOaq", "_version" : 3, "result" : "updated", "_shards" : {
"total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 }
5.3.4 删除文档
DELETE /user/_doc/lfKkRoQB18W7DskkwOaq
结果:
{
"_index" : "user", "_type" : "_doc", "_id" : "lfKkRoQB18W7DskkwOaq", "_version" : 4, "result" : "deleted", "_shards" : {
"total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 3, "_primary_term" : 1 }
第六节 Java 操作 ES
6.1 创建maven工程
<parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.3.2.RELEASE</version> </parent> <groupId>com.qf</groupId> <artifactId>es</artifactId> <version>1.0</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--spring-data提供的操作elasticsearch的包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
6.2 编写实体类
@Data //Document表示文档,文档的类型就是Article,文档中的字段就是Article中定义的字段 //indexName表示这个文档所在的索引,shards表示分片数量,replicas表示副本的数量 @Document(indexName = "article", shards = 10, replicas = 2) public class Article {
//Id表示这个字段就是确定这条数据的 @Id //Field表示字段的定义,name表示在文档中存储的名字,type表示字段类型 @Field(name = "id", type = FieldType.Long) private Long id; //Field表示字段的定义,analyzer表示存储时使用的分词器,searchAnalyzer表示查询时使用的分词器 @Field(name = "title", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word") private String title; //Field表示字段的定义,FieldType.Keyword表示这个字段就是个关键词,不需要再分词, //index表示是否建立索引,默认为true,不需要建立索引时需要显示指定 @Field(name = "content", type = FieldType.Keyword, index = false) private String content; @Field(name = "author", type = FieldType.Text) private String author; @Field(name = "author_name", type = FieldType.Text) private String authorName; //日期格式网址说明 // https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html @Field(name = "publish_date", type = FieldType.Date, format = DateFormat.year_month_day) private Date publishDate; @Field(name = "category", type=FieldType.Text) private String category; @Field(name = "publishType", type = FieldType.Text) private String publishType; //发布形式:全部可见,部门可见 @Field(name = "level", type = FieldType.Text) private String level; //文章等级: 初级、中级、高级 }
6.3 索引操作
6.3.1 编写测试类
@SpringBootTest class IndexTest {
@Autowired private ElasticsearchRestTemplate restTemplate; }
6.3.2 创建索引
@Test public void createIndex(){
//获取一个索引操作对象,如果索引不存在,则直接创建 IndexOperations indexOperations = restTemplate.indexOps(Article.class); System.out.println(indexOperations); }
6.3.3 删除索引
@Test public void deleteIndex(){
//获取一个索引操作对象 IndexOperations indexOperations = restTemplate.indexOps(Article.class); indexOperations.delete(); }
6.4 文档操作
6.4.1 编写Repository
//ElasticsearchRepository 继承所有父接口的特性 //PagingAndSortingRepository 分页操作 //CrudRepository 基本的CRUD操作 @Repository public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
}
6.4.2 编写测试类
@SpringBootTest class DocumentTest {
@Autowired private ArticleRepository articleRepository; }
6.4.3 添加文档
@Test public void addArticle(){
Article article = new Article(); article.setId(1L); article.setAuthor("admin"); article.setContent("这是一篇很有意思的文章"); article.setCreatedDate(new Date()); article.setTitle("Java是一门很简单的语言"); articleRepository.save(article); }
6.4.4 修改文档
@Test public void updateArticle(){
Article article = new Article(); article.setId(1L); article.setAuthor("admin"); article.setContent("这是一篇很有意思的文章"); article.setCreatedDate(new Date()); article.setTitle("Java很牛逼"); articleRepository.save(article); }
6.4.5 查询文档
@Test public void searchArticle(){
Optional<Article> opt = articleRepository.findById(1L); Article article = opt.orElse(null); System.out.println(article); }
6.4.6 删除文档
@Test public void deleteArticle(){
articleRepository.deleteById(1L); }
6.4.7 批量保存
@Test public void batchAddArticle(){
List<Article> articles = new ArrayList<>(); for(int i=0; i<100; i++){
Article article = new Article(); article.setId((long) (i+2)); article.setAuthor("author"+i); article.setContent("这是一篇很有意思的文章" + i); article.setCreatedDate(new Date()); article.setTitle("Java很牛逼" + i); articles.add(article); } articleRepository.saveAll(articles); }
6.4.8 分页查询
@Test public void pageSearch(){
//查询构建器,这里用的匹配所有的查询构建器 QueryBuilder builder = new MatchAllQueryBuilder(); //分页对象 Pageable pageable = PageRequest.of(1, 20); //分页查询 Page<Article> articlePage = articleRepository.search(builder, pageable); //获取总条数 long total = articlePage.getTotalElements(); System.out.println("总条数:" + total); //获取查询结果 List<Article> articleList = articlePage.getContent(); articleList.forEach(System.out::println); }
6.4.9 排序查询
@Test public void sortSearch(){
Sort.Order dateOrder = Sort.Order.desc("createdDate"); //日期降序排列 Sort.Order idOrder = Sort.Order.asc("id");//ID升序排列 Iterable<Article> articles = articleRepository.findAll(Sort.by(dateOrder, idOrder)); articles.forEach(System.out::println); }
6.4.10 自定义查询
自定义查询的命名规则:方法名必须是get、find、read、query其中之一开始,后面接字段名以及条件,条件之间的组合使用and或者or, 方法参数必须与使用的字段一一匹配
@Repository public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
//文章标题模糊查询,并且ID在给定的范围之内 List<Article> getByTitleLikeAndIdBetween(String title, Long min, Long max); }
测试:
@Test public void customerQuery(){
Calendar c = Calendar.getInstance(); c.roll(Calendar.DAY_OF_MONTH, -1); List<Article> articles = articleRepository.getByTitleLikeAndIdBetween("Java", 20L, 30L); articles.forEach(System.out::println); }
6.5 ES查询操作[重点]
新建一个测试类
@SpringBootTest class QueryTest {
@Autowired private RestHighLevelClient client; }
6.5.1 term 查询
term的查询是代表完全匹配,这里的完全匹配指的是,查询的内容不会被分词,而是作为一个整体到存储的数据中去匹配,如果数据对应的字段有进行分词,那么只要其中任何一个分词结果与查询内同匹配,那么该数据将在查询结果中展示
查询语法:
# from表示开始的位置,size表示最大查询的条数, query表示查询的条件,term表示这里使用的是精确查找 # 精确查找的条件就是id字段的值为2, term中只能有一个字段作为条件 GET /article/_search {
"from": 0, "size": 20, "query": {
"term": {
"id": {
"value": 2 } } } }
测试:
@Test public void termQueryTest() throws IOException {
//查询资源构建器 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.from(0).size(20); //相当于 LIMIT 0, 20 //term查询 = 精确查询 builder.query(QueryBuilders.termQuery("id", 2L)); //创建查询请求 SearchRequest request = new SearchRequest("article"); //将查询构建器放入请求中 request.source(builder); //查询并得到结果 SearchResponse response = client.search(request, RequestOptions.DEFAULT); //从结果中获取查询命中信息 SearchHits searchHits = response.getHits(); //获取总命中数 long totalHits = searchHits.getTotalHits(); System.out.println("查询命中条数:" + totalHits); //获取所有的命中数据 SearchHit[] hits = searchHits.getHits();//获取每一条被命中的信息 for(SearchHit hit: hits){
//将命中的数据转换为一个map Map<String, Object> rowData = hit.getSourceAsMap(); System.out.println(rowData); } client.close(); }
6.5.2 terms 查询
terms查询与term查询的原理是一样的,只是terms查询针对的是一个字段可能对应多个值的情况,相当于 MySQL
中的条件in
# 查询id在2,3,4,5中的数据 POST /article/_search {
"from": 0, "size": 20, "query": {
"terms": {
"id": [2, 3, 4, 5] } } }
测试:
@Test public void termsQueryTest() throws IOException {
//查询资源构建器 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.from(0).size(20); //相当于 LIMIT 0, 20 //terms查询 = 相当于MySQL中的IN builder.query(QueryBuilders.termsQuery("id", Arrays.asList(2,3,4,5))); //创建查询请求 SearchRequest request = new SearchRequest("article"); //将查询构建器放入请求中 request.source(builder); //查询并得到结果 SearchResponse response = client.search(request, RequestOptions.DEFAULT); //从结果中获取查询命中信息 SearchHits searchHits = response.getHits(); //获取总命中数 long totalHits = searchHits.getTotalHits(); System.out.println("查询命中条数:" + totalHits); //获取所有的命中数据 SearchHit[] hits = searchHits.getHits();//获取每一条被命中的信息 for(SearchHit hit: hits){
//将命中的数据转换为一个map Map<String, Object> rowData = hit.getSourceAsMap(); System.out.println(rowData); } client.close(); }
6.5.3 match查询[重点]
match查询属于高层查询,会根据查询的字段类型不一样,采用不同的查询方式。
- 查询的是日期或者是数值的话,会将你基于的字符串查询内容转换为日期或者数值对待。
- 如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
- 如果查询的内容是一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起
6.5.3.1 查询日期
GET /article/_search {
"size": 500, "query": {
"match": {
"publish_date": "2022-11-07" } } }
测试:
@Test public void date_matchTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //这里的日期格式自动转换为日期匹配 builder.query(QueryBuilders.matchQuery("publish_date","2022-11-07")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.3.2 查询内容分词
如果查询的字段使用了分词,那么查询的内容也将分词。只要数据匹配其中的一个分词内容即可。
GET /article/_search {
"size": 500, "query": {
"match": {
"title": "中国人民" } } }
测试:
@Test public void analyzer_matchTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //这里的title使用的查询内容是"中国人民",这里会进行分词,分词之后再匹配 builder.query(QueryBuilders.matchQuery("title","中国人民")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.3.2 查询内容不分词
如果查询的字段没有使用分词,那么查询的内容就不会分词。
GET /article/_search {
"size": 500, "query": {
"match": {
"author_name": "张三丰" } } }
测试:
@Test public void none_analyzer_matchTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //这里的author_name使用的查询内容是"张三丰",这里会进行分词,分词之后再匹配 builder.query(QueryBuilders.matchQuery("author_name","张三丰")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.3.3 布尔 match 查询
基于一个Field匹配的内容,采用and或者or的方式连接
# 这里的"中华共和"会被分词,分为"中华"和"共和",这两个词用 and 衔接,表示要同时匹配上 GET /article/_search { "size": 500, "query": { "match": { "title": { "query": "中华共和", "operator": "and" } } } }
测试:
@Test public void bool_analyzer_matchTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //这里的title使用的查询内容是"中华共和",这里会进行分词,分词之后的所有分词结果必须要全部匹配 builder.query(QueryBuilders.matchQuery("title","中华共和").operator(Operator.AND)); //默认衔接操作就是OR // builder.query(QueryBuilders.matchQuery("title","中国人民").operator(Operator.OR)); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.3.4 multi_match 查询
查询内容可以与多个字段匹配的就需要使用 multi_match查询
GET /article/_search {
"size": 500, "query": {
"multi_match": {
"query": "中国", "fields": ["category", "title"] } } }
测试:
@Test public void multi_matchTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //这里的"中国"既可以与title匹配,也可以与category匹配,任意满足即可 builder.query(QueryBuilders.multiMatchQuery("中国","title", "category")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.4 其他查询
6.5.4.1 prefix 查询
GET /article/_search {
"size": 500, "query": {
"prefix": {
"content": "测试1" } } }
测试:
@Test public void prefixQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //content字段内容以"测试1"开始 builder.query(QueryBuilders.prefixQuery("content","测试1")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.4.2 fuzzy查询
在实际的搜索中,我们有时候会打错字,从而导致搜索不到。在 Elastic Search 中,我们可以使用 fuzziness 属性来进行模糊查询,从而达到搜索有错别字的情形。fuzziness 表示编辑距离,编辑距离是对两个字符串差异长度的量化,及一个字符至少需要处理多少次才能变成另一个字符,比如lucene
和lucece
只差了一个字符他们的编辑距离是1。 编辑距离的值可以是0,1,2或者auto
GET /article/_search {
"size": 500, "query": {
"fuzzy": {
"title": {
"value": "中华国", "fuzziness": 2 } } } }
测试:
@Test public void fuzzyQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //中华国变化两次可以与title的内容匹配 builder.query(QueryBuilders.fuzzyQuery("title","中华国").fuzziness(Fuzziness.TWO)); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.4.3 wildcard 查询
通配查询,和 MySQL
中的like是一个套路,可以在查询时,在字符串中指定通配符*和占位符?
GET /article/_search {
"size": 500, "query": {
"wildcard": {
"title": {
"value": "中华*" } } } }
测试:
@Test public void wildcardQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //title的内容需要以中华开始 builder.query(QueryBuilders.wildcardQuery("title","中华*")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.4.4 range查询
范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定。需要注意的是,ES中的存储比较都是按照字符串顺序来的
GET /article/_search { "size": 500, "query": { "range": { "id": { "gte": 100, "lte": 50 } } } }
测试:
@Test public void rangeQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); //id的字符串顺序在100~50 builder.query(QueryBuilders.rangeQuery("id").gte(100).lte(50)); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.5 复合查询[重点]
6.5.5.1 bool 查询
复合过滤器,将多个查询条件,以一定的逻辑组合在一起。
- must: 所有的条件,用must组合在一起,表示And的意思
- must_not:将must_not中的条件,全部都不能匹配,表示Not的意思
- should:所有的条件,用should组合在一起,表示Or的意思
GET /article/_search {
"size": 500, "query": {
"bool": {
"should": [ {
"term": {
"author_name": {
"value": "张三" } } }, {
"term": {
"category": {
"value": "中国文化" } } } ], "must_not": [ {
"match": {
"category": "黄赌毒" } } ], "must": [ {
"range": {
"publish_date": {
"gte": "2022-11-06", "lte": "2022-11-08" } } } ] } } }
测试
@Test public void boolQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.should(QueryBuilders.termQuery("author_name", "张三")); boolQueryBuilder.should(QueryBuilders.termQuery("category", "中国文化")); boolQueryBuilder.mustNot(QueryBuilders.matchQuery("category", "黄赌毒")); boolQueryBuilder.must(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lte("2022-10-08")); builder.query(boolQueryBuilder); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap()); } }
6.5.5.2 boosting 查询
boosting查询可以帮助我们去影响查询后的score。
- positive:只有匹配上positive的查询的内容,才会被放到返回的结果集中。
- negative:如果匹配上和positive并且也匹配上了negative,就可以降低这样的文档score。
- negative_boost:指定系数,必须小于1.0
关于查询时,分数是如何计算的:
- 搜索的关键字在文档中出现的频次越高,分数就越高
- 指定的文档内容越短,分数就越高
- 我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
GET /article/_search {
"size": 500, "query": {
"boosting": {
"positive": {
"match": {
"title": "中国共和" } }, "negative": {
"match": {
"level": "高级" } }, "negative_boost": 0.4 } } }
测试:
@Test public void boostingQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); BoostingQueryBuilder boostingQueryBuilder = QueryBuilders.boostingQuery( QueryBuilders.matchQuery("title", "中华共和"), QueryBuilders.matchQuery("level", "高级") ).negativeBoost(0.4f); builder.query(boostingQueryBuilder); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); SearchHits hits = resp.getHits(); //4. 输出结果 for (SearchHit hit : hits.getHits()) {
float score = hit.getScore(); Map<String, Object> map = hit.getSourceAsMap(); System.out.println(map + " => " + score); } }
6.5.6 filter 查询[重点]
query,根据你的查询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。
filter,根据你的查询条件去查询文档,不去计算分数,而且filter会对经常被过滤的数据进行缓存。
GET /article/_search {
"size": 500, "query": {
"bool": {
"filter": [ {
"term": {
"title": "中国" } }, {
"range": {
"publish_date": {
"gte": "2022-11-06", "lte": "2022-11-08" } } } ] } } }
测试:
@Test public void filterQueryTest() throws IOException { //1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.filter(QueryBuilders.termQuery("title", "中国")); boolQueryBuilder.filter(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lt("2022-11-08")); builder.query(boolQueryBuilder); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); SearchHits hits = resp.getHits(); //4. 输出结果 for (SearchHit hit : hits.getHits()) { float score = hit.getScore(); Map<String, Object> map = hit.getSourceAsMap(); System.out.println(map + " => " + score); } }
6.5.7 高亮查询[重点]
高亮查询就是用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。
高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。
ES提供了一个highlight属性,和query同级别的。
- fragment_size:指定高亮数据展示多少个字符回来。
- pre_tags:指定前缀标签,举个例子< font color=“red” >
- post_tags:指定后缀标签,举个例子< /font >
- fields:指定哪几个Field以高亮形式返回
GET /article/_search {
"size": 500, "query": {
"bool": {
"filter": [ {
"term": {
"title": "中国" } }, {
"range": {
"publish_date": {
"gte": "2022-11-06", "lte": "2022-11-08" } } } ] } }, "highlight": {
"fields": {
"title": {
} }, "pre_tags": "<font color='red'>", "post_tags": "</font>" } }
测试:
@Test public void highlightQueryTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(500); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.filter(QueryBuilders.termQuery("title", "中国")); boolQueryBuilder.filter(QueryBuilders.rangeQuery("publish_date").gte("2022-11-06").lt("2022-11-08")); builder.query(boolQueryBuilder); HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>"); builder.highlighter(highlightBuilder); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); SearchHits hits = resp.getHits(); //4. 输出结果 for (SearchHit hit : hits.getHits()) {
HighlightField highlightField = hit.getHighlightFields().get("title"); String fieldName = highlightField.getName(); Text[] fragments = highlightField.getFragments(); System.out.println(fieldName + " => "+ Arrays.stream(fragments).map(Text::string).collect(Collectors.joining(","))); } }
6.5.8 聚合查询[重点]
ES的聚合查询和 MySQL
的聚合查询类似,ES的聚合查询相比 MySQL
要强大的多,ES提供的统计数据的方式多种多样。
# ES聚合查询的RESTful语法 POST /index/type/_search {
"aggs": {
"名字(agg)": {
"agg_type": {
"属性": "值" } } } }
6.5.8.1 去重计数查询
GET /article/_search {
"aggs": {
"authorNameAgg": {
"cardinality": {
"field": "author_name" } } } }
测试:
@Test public void cardinalityTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.aggregation(AggregationBuilders.cardinality("authorNameAgg").field("author_name")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); Aggregations aggregations = resp.getAggregations(); Cardinality cardinality = aggregations.get("authorNameAgg"); String name = cardinality.getName(); long value = cardinality.getValue(); System.out.println(name + "=" + value); }
6.5.8.2 范围统计
统计一定范围内出现的文档个数,比如,针对某一个Field的值在 0100,100200,200~300之间文档出现的个数分别是多少。
范围统计可以针对普通的数值,针对时间类型,针对ip类型都可以做相应的统计。
range,date_range,ip_range
GET /article/_search {
"aggs": {
"publishDateAgg": {
"range": {
"field": "publish_date", "ranges": [ {
"from": "2022-11-01", "to": "2022-11-10" }, {
"from": "2022-11-11", "to": "2022-11-20" }, {
"from": "2022-11-21", "to": "2022-11-30" } ] } } } }
测试:
@Test public void rangeAggregationTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.aggregation(AggregationBuilders.dateRange("publishDateAgg") .field("publish_date") .addRange("2022-11-01", "2022-11-10") .addRange("2022-11-11", "2022-11-20") .addRange("2022-11-21", "2022-11-30")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); Aggregations aggregations = resp.getAggregations(); Range range = aggregations.get("publishDateAgg"); List<? extends Range.Bucket> buckets = range.getBuckets(); buckets.forEach(bucket -> {
String key = bucket.getKeyAsString(); long docCount = bucket.getDocCount(); System.out.println(key + " => " + docCount); }); }
6.5.8.2 统计聚合查询
可以查询指定Field的最大值,最小值,平均值,平方和等
GET /article/_search {
"aggs": {
"agg": {
"extended_stats": {
"field": "publish_date" } } } }
测试:
@Test public void extended_statsAggregationTest() throws IOException {
//1. 创建Request SearchRequest request = new SearchRequest("article"); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.aggregation(AggregationBuilders.extendedStats("agg").field("publish_date")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); Aggregations aggregations = resp.getAggregations(); ExtendedStats extendedStats = aggregations.get("agg"); String minAsString = extendedStats.getMinAsString(); String maxAsString = extendedStats.getMaxAsString(); String avgAsString = extendedStats.getAvgAsString(); System.out.println(minAsString); System.out.println(maxAsString); System.out.println(avgAsString); }
第七节 倒排索引
将存放的数据,以一定的方式进行分词,并且将分词的内容存放到一个单独的分词库中。
当用户去查询数据时,会将用户的查询关键字进行分词。
然后去分词库中匹配内容,最终得到数据的id标识。
根据id标识去存放数据的位置拉取到指定的数据。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125104.html