elasticsearch:一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析
elastic stack(ELK):是以elasticsearch为核心的技术栈
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK),被广泛应用在日志数据分析、实时监控等领域
elasticsearch是elastic stack的核心,负责存储、搜索、分析数据
kibana 数据可视化
Logstash、Beats 数据抓取
elasticsearch采用倒排索引
文档:每条数据就是一个文档
词条:文档按照语义分成的词语
id title price
1 小米手机 3499
2 华为手机 4999
3 华为小米充电器 49
词条 文档
小米 1,3,4
手机 1,2
华为 2,3
充电器 3
elasticsearch是面向文档存储的,文档数据会被序列化json格式后存储到elasticsearch
索引:相同类型的文档集合
映射:索引中文档的字段约束信息,类似表结构约束
MySQL Elasticsearch
table表 index
row行 document
column列 field
schema约束 mapping
SQL DSL(实现CRUD)
MySQL:擅长事务类型操作,可以确保数据的安全和一致性
Elasticsearch:擅长海量数据的搜索、分析、计算
Elasticsearch内置的分词对中文处理不太友好,一般使用IK分词器引
mapping是对索引库中文档的约束,常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
RestClient:封装DSL语句,通过http请求发送给ES
RestClient操作索引库
初始化
private RestHighLevelClient client;
@BeforeEach
void setUp(){
this.client=new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.136.160:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
创建索引库
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
删除索引库
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
判断索引库是否存在
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
RestClient操作文档
新增文档
//根据id查询mysql数据
Hotel hotel = iHotelService.getById(60363L);
//转换为文档类型
HotelDoc hotelDoc=new HotelDoc(hotel);
//准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//准备json文档
request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
//发送请求
client.index(request,RequestOptions.DEFAULT);
查询文档
//根据id查询mysql数据
//准备Request对象
GetRequest request = new GetRequest("hotel", "60363");
//发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
删除文档
DeleteRequest request = new DeleteRequest("hotel", "60363");
client.delete(request,RequestOptions.DEFAULT);
更新文档
//准备request
UpdateRequest request = new UpdateRequest("hotel", "60363");
//准备请求参数
request.doc(
"price","1345"
);
//发送请求
client.update(request,RequestOptions.DEFAULT);
批量导入文档
// 批量查询酒店数据
List<Hotel> hotels = hotelService.list();
// 1.创建Request
BulkRequest request = new BulkRequest();
// 2.准备参数,添加多个新增的Request
for (Hotel hotel : hotels) {
// 2.1.转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.创建新增文档的Request对象
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
DSL查询文档
- 查询所有:查询出所有数据,一般测试用。例如:match_all
- 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
- 地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件例如:
- bool
- function_score
检索所有
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
全文检索
GET /hotel/_search
{
"query": {
"match": {
"city": "外滩"
}
}
}
精确查询
range(查询根据值的范围查询)
term(根据词条精确值)
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 500
}
地理算分
GET /hotel/_search
{
"query": {
"geo_distance":{
"distance":"15km",
"location":"31.21,121.5"
}
复合查询:将上述各种查询条件组合起来
fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
GET /hotel/_search
{
"query": {
"function_score": {
"query": { .... }, // 原始查询,可以是任意条件
"functions": [ // 算分函数
{
"filter": { // 满足的条件,品牌必须是如家
"term": {
"brand": "如家"
}
},
"weight": 2 // 算分权重为2
}
],
"boost_mode": "sum" // 加权模式,求和
}
bool:布尔查询是一个或多个查询子句的组合
must:必须匹配每个子查询,类似与
should:选择性匹配子查询,类似或
must_not:必须不匹配
filter:必须匹配,不参与算分
排序
ES支持对搜索结果排序,默认是根据相关度算分来排序,可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": {
"order": "desc"
}
},
{
"price": {
"order": "asc"
}
}
]
}
分页
ES默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了
ES通过修改from、size参数控制要返回的分页结果
from 分页开始的位置,默认为0 (page-1)*pagesize
size 期望获取的文档总数 pagesize
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
}
}
],
"from": 10,
"size": 10
}
高亮
在搜索结果中把搜索关键字突出显示
默认情况下,ES搜索字段必须与高亮字段一致
GET /hotel/_search
{
"query": {
"match": {
"all": "外滩"
}
},
"highlight": {
"fields": {
"name": {//指定高亮的字段
"pre_tags":"<em>",//用来标记高亮字段的前置标签
"post_tafs":"</em>"//用来标记高亮字段的后置标签
}
}
}
}
RestClient查询文档
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
request.source()
.query(QueryBuilders.matchAllQuery());
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
// 4.2.文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
for (SearchHit hit : hits) {
// 获取文档source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
match查询
request.source()
.query(QueryBuilders.matchQuery("all", "如家"));
精确查询
QueryBuilders.termQuery("city","杭州")
QueryBuilders.rangeQuery("price").gte(100).lte(150)
布尔查询
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.准备BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.2.添加term
boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
// 2.3.添加range
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
分页排序
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
request.source().query(QueryBuilders.matchAllQuery());
// 2.2.排序 sort
request.source().sort("price", SortOrder.ASC);
// 2.3.分页 from、size
request.source().from((page - 1) * size).size(5);
高亮
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
request.source().query(QueryBuilders.matchQuery("all", "如家"));
// 2.2.高亮
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
自动补全
PUT /hotel
{
"settings": {
"analysis": {
"analyzer": {
"text_anlyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": {
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false,
"keep_first_letter" :false
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart"
},
"suggestion":{
"type": "completion",
"analyzer": "completion_analyzer"
}
}