1、面临问题
2、解决方案-ElasticSearch
2.1、简介
【elasticsearch】
:::tips elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容. :::
【ELK】
:::tips elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。 :::
【Lucene】
:::tips
elasticsearch底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。
官网:https://lucene.apache.org/
:::
2.2、优缺点
【优点】
【缺点】
2.3、运行原理-倒排索引
【正向索引】
:::tips
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程.
优点:
可以给多个字段创建索引
根据索引字段搜索、排序速度非常快
缺点:
根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
:::
【倒排索引】
:::tips
倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
优点:
根据词条搜索、模糊搜索时,速度非常快
缺点:
只能给词条创建索引,而不是字段
无法根据字段做排序
:::
2.4、相关名词
【索引库】
:::tips 索引(Index),就是相同类型的文档的集合。类似于mysql中的表 :::
【mapping映射】
:::tips 映射(mapping),是索引中文档的字段约束信息,类似mysql表的结构约束。 :::
【文档】
:::tips 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 :::
【词条】
:::tips 词条(Term),对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。 :::
2.5、Kibana
:::tips 可视化工具,在浏览器输入DSL语句即可查看ES中的内容。 :::
3、使用步骤
3.1、Kibana浏览器操作-索引库&文档
【索引库操作】
【创建索引库】
PUT /索引库名称{"mappings": {"properties": {"字段名":{"type": "text","analyzer": "ik_smart"},"字段名2":{"type": "keyword","index": "false"},"字段名3":{"properties": {"子字段": {"type": "keyword"}}}}}}
【查看索引库】
【删除索引库】
【修改索引库(追加)】
PUT /索引库名/_mapping{"properties":{"新字段":{"type":"数据类型(integer)"}}}
【文档操作】
【新增文档】
POST /索引库名/_doc/文档id(不给会随机生成id){"字段1": "值1","字段2": "值2","字段3": {"子属性1": "值3","子属性2": "值4"}}
【查看文档】
:::tips GET /索引库名/_doc/文档id :::
【删除文档】
:::tips DELETE /索引库名/_doc/文档id :::
【修改文档】
【全量修改】
PUT /索引库名/_doc/文档id{"字段1": "值1","字段2": "值2"}
【增量修改】
POST /索引库名/_update/文档id{"doc": {"字段名": "新的值"}}
3.2、java代码操作-索引库&文档
【导入依赖】
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId></dependency>
【与版本对应】
<properties><java.version>1.8</java.version><elasticsearch.version>7.12.1</elasticsearch.version></properties>
【初始化连接&关闭连接】
第一种模式:private RestHighLevelClient client;// 创建连接@BeforeEachvoid setUp() {this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.248.222:9200")));}// 关闭连接@AfterEachvoid tearDown() throws IOException {this.client.close();}第二种模式:/*** 在单元测试方法执行前执行*/@BeforeEachpublic void init(){client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.200.129", 9200, "http")));}/*** 在单元测试类执行后执行*/@AfterEachpublic void destory() throws IOException {if (client!=null){client.close();}}
【索引库操作】
【创建索引库】
// 创建索引库@Testpublic void createIndex() throws IOException {// 1.创建请求语义对象CreateIndexRequest request = new CreateIndexRequest("hotel");// 添加 sourcerequest.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);// 2.发送请求CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);// 3.解析响应boolean flag = response.isAcknowledged();// 打印结果System.out.println(flag);}
【删除索引库】
@Testpublic void deleteIndex() throws IOException {// 1.创建请求语义对象DeleteIndexRequest request = new DeleteIndexRequest("hotel");// 2.发送请求AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);// 3.解析响应boolean flag = response.isAcknowledged();// 打印结果System.out.println(flag);}
【查看索引库是否存在】
// 查看索引库是否存在@Testpublic void existsIndex() throws IOException {// 1.创建请求语义对象GetIndexRequest request = new GetIndexRequest("hotel");// 2.发送请求boolean flag = client.indices().exists(request, RequestOptions.DEFAULT);// 3.解析响应// 打印结果System.out.println(flag);}
【文档操作】
【添加文档数据】
// 添加文档数据@Testpublic void add() throws IOException {// 1.根据id获取数据Hotel hotel = hotelService.getById(36934L);// 2.转换成 ES 对应的数据HotelDoc hotelDoc = new HotelDoc(hotel);// 3.转换成JSONString hotelDocJson = JSON.toJSONString(hotelDoc);// 4.创建请求语义对象IndexRequest request = new IndexRequest("hotel");request.id(hotel.getId() + "");request.source(hotelDocJson, XContentType.JSON);// 5.发送请求IndexResponse response = client.index(request, RequestOptions.DEFAULT);// 6.解析响应结果DocWriteResponse.Result result = response.getResult();System.out.println(result);}
【查询文档数据】
@Testpublic void select() throws IOException {// 1.创建请求语义对象GetRequest request = new GetRequest("hotel","36934");// 2.发送请求GetResponse response = client.get(request, RequestOptions.DEFAULT);// 3.解析响应结果String json = response.getSourceAsString();HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc);}
【修改文档数据】
// 修改文档数据@Testpublic void update() throws IOException {// 1.创建请求语义对象UpdateRequest request = new UpdateRequest("hotel","36934");request.doc("name", "7天连锁酒店(上海宝山路地铁站店)");// 2.发送请求UpdateResponse response = client.update(request, RequestOptions.DEFAULT);// 3.解析响应结果DocWriteResponse.Result result = response.getResult();System.out.println(result);}
【删除文档数据】
// 删除文档数据@Testpublic void delete() throws IOException {// 1.创建请求语义对象DeleteRequest request = new DeleteRequest("hotel","36934");// 2.发送请求DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);// 3.解析响应结果DocWriteResponse.Result result = response.getResult();System.out.println(result);}
【批量添加】
// 批量添加文档数据@Testpublic void add() throws IOException {// 1.批量查询酒店数据List<Hotel> hotels = hotelService.list();// 2.创建请求语义对象BulkRequest request = new BulkRequest();for (Hotel hotel : hotels) {// 转换格式HotelDoc hotelDoc = new HotelDoc(hotel);String hotelDocJson = JSON.toJSONString(hotelDoc);IndexRequest indexRequest = new IndexRequest("hotel");indexRequest.id(hotel.getId() + "");indexRequest.source(hotelDocJson, XContentType.JSON);request.add(indexRequest);}// 5.发送请求BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);// 6.解析响应结果RestStatus status = response.status();System.out.println(status);}
3.3、Kibana浏览器-查询&结果处理
GET /索引库名/_search{"query": {"match_all": {}}}
【全文检索查询】
【多字段】
GET /索引库名/_search{"query": {"multi_match": {"query": "内容","fields": ["字段1", "字段2"]}}}
【单字段】
GET /hotel/_search{"query": {"match": {"字段": "值"}}}
【精确查询】
【term查询】
GET /索引库名/_search{"query": {"term": {"字段": {"value": "值"}}}}
【range查询】
GET /索引库名/_search{"query": {"range": {"字段": {"gte": 10, // 这里的gte代表大于等于,gt则代表大于"lte": 20 // lte代表小于等于,lt则代表小于}}}}
【地理坐标查询】
GET /索引库名/_search{"query": {"geo_bounding_box": {"字段": {"top_left": { // 左上点"lat": 31.1,"lon": 121.5},"bottom_right": { // 右下点"lat": 30.9,"lon": 121.7}}}}}
【附近/距离查询】
GET /索引库名/_search{"query": {"geo_distance": {"distance": "15km", // 半径"字段": "31.21,121.5" // 圆心}}}
【复合查询】
【算分函数查询】
GET /hotel/_search{"query": {"function_score": {"query": {// 原始查询条件 根据BM25算法进行算分"match": {"all": "外滩"}},"functions": [{// 过滤条件 符合条件重新算分"filter": {"term": {"brand": "如家"}},//算分函数 weight是常量值"weight": 10}],//加权模式 默认是multiply"boost_mode": "sum"}}}
【布尔查询】
GET /hotel/_search{"query": {"bool": {//必须匹配"must": [{"match": {"all": "上海"}}],//选择匹配"should": [{"term": {"brand": {"value": "皇冠假日"}}},{"term": {"brand": "华美达"}}],//必须不匹配 不参与算分"must_not": [{"range": {"price": {"lte": 400}}}],//必须匹配 不参与算分"filter": [{"geo_distance": {"distance": "10km","location": {"lat": 31.21,"lon": 121.5}}}]}}}
【排序】
【普通字段】
GET /索引库名/_search{"query": {"match_all": {}},"sort": [{"字段": "desc" // 排序字段、排序方式ASC、DESC}]}
【地理坐标】
GET /hotel/_search{"query": {"match_all": {}},"sort": [{"_geo_distance": {"地理坐标相关字段": {"lat": 31.21,"lon": 121.5},"order":"asc",//单位"unit":"km"}}]}
【分页】
【基本分页】
GET /hotel/_search{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10, // 期望获取的文档总数"sort": [{"price": "asc"}]}
【深度分页】
【高亮】
GET /hotel/_search{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10, // 期望获取的文档总数"sort": [{"price": "asc"}]}
3.4.ES相关的RESTClient代码实现
@Testpublic void search01() throws IOException {//1.创建请求语义对象SearchRequest request = new SearchRequest("hotel");// QueryBuilders: 构建查询类型//查询所有request.source().query(QueryBuilders.matchAllQuery());//条件查询searchRequest.source().query(QueryBuilders.matchQuery("name","如家酒店"));searchRequest.source().query(QueryBuilders.multiMatchQuery("如家酒店","name","brand"));//精准查询//term查询searchRequest.source().query(QueryBuilders.termQuery("brand","如家"));//range范围查询searchRequest.source().query(QueryBuilders.rangeQuery("price").gte("100").lte("200"));//布尔查询// 2.1.准备BooleanQueryBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 2.2.添加termboolQuery.must(QueryBuilders.termQuery("city", "杭州"));// 2.3.添加rangeboolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));request.source().query(boolQuery);//排序、分页// 2.2.排序 sortrequest.source().sort("price", SortOrder.ASC);// 2.3.分页 from、sizerequest.source().from((page - 1) * size).size(5);//2.发送请求给ESSearchResponse response = client.search(request, RequestOptions.DEFAULT);//3.处理返回结果handleResponse(response);}高亮显示@Testvoid testHighlight() throws IOException {// 1.准备RequestSearchRequest request = new SearchRequest("hotel");// 2.准备DSL// 2.1.queryrequest.source().query(QueryBuilders.matchQuery("all", "如家"));// 2.2.高亮request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);}
1、controller@Autowiredprivate IHotelService service;/*** 根据关键字查询* @param requestParams* @return* @throws IOException*/@PostMapping("/list")public PageResult searchAll(@RequestBody RequestParams requestParams) throws IOException {return service.search(requestParams);}2、public interface IHotelService extends IService<Hotel> {/*** 根据关键字搜索酒店信息* @param requestParams 请求参数对象,包含用户输入的关键字* @return 酒店文档列表*/PageResult search(RequestParams requestParams) throws IOException;}3、 @Autowiredprivate RestHighLevelClient client;/*** 根据关键字搜索酒店信息** @param requestParams 请求参数对象,包含用户输入的关键字* @return 酒店文档列表*/@Overridepublic PageResult search(RequestParams requestParams) throws IOException {//创建请求对象SearchRequest request = new SearchRequest("hotel");//根据条件进行查询buildBasicQuery(request, requestParams);//分页查询request.source().from((requestParams.getPage() - 1) * requestParams.getSize()).size(requestParams.getSize());//处理地址位置if (requestParams.getLocation() != null && requestParams.getLocation().length() > 0) {request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(requestParams.getLocation())).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));}//发送请求到esSearchResponse response = client.search(request, RequestOptions.DEFAULT);//处理响应return handlerResponse(response);}4、//响应结果处理private PageResult handlerResponse(SearchResponse response) {SearchHits hits = response.getHits();//获取总条数long count = hits.getTotalHits().value;//获取数据SearchHit[] hitsHits = hits.getHits();//创建一个已经用来装HotelDocList<HotelDoc> docs = new ArrayList<>();//遍历for (SearchHit hit : hitsHits) {String json = hit.getSourceAsString();//反序列化HotelDoc doc = JSON.parseObject(json, HotelDoc.class);//获取距离Object[] sortValue = hit.getSortValues();for (Object val : sortValue) {doc.setDistance(val);}docs.add(doc);}return new PageResult(count, docs);}5、查询过滤条件//查询过滤条件private void buildBasicQuery(SearchRequest request, RequestParams requestParams) {// 1.构建BooleanQueryBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//判断关键字是否存在if (requestParams.getKey() == null || requestParams.getKey() == "") {boolQuery.must(QueryBuilders.matchAllQuery());} else {boolQuery.must(QueryBuilders.matchQuery("all", requestParams.getKey()));}//条件过滤//城市过滤if (requestParams.getCity() != null && requestParams.getCity().length() > 0) {boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));}//品牌过滤if (requestParams.getBrand() != null && requestParams.getBrand().length() > 0) {boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));}//星级过滤if (requestParams.getStarName() != null && requestParams.getStarName().length() > 0) {boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));}//价格过滤if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));}//添加算分函数控制权重FunctionScoreQueryBuilder functionScoreQueryBuilder =QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//其中一个function score元素new FunctionScoreQueryBuilder.FilterFunctionBuilder(//过滤条件QueryBuilders.termQuery("isAD", true),//算分函数ScoreFunctionBuilders.weightFactorFunction(10))});request.source().query(functionScoreQueryBuilder);}

