Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。
Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。
为了解决Lucene使用时的繁复性,于是Elasticsearch便应运而生。它使用 Java 编写,内部采用 Lucene 做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene 做了一层封装,它提供了一套简单一致的 RESTful API 来帮助我们实现存储和检索。
当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:
安装 Elasticsearch 之前,你需要先安装一个较新版本的 Java,目前市面的主流java是jdk1.8。
你可以从 elastic 的官网 elastic.co/downloads/elasticsearch 获取最新版本的Elasticsearch。解压文档后,按照下面的操作,即可在前台(foregroud)启动 Elasticsearch:
cd elasticsearch-<version>
./bin/elasticsearch
此时,Elasticsearch运行在本地的9200端口,在浏览器中输入网址“http://localhost:9200/”,如果看到以下信息就说明你的电脑已成功安装Elasticsearch:
{
"name" : "YTK8L4q",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "hB2CZPlvSJavhJxx85fUqQ",
"version" : {
"number" : "6.5.4",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "d2ef93d",
"build_date" : "2018-12-17T21:17:40.758843Z",
"build_snapshot" : false,
"lucene_version" : "7.5.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
这里,我们安装的Elasticsearch版本号为6.5.4。
Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。
你可以从 elastic 的官网 https://www.elastic.co/downloads/kibana 获取最新版本的Kibana。解压文档后,按照下面的操作,即可在前台(foregroud)启动Kibana:
cd kibana-<version>
./bin/kabana
此时,Kibana运行在本地的5601端口,在浏览器中输入网址“http://localhost:5601”,即可看到以下界面:
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:
Apache Lucene
Elasticsearch
Solr
Ferret
该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。
Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。
Elasticsearch 数据管理的顶层单位就叫做 Index(索引),相当于关系型数据库里的数据库的概念。另外,每个Index的名字必须是小写。
Index里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。Document 使用 JSON 格式表示。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
Document 可以分组,比如employee这个 Index 里面,可以按部门分组,也可以按职级分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document,类似关系型数据库中的数据表。
不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。
文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。
每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。
在 Elasticsearch 中,文档(Document)归属于一种类型(Type),而这些类型存在于索引(Index)中,下图展示了Elasticsearch与传统关系型数据库的类比:
elasticsearch的操作主要是通过http请求,restful风格,json作为请求体来操作的;比较方便操作,且比较容易,本次暂时分享 springdata 来操作es,至于用es的http请求和java原生操作的api,下次分享
在安装好es后,我们首先创建springdata项目,这里就不一一详细说明了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
spring:
data:
elasticsearch:
# 连接的es名称
cluster-name: elasticsearch
# 连接的节点地址,如果是集群则用 多个地址连接用 , 相隔
# Java调用时端口是9300,restfu调用端口是9200,注意区分
cluster-nodes: 192.168.66.134:9300
//文档 相当于数据库的数据表 indexName 文档名称 type 类型 shards 分片片数 replicas每个分区默认的备份数
@Document(indexName = "goods", type = "docs", shards = 3, replicas = 1)
@Data
public class Goods {
@Id //主键id
private Long id;
//属性
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;//标题
@Field(type = FieldType.Keyword)
private String category;//分类
@Field(type = FieldType.Keyword)
private String brand;//品牌
@Field(type = FieldType.Double)
private Double price;//价格
@Field(type = FieldType.Keyword,index = false)
private String images;//图片
public Goods(){}
public Goods(Long id, String title, String category, String brand, Double price, String images) {
this.id = id;
this.title = title;
this.category = category;
this.brand = brand;
this.price = price;
this.images = images;
}
}
注解解释:
@Document 注解:
public @interface Field {
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
FieldIndex index() default FieldIndex.analyzed; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到
DateFormat format() default DateFormat.none; //时间类型的格式化
String pattern() default "";
boolean store() default false; //默认情况下不存储原文
String searchAnalyzer() default ""; //指定字段搜索时使用的分词器
String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器
String[] ignoreFields() default {}; //如果某个字段需要被忽略
boolean includeInParent() default false;
}
@Field注解:
public @interface Field {
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
FieldIndex index() default FieldIndex.analyzed; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到
DateFormat format() default DateFormat.none; //时间类型的格式化
String pattern() default "";
boolean store() default false; //默认情况下不存储原文
String searchAnalyzer() default ""; //指定字段搜索时使用的分词器
String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器
String[] ignoreFields() default {}; //如果某个字段需要被忽略
boolean includeInParent() default false;
}
FieldType类型:
public enum FieldType {
Text,
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword
}
Text类型:索引全文字段,如电子邮件正文的描述或者产品描述。这些字段被分析器将字符串转换为单个术语列表。分析过程允许es在每个的全文域中搜索单个单词。文本字段不用于排序,也很少用于聚合
Object类型:Json文档本质上是分层的,文档可能包含内部对象,而这些对象又可能包含内部对象本身。test是在搜索时候不会被分词,而Keyword会被分词
//形参一个是实体类和主键属性
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {
}
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
操作很简单,使用期api即可
@Test
public void demo2() {
//创建索引库
elasticsearchTemplate.createIndex(Goods.class);
//删除索引库
elasticsearchTemplate.deleteIndex(Goods.class);
}
@Test
public void demo() {
// 准备文档数据:
List<Goods> list = new ArrayList<>();
list.add(new Goods(1L, "苹果11proMax", "手机", "苹果", 9200.00, "/13123.jpg"));
list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "/13123.jpg"));
list.add(new Goods(3L, "华为META30", "手机", "华为", 4499.00, "/13123.jpg"));
list.add(new Goods(4L, "小米9pro", "手机", "小米", 4299.00, "/13123.jpg"));
list.add(new Goods(5L, "荣耀V30", "手机", "华为", 2799.00, "/13123.jpg"));
list.add(new Goods(6L, "华为p30", "手机", "华为", 4299.00, "/13123.jpg"));
list.add(new Goods(7L, "苹果11pro", "手机", "苹果", 8399.00, "/13123.jpg"));
list.add(new Goods(8L, "苹果11", "手机", "苹果", 5499.00, "/13123.jpg"));
// 批量保存
goodsRepository.saveAll(list);
//保存单个
goodsRepository.save(new Goods(8L, "苹果11", "手机", "苹果", 5499.00, "/13123.jpg"));
//删除所有
goodsRepository.deleteAll();
List<Goods> arr = (List<Goods>) goodsRepository.findAll();
//根据id查找
Goods result = goodsRepository.findById(1L).get();
}
//自定义查询
@Test
public void demo4() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//过滤查询source 第一个参数是需要显示的,第二个参数是不需要显示
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[]{"id", "category", "images"}));
//搜索match 单个匹配单个字段 会根据分词器查询 第一个参数是 字段,第二个搜索内容
queryBuilder.withQuery(QueryBuilders.matchQuery("title","苹果11proMax"));
//搜索 multi_match 单个匹配多个字段 会根据分词器查询 第一个参数是 搜索内容,第二个是多个字段拼接
queryBuilder.withQuery(QueryBuilders.multiMatchQuery("苹果","title","brand"));
//精确匹配 term 不会分词匹配
queryBuilder.withQuery(QueryBuilders.termQuery("title","华为"));
//多词条精确匹配 第一个参数是 字段 其余都是 搜索词
queryBuilder.withQuery(QueryBuilders.termsQuery("title","华为","苹果"));
//布尔组合
queryBuilder.withQuery(QueryBuilders.boolQuery()
//与 必须包含当前 查询的结果
.must(QueryBuilders.matchQuery("title", "苹果11proMax"))
//非 不可以包含符合搜索的条件
.mustNot(QueryBuilders.matchQuery("title", "苹果11proMax"))
//或 可以包含查询的结果
.should(QueryBuilders.matchQuery("title", "苹果11proMax"))
//过滤符合当前查询结果的 搜索结果
.filter(QueryBuilders.matchQuery("title", "苹果11proMax")));
//范围查询 可以链式查询 第一个是 字段 gt 大于 gte 大于等于 lt 小于 lte小于等于
queryBuilder.withQuery(QueryBuilders.rangeQuery("price").gt(1000.0).lt(4400.0));
//模糊查询 第一个是字段 第二个是搜索的模糊条件
queryBuilder.withQuery(QueryBuilders.fuzzyQuery("title", "华为"));
//排序 同时可以设置开始页(es开始页为0) 和页码 设置排序字段 多字段排序时候 按照从前到后排序字段
queryBuilder.withPageable(PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC,"price")));
//聚合
queryBuilder.addAggregation(AggregationBuilders.terms("brand").field("1"));
AggregatedPage<Goods> page = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
//返回的查询结果.
//查询到的元素数量
System.out.println(page.getTotalElements());
//获取到查询的集合
System.out.println(page.getContent());
//获取查询的页数
System.out.println(page.getTotalPages());
}
//聚合处理
@Test
public void demo41() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//聚合 第一个为聚合的组名称 第二个为聚合的字段
//根据词条内容分组,词条内容完全匹配的为一组
queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
//根据数值阶梯分组,与日期类似
queryBuilder.addAggregation(AggregationBuilders.histogram("prices").field("price").interval(500.0));
//根据日期阶梯分组
queryBuilder.addAggregation(AggregationBuilders.dateHistogram("dataPrices").field("prices").dateHistogramInterval(DateHistogramInterval.QUARTER));
//数值和日期的范围分组,指定开始和结束,然后按段分组
queryBuilder.addAggregation(AggregationBuilders.range("rangePrice").field("price").addRange(1000.0,20000.0));
AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
//返回的查询结果
//查询到的元素数量
System.out.println(result.getTotalElements());
//获取到查询的集合
result.getContent().stream().forEach(System.out::println);
//获取查询的页数
System.out.println(result.getTotalPages());
//获取所有的聚合
Aggregations aggregations = result.getAggregations();
//词条聚合解析
Terms brandTerms = aggregations.get("brands");
brandTerms.getBuckets().forEach(b -> {
System.out.println("品牌 = " + b.getKeyAsString());
System.out.println("count = " + b.getDocCount());
});
//数值阶梯聚合解析
Histogram price = aggregations.get("prices");
price.getBuckets().forEach(b ->{
System.out.println("价格 = " + b.getKeyAsString());
System.out.println("数量" + b.getDocCount());
});
//日期范围聚合解析
Histogram data = aggregations.get("dataPrices");
data.getBuckets().forEach(b ->{
System.out.println("价格 = " + b.getKeyAsString());
System.out.println("数量" + b.getDocCount());
});
//范围聚合解析
Range range = aggregations.get("rangePrice");
range.getBuckets().forEach(b ->{
System.out.println("价格2 = " + b.getKeyAsString());
System.out.println("数量2" + b.getDocCount());
});
}
在测试过程时候,使用springboot来操作redis和es的时候,可能会出现启动报错的问题,建议使用一下配置文件可处理
@Configuration
public class ElasticSearchConfig {
/**
* redis和elasticsearch整合时候会报错
* 防止netty的bug
* java.lang.IllegalStateException: availableProcessors is already set to [4], rejecting [4]
*/
@PostConstruct //在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
void init() {
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
}
es是目前开发比较主流的工具之一,由于其优秀的搜索功能,使其也可以作为数据库之外的存储数据,用作数据分析也是很不错的工具;这里学习推荐先学习其 http请求操作es的,其他的封装操作es的工具市面有不少,有点公司也有自己封装的,但是都是用http请求为基础的;
注:es的springdata操作不太详细,具体详细 会慢慢补充