大型分布式的商城项目,例如京东,淘宝的关键字搜索框如何高效地检索数据?
如果使用数据库的模糊查询,像是like,缺点如下:
速度非常慢,因为LIKE是将数据从头到尾匹配,在大数据的情况下,匹配速度会非常久
需要匹配的数据库表、数据库数据众多
使用全文检索引擎:可以通过提前将数据库中要检索的数据,放入到全文搜索工具中,将所有数据按照一定的规则进行排序,再进搜索
优点:
速度快:按照一定的规则排序之后,无论用户搜索什么,我们都可以快速找到对应数据
可以实现搜索相似度高的数据排在前面
关键字的高亮,仔细观察我们的百度搜索、商城搜索,可以发现我们搜索的关键字是会有颜色标注的
只处理文本不处理语义:意思就是把相关搜索结果文章告诉你,而不是直接得到答案
全文检索引擎的优点:搜索速度快,可根据需求进行定制化配置
全文搜索领域,Lucene被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库
但是Lucene只是一个库,想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,而且Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的,所以我们需要使用Lucene的包装技术ElasticSearch
ES是一个分布式的全文搜索引擎,为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎
ES使用Java开发,并使用Lucene作为其核心来实现索引创建和索引搜索的功能,但是它的目的是通过简单的 RESTful API来替换掉Lucene的复杂性,从而让全文搜索变得简单
总的来说ElasticSearch简化了全文检索lucene的使用,同时增加了分布式的特性,使得构建大规模分布式全文检索变得非常容易 ,底层基于Luence,上手快。
添加一个文档:
PUT /shop/user/1
{
"id":1,
"name":"张三"
}
根据索引获取文档:
GET /shop/user/1
分布式架构:ES是一种基于Lucene的分布式全文搜索引擎,在数据存储和查询方面具有较强的并发处理能力和水平扩展能力。例如,在日志分析领域,使用ELK Stack(Elasticsearch、Logstash和Kibana)将日志收集、过滤、存储和可视化展现的整个流程,其中的Elasticsearch作为强大的搜索引擎支持海量日志的快速检索和分析。
实时搜索:ES支持实时查询,并且可以在毫秒级别内返回搜索结果,有效地支持监控应用程序、搜索引擎、电商应用、金融交易等需要快速反馈的场景。例如,在电商应用领域,使用ES来搜索商品可以实现实时展示用户感兴趣的商品信息,提高用户购物体验。
强大的搜索功能:ES拥有先进的搜索能力,可以支持全文检索、多字段匹配、模糊匹配、拼音转换、近似搜索、词项匹配等多种搜索方式,同时还支持聚合、分组、分页、排序等高级特性。例如,在社交网络应用中,可以使用ES来搜索用户、话题和帖子,实现高效的匹配和聚合。
易于使用:ES提供了简单易用的API和UI界面,支持多种编程语言的客户端,方便开发人员进行搜索和管理。同时ES还有许多插件和第三方工具,如Kibana、Beats等,能够进一步扩展ES的功能。例如,在安全分析领域,使用Elasticsearch Security插件可以扩展ES的用户认证和授权功能,提高系统的安全性。
大数据处理能力:ES支持海量数据的存储和处理,并且能够与Hadoop、Spark等大数据技术结合使用,处理大规模数据。例如,在广告推荐系统中,使用ES作为数据存储和查询的引擎,可以处理千万级别的用户和广告数据,从而实现更准确的推荐服务。
开源免费:ES是一款开源软件,采用Apache 2.0许可证,可以免费使用和修改,对于独立开发者和小型企业来说成本较低。例如,国内知名的拼音搜索引擎Pinyinjoe就采用了Elasticsearch,提供了开放的搜索接口。
综上所述,ES具有分布式、实时、强大的搜索功能、易于使用、大数据处理能力和开源免费等多种优点,成为了企业级全文搜索引擎的首选方案,被广泛应用于多个领域。
# 创建索引,可以先删除索引,再重新添加文档类型映射
DELETE shop
PUT /shop
PUT /shop/_doc/_mapping
{
"_doc":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"age":{
"type": "integer"
},
"sex":{
"type": "integer"
}
}
}
}
GET /shop/_doc/_search
GET /shop/_doc/_search
{
"query":{
"bool":{
"must": [{
"match": {
"name":"我在成都"
}
}
],
"filter": [{
"term": {
"sex": {
"value": "1"
}
}
},{
"range": {
"age": {
"gte": 1,
"lte": 12
}
}
}
]
}
},
"from": 2,
"size":2,
"sort": [
{
"age": "desc"
}
],
"_source": ["name","age","sex"]
}
package com.alibaba.es;
import com.alibaba.utils.ESClientUtil;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class EsTest {
/*
* ES的crud
* */
@Test
public void testAdd() {
TransportClient client = ESClientUtil.getClient();// 获取连接,这里的client就是java连接ES的客户端
// 添加数据
// client.prepareIndex("索引名","类型名","id").setSource("json格式的数据").get();
IndexRequestBuilder indexRequestBuilder = client.prepareIndex("shop", "_doc");
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "李思思");
map.put("age", 16);
map.put("sex", 0);
IndexResponse indexResponse = indexRequestBuilder.setSource(map).get();
System.out.println(indexResponse);
}
@Test
public void testGet() {
TransportClient client = ESClientUtil.getClient();// 获取连接,这里的client就是java连接ES的客户端
// 查询数据
// client.prepareGet("索引名","类型名","id").get();
// client.prepareGet("索引名","类型名","id").setOperationThreaded(false).get();
GetResponse documentFields = client.prepareGet("shop", "_doc", "9g4_vogBBfk9FP3Z9OLv").get();
System.out.println(documentFields.getSourceAsString());
}
@Test
public void testUpdate() {
TransportClient client = ESClientUtil.getClient();// 获取连接,这里的client就是java连接ES的客户端
// 修改数据
// client.prepareUpdate("索引名","类型名","id").setDoc("json格式的数据").get();
IndexRequestBuilder indexRequestBuilder = client.prepareIndex("shop", "_doc", "9g4_vogBBfk9FP3Z9OLv");
Map<String, Object> map = new HashMap<>();
map.put("name", "李二麻子");
IndexResponse indexResponse = indexRequestBuilder.setSource(map).get();
System.out.println(indexResponse);
}
@Test
public void testDelete() {
TransportClient client = ESClientUtil.getClient();// 获取连接,这里的client就是java连接ES的客户端
// 删除数据
// client.prepareDelete("索引名","类型名","id").get();
DeleteResponse deleteResponse = client.prepareDelete("shop", "_doc", "9g4_vogBBfk9FP3Z9OLv").get();
System.out.println(deleteResponse);
}
/*
复杂查询
条件
- 查询_doc表
- name包含:我在成都
- age在1~12之间
- sex=1
- 每页大小2
- 从2页开始查
- 按照age倒序
*/
@Test
public void testQuery() {
TransportClient client = ESClientUtil.getClient();// 获取连接,这里的client就是java连接ES的客户端
// 复杂查询
// client.prepareSearch("索引名").setTypes("类型名").setQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("字段名","字段值"))).get();
SearchRequestBuilder searchRequestBuilder = client.prepareSearch("shop").setTypes("_doc"); // 查询_doc表
// 查询条件
// 查询_doc表
// - name包含:我在成都
// - age在1~12之间
// - sex=1
// - 每页大小2
// - 从2页开始查
// 按照age倒序
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 分页查询
searchRequestBuilder.setFrom(2).setSize(2);
// 排序, 按照age倒序
searchRequestBuilder.addSort("age", SortOrder.DESC);
// name包含:我在成都
boolQueryBuilder.must(QueryBuilders.matchQuery("name", "我在成都"));
// sex = 1, age在1~12之间
boolQueryBuilder.filter(QueryBuilders.termQuery("sex", 1)).filter(QueryBuilders.rangeQuery("age").gte(1).lte(12));
// 查询
SearchRequestBuilder searchRequestBuilder1 = searchRequestBuilder.setQuery(boolQueryBuilder);
// 将结果转换为map输出
Stream<SearchHit> stream = Arrays.stream(searchRequestBuilder1.get().getHits().getHits());
// 流转换为数组
SearchHit[] searchHits = stream.toArray(SearchHit[]::new);
System.out.println(Arrays.toString(searchHits));
// System.out.println(searchRequestBuilder1.get().getHits().getHits());
}
@Test
public void testGenData(){
TransportClient client = ESClientUtil.getClient();
// for循环生成数据
IndexRequestBuilder indexRequestBuilder = client.prepareIndex("shop", "_doc");
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
map.put("id", i);
map.put("name", i%2==0?"李思思"+i:"我在成都");
map.put("age", i);
map.put("sex", i%2==0?0:1);
indexRequestBuilder.setSource(map).get();
// 清空map数据
map.clear();
}
}
}
{
"took" : 20,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 6,
"max_score" : null,
"hits" : [
{
"_index" : "shop",
"_type" : "_doc",
"_id" : "_g5KvogBBfk9FP3Zo-JB",
"_score" : null,
"_source" : {
"sex" : 1,
"name" : "我在成都",
"age" : 7
},
"sort" : [
7
]
},
{
"_index" : "shop",
"_type" : "_doc",
"_id" : "_A5KvogBBfk9FP3Zo-I2",
"_score" : null,
"_source" : {
"sex" : 1,
"name" : "我在成都",
"age" : 5
},
"sort" : [
5
]
}
]
}
}
这个信息是Elasticsearch查询操作的返回结果,主要包括以下几个部分:
took:该查询操作耗时,单位为毫秒。
timed_out:查询操作是否超时。
_shards:查询操作在集群中涉及到的分片信息,包括:
a) total:涉及到的总分片数。
b) successful:查询成功的分片数。
c) skipped:跳过的分片数。
d) failed:查询失败的分片数。
hits:查询结果,包括:
a) total:符合查询条件的文档数量。
b) max_score:最高得分。
c) hits:命中的文档详细信息,包括该文档所在的索引、类型、ID等信息。
- _index:文档所在的索引名称。
- _type:文档类型。
- _id:文档ID。
- _score:文档得分。
- _source:文档内容。
- sort:排序方式和依据。
例如,上述结果中返回的6个命中文档,其中两个文档的相关信息包含有 “sex”、“name”、“age”三个字段。同时使用对“age”字段进行排序,第一个文档的“age”字段值为7,第二个文档的“age”字段值为5。
Relational DB | ElasticSearch |
---|---|
数据库 | 索引-Index |
表 | 类型-Type |
行 | 文档-Document |
字段 | 字段-fieId |
SQL语句 | DSL语句 |
主键ID | 文档ID |