elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
elasticsearch底层是基于lucene来实现的。
什么是elasticsearch?
一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
什么是elastic stack(ELK)?
是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch
什么是Lucene?
是Apache的开源搜索引擎类库,提供了搜索引擎的核心API
注意:
这里只是一个笔记,我是菜鸟,有啥不对的地方提出来,一起学习
https://www.elastic.co/cn/downloads/past-releases#elasticsearch
可以选择下载的版本,这里建议7.6.1,高了可能就要jdk1.8以上了
解压
es的bin目录下点elasticsearch.bat进行启动或命令行启动
安装配置中文分词工具(根据不同版本下载不同的分词插件)【elasticsearch默认对中文不友好,所以要安装中文分词器ik】
下载网址:https://github.com/medcl/elasticsearch-analysis-ik/releases?page=8
在浏览器中输入localhost:9200检测是否运行成功(出现如下的界面则说明elasticsearch运行成功)
分词:
kibana的版本和elasticsearch的版本必须一致
下载网址:https://www.elastic.co/cn/downloads/past-releases#kibana
到\bin目录,双击kibana.bat(前提是保证elasticsearch服务已经启动)
访问:http://localhost:5601
xpack.security.enabled: true
xpack.license.self_generated.type: basic
xpack.security.transport.ssl.enabled: true
2. 重启elasticsearch服务
3. 设置elasticsearch密码
到bin目录下输入cmd进入命令窗口,并执行以下命令
elasticsearch-setup-passwords interactive
5.找到 Kibana 目录下的config中的kibana.yml文件添加以下
elasticsearch.username: "elastic"
elasticsearch.password: "123456"
不设置的话kibana连不上elasticsearch就启动不了
elastic
这里输入的是es的账号密码而不是kibana的账号密码
输入elastic,和密码才能登录
https://blog.csdn.net/abst122/article/details/125508650#14eskibana_5673
elasticsearch对版本比较严谨,版本对不上就报一些奇奇怪怪的错误
官方版本对照:https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#preface.versions
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
<relativePath/>
parent>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-high-level-clientartifactId>
dependency>
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client'
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.demo.security.config")
public class ElasticRestClientConfig extends AbstractElasticsearchConfiguration {
@Value("${spring.elasticsearch.rest.uris}")
private String url;
@Value("${spring.elasticsearch.rest.username}")
private String username;
@Value("${spring.elasticsearch.rest.password}")
private String password;
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
url = url.replace("http://", "");
String[] urlArr = url.split(",");
HttpHost[] httpPostArr = new HttpHost[urlArr.length];
for (int i = 0; i < urlArr.length; i++) {
HttpHost httpHost = new HttpHost(urlArr[i].split(":")[0].trim(),
Integer.parseInt(urlArr[i].split(":")[1].trim()), "http");
httpPostArr[i] = httpHost;
}
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
RestClientBuilder builder = RestClient.builder(httpPostArr)
// 异步httpclient配置
.setHttpClientConfigCallback(httpClientBuilder -> {
// 账号密码登录
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
// httpclient连接数配置 未知情况暂时不设置
// httpClientBuilder.setMaxConnTotal(50);
// httpClientBuilder.setMaxConnPerRoute(20);
// httpclient保活策略
httpClientBuilder.setKeepAliveStrategy(((response, context) -> Duration.ofMinutes(5).toMillis()));
return httpClientBuilder;
});
return new RestHighLevelClient(builder);
}
}
@Document:在类级别应用,以指示该类是映射到数据库的候选对象。最重要的属性是:
@Id:在字段级别应用,以标记用于标识目的的字段。
@Transient:默认情况下,存储或检索文档时,所有字段都映射到文档,此注释不包括该字段。
@PersistenceConstructor:标记从数据库实例化对象时要使用的给定构造函数,甚至是受保护的程序包。构造函数参数按名称映射到检索到的Document中的键值。
@Field:在字段级别应用并定义字段的属性,大多数属性映射到各自的Elasticsearch映射定义(以下列表不完整,请查看注释Javadoc以获得完整参考):
将字段标记为geo_point数据类型。
映射配置为@MultiField 的,会映射为多字段,并使用对应的分析器。
所谓多字段,是说同一个字段同时建立多种索引,比如 text 和 keyword。注意,不能用 FieldType.Auto
注意:这里可以不需要mapping文件,定义mapping文件后实体类上字段的注解会失效
//设置mapping
@Mapping(mappingPath = "elasticsearch_mapping.json")//设置mapping
//设置setting
@Setting(settingPath = "elasticsearch/settings.json")
@Data
@AllArgsConstructor
@NoArgsConstructor
// @Document是SpringDataES框架标记实体类的注解
// indexName指定的是索引名称,运行时items索引不存在,SpringDataES会自动创建这个索引
@Document(indexName = Constants.ES_HOTEL_INDEX, indexStoreType = Constants.ES_COMMODITY_INDEX_TYPE)
public class Hotel implements Serializable {
@Id// SpringData标记当前属性为ES主键的注解
private Long id;
/**
* 酒店名称
*/
@Field(type = FieldType.Text,
analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")// SpringData标记title属性的支持分词的类似和相关分词器
private String title;
/**
* 分类
*/
@Field(type = FieldType.Keyword)// Keyword是不需要分词的字符串类型
private String category;
/**
* 品牌
*/
@Field(type = FieldType.Keyword)
private String brand;
/**
* 价格
*/
@Field(type = FieldType.Double)
private BigDecimal price;
// 设置index=false 今后所有不会称为查询条件的列都照此配置
// 不索引,不代表不保存数据,数据本身仍然是保存在ES的
@Field(type = FieldType.Keyword,index = false)
private String imgPath;
/**
* 定位
*/
@GeoPointField
private GeoPoint localhost;
}
查询name=如家:
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("title", "如家"));
查询价格大于等于99.9,并且小于等于120:
BoolQueryBuilder queryBuilder = QueryBuilders.rangeQuery("price")
.gte(99.9)// gt大于,不包含边界值
.lte(120);// lt小于,不包含边界值
查询姓名中包含有如家
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.wildcardQuery("title", "*如家*"));
这个我试了没起作用,不知啥原因
实体类
/**
* 商品名称
*/
@MultiField(mainField = @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word"),
otherFields = @InnerField(suffix = "inner", type = FieldType.Text, analyzer = "pinyin"))
private String name;
模糊查询名称和分类
if (StrUtil.isNotBlank(indistinct)) {
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(indistinct, "memberName", "customerCategory.name");
queryBuilder.must(multiMatchQueryBuilder);
}
查询名称为:如家,并且价格在100.5-520之间:
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("title", "如家"))
.must(QueryBuilders.rangeQuery("price")
.gte(100.5)
.lte(520));
查询城市在北京、上海、杭州,并且价格在100.5-520之间,查询名称为如家
List<String> list = Arrays.asList("北京", "上海", "杭州");
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("title", "如家"))
.must(QueryBuilders.termsQuery("cityName", list))
.must(QueryBuilders.rangeQuery("price")
.gte(100.5)
.lte(520));
查询姓名包含如家或者是城市是北京,should相当于或者or
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.wildcardQuery("title", "*如家*"))
.should(QueryBuilders.termQuery("cityName", "北京"));
查询品牌为如家,名称包含如家或地址为北京的记录,**minimumShouldMatch(1)**表示最少要匹配到一个should条件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("brand", "如家"))
.should(QueryBuilders.wildcardQuery("title", "*如家*"))
.should(QueryBuilders.termQuery("cityName", "北京"))
.minimumShouldMatch(1);
查询name有值,tag不存在值
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.existsQuery("name"))
.mustNot(QueryBuilders.existsQuery("aaa"));
类似jpa:https://blog.csdn.net/abst122/article/details/126423932#Spring_Data_Jpa_1514
Pageable pageable = PageRequest.of(request.getCurrent()-1, request.getPageSize());
Page<Hotel> page = searchHotelRepository.findAll(pageable);
地理位置查询https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/
@Override
public Page<Hotel> findPage(double latitude, double longitude, String distance, Pageable pageable) {
// 实现了SearchQuery接口,用于组装QueryBuilder和SortBuilder以及Pageable等
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withPageable(pageable);
// 间接实现了QueryBuilder接口
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 以某点为中心,搜索指定范围
GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("localhost");
distanceQueryBuilder.point(latitude, longitude);
// 定义查询单位:公里
distanceQueryBuilder.distance(distance, DistanceUnit.KILOMETERS);
boolQueryBuilder.filter(distanceQueryBuilder);
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
// 按距离升序
GeoDistanceSortBuilder distanceSortBuilder =
new GeoDistanceSortBuilder("localhost", latitude, longitude);
distanceSortBuilder.unit(DistanceUnit.KILOMETERS);
distanceSortBuilder.order(SortOrder.ASC);
nativeSearchQueryBuilder.withSort(distanceSortBuilder);
return searchHotelRepository.search(nativeSearchQueryBuilder.build());
}
@Override
public PageData<HotelResponse> findLocalHostPage(HotelRequest request) {
Pageable pageable = PageRequest.of(request.getCurrent()-1, request.getPageSize());
Page<Hotel> page = findPage(request.getLatitude(), request.getLongitude(), request.getDistance(), pageable);
return new PageData<>(page.getTotalElements(),page.getContent().stream().map(s -> {
HotelResponse response = new HotelResponse();
BeanUtil.copyProperties(s, response);
double lon = s.getLocalhost().getLon();
double lat = s.getLocalhost().getLat();
response.setLatitude(lat);
response.setLongitude(lon);
// 计算两点之间的距离
double distance = GeoDistance.ARC.calculate(request.getLatitude(), request.getLongitude(), lat, lon, DistanceUnit.KILOMETERS);// GeoDistance.ARC和GeoDistance.PLANE,前者比后者计算起来要慢,但精确度要比后者高
response.setDistance(distance);
return response;
}).collect(Collectors.toList()));
}
使用RestHighLevelClient基本流程:
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//Fuzzy 查找
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "张").fuzziness(Fuzziness.ONE);
searchSourceBuilder.query(fuzzyQueryBuilder);
public void testHighlight() throws IOException {
//1.创建 SearchRequest搜索请求,并指定要查询的索引
SearchRequest searchRequest = new SearchRequest("db_idx4");
//2.创建 SearchSourceBuilder条件构造。
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//Term 查找
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("address", "王者");
searchSourceBuilder.query(termQueryBuilder);
//自定义高亮 查找
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("");
highlightBuilder.postTags("");
highlightBuilder.field("address");
highlightBuilder.requireFieldMatch(false); //多字段时,需要设置为false
highlightBuilder.field("desc");
searchSourceBuilder.highlighter(highlightBuilder);
//3.将 SearchSourceBuilder 添加到 SearchRequest中
searchRequest.source(searchSourceBuilder);
//4.执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//5.解析查询结果
System.out.println("花费的时长:" + searchResponse.getTook());
SearchHits hits = searchResponse.getHits();
System.out.println("符合条件的总文档数量:" + hits.getTotalHits().value);
hits.forEach(p -> {
System.out.println("文档原生信息:" + p.getSourceAsString());
System.out.println("高亮信息:" + p.getHighlightFields());
});
}
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
SearchRequest request = new SearchRequest("hotel");
queryBuilder.must(QueryBuilders.geoDistanceQuery("location")
// 在3km之内
.distance("3", DistanceUnit.KILOMETERS)
// 以那个点为中心
.point(31.256224D, 121.462311D)
.geoDistance(GeoDistance.ARC)
// 一个查询的名字,可选
.queryName("optional_name"));
sourceBuilder.query(queryBuilder);
request.source(sourceBuilder);
// 排序
searchSourceBuilder.sort(
// 不同的类型使用不同的SortBuilder
new GeoDistanceSortBuilder("location", 31.256224D, 121.462311D)
.order(SortOrder.DESC)
.unit(DistanceUnit.METERS)
.geoDistance(GeoDistance.ARC));
public PageResult search(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
buildBasicQuery(params, request);
// 2.2.分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
// 2.3.排序
String location = params.getLocation();
if (location != null && !location.equals("")) {
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS)
);
}
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void buildBasicQuery(RequestParams params, SearchRequest request) {
// 1.构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 城市条件
if (params.getCity() != null && !params.getCity().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
// 品牌条件
if (params.getBrand() != null && !params.getBrand().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
// 星级条件
if (params.getStarName() != null && !params.getStarName().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 价格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 2.算分控制
FunctionScoreQueryBuilder functionScoreQuery =
QueryBuilders.functionScoreQuery(
// 原始查询,相关性算分的查询
boolQuery,
// function score的数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery("isAD", true),
// 算分函数
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
}