作者:Hai Xiang \
起源:https://www.cnblogs.com/haixi...
什么是elasticsearch
Elasticsearch 是一个开源的高度可扩大的全文搜寻和剖析引擎,领有查问近实时的超强性能。
赫赫有名的Lucene 搜索引擎被宽泛用于搜寻畛域,然而操作简单繁琐,总是让开发者敬而远之。而 Elasticsearch将 Lucene 作为其外围来实现所有索引和搜寻的性能,通过简略的 RESTful 语法来暗藏掉 Lucene 的复杂性,从而让全文搜寻变得简略
ES在Lucene根底上,提供了一些分布式的实现:集群,分片,复制等。
搜寻为什么不必MySQL而用es
咱们本文案例是一个迷你商品搜寻零碎,为什么不思考应用MySQL来实现搜寻性能呢?起因如下:
- MySQL默认应用innodb引擎,底层采纳b+树的形式来实现,而Es底层应用倒排索引的形式实现,应用倒排索引反对各种维度的分词,能够掌控不同粒度的搜寻需要。(MYSQL8版本也反对了全文检索,应用倒排索引实现,有趣味能够去看看两者的差异)
- 如果应用MySQL的
%key%
的含糊匹配来与es的搜寻进行比拟,在8万数据量时他们的耗时曾经达到40:1左右,毫无疑问在速度方面es完胜。
es在大厂中的利用状况
- es使用最宽泛的是elk组合来对日志进行搜寻剖析
- 58安全部门、京东订单核心简直全采纳es来实现相干信息的存储与检索
- es在tob的我的项目中也用于各种检索与剖析
- 在c端产品中,企业通常本人基于Lucene封装本人的搜寻零碎,为了适配公司营销策略、举荐零碎等会有更多定制化的搜寻需要
es客户端选型
spring-boot-starter-data-elasticsearch
我置信你看到的网上各类公开课视频或者小我的项目均举荐应用这款springboot整合过的es客户端,然而咱们要say no!
另外,ES 系列面试题和答案全副整顿好了,微信搜寻Java技术栈,在后盾发送:面试,能够在线浏览。
此图是引入的最新版本的依赖,咱们能够看到它所应用的es-high-client也为6.8.7,而es7.x版本都曾经更新很久了,这里许多新个性都无奈应用,所以版本滞后是他最大的问题。而且它的底层也是highclient,咱们操作highclient能够更灵便。我呆过的两个公司均未采纳此客户端。
elasticsearch-rest-high-level-client
这是官网举荐的客户端,反对最新的es,其实应用起来也很便当,因为是官网举荐所以在个性的操作上必定优于前者。而且该客户端与TransportClient不同,不存在并发瓶颈的问题,官网首推,必为精品!
搭建本人的迷你搜寻零碎
引入es相干依赖,除此之外需引入springboot-web依赖、jackson依赖以及lombok依赖等。
Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://www.javastack.cn/cate...
<properties> <es.version>7.3.2</es.version></properties><!-- high client--><dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>${es.version}</version> <exclusions> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </exclusion> </exclusions></dependency><dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${es.version}</version></dependency><!--rest low client high client以来低版本client所以须要引入--><dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>${es.version}</version></dependency>
es配置文件es-config.properties
es.host=localhostes.port=9200es.token=es-tokenes.charset=UTF-8es.scheme=httpes.client.connectTimeOut=5000es.client.socketTimeout=15000
封装RestHighLevelClient
@Configuration@PropertySource("classpath:es-config.properties")public class RestHighLevelClientConfig { @Value("${es.host}") private String host; @Value("${es.port}") private int port; @Value("${es.scheme}") private String scheme; @Value("${es.token}") private String token; @Value("${es.charset}") private String charSet; @Value("${es.client.connectTimeOut}") private int connectTimeOut; @Value("${es.client.socketTimeout}") private int socketTimeout; @Bean public RestClientBuilder restClientBuilder() { RestClientBuilder restClientBuilder = RestClient.builder( new HttpHost(host, port, scheme) ); Header[] defaultHeaders = new Header[]{ new BasicHeader("Accept", "*/*"), new BasicHeader("Charset", charSet), //设置token 是为了平安 网关能够验证token来决定是否发动申请 咱们这里只做象征性配置 new BasicHeader("E_TOKEN", token) }; restClientBuilder.setDefaultHeaders(defaultHeaders); restClientBuilder.setFailureListener(new RestClient.FailureListener(){ @Override public void onFailure(Node node) { System.out.println("监听某个es节点失败"); } }); restClientBuilder.setRequestConfigCallback(builder -> builder.setConnectTimeout(connectTimeOut).setSocketTimeout(socketTimeout)); return restClientBuilder; } @Bean public RestHighLevelClient restHighLevelClient(RestClientBuilder restClientBuilder) { return new RestHighLevelClient(restClientBuilder); }}
封装es罕用操作es搜寻零碎封装源码
@Servicepublic class RestHighLevelClientService { @Autowired private RestHighLevelClient client; @Autowired private ObjectMapper mapper; /** * 创立索引 * @param indexName * @param settings * @param mapping * @return * @throws IOException */ public CreateIndexResponse createIndex(String indexName, String settings, String mapping) throws IOException { CreateIndexRequest request = new CreateIndexRequest(indexName); if (null != settings && !"".equals(settings)) { request.settings(settings, XContentType.JSON); } if (null != mapping && !"".equals(mapping)) { request.mapping(mapping, XContentType.JSON); } return client.indices().create(request, RequestOptions.DEFAULT); } /** * 判断 index 是否存在 */ public boolean indexExists(String indexName) throws IOException { GetIndexRequest request = new GetIndexRequest(indexName); return client.indices().exists(request, RequestOptions.DEFAULT); } /** * 搜寻 */ public SearchResponse search(String field, String key, String rangeField, String from, String to,String termField, String termVal, String ... indexNames) throws IOException{ SearchRequest request = new SearchRequest(indexNames); SearchSourceBuilder builder = new SearchSourceBuilder(); BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); boolQueryBuilder.must(new MatchQueryBuilder(field, key)).must(new RangeQueryBuilder(rangeField).from(from).to(to)).must(new TermQueryBuilder(termField, termVal)); builder.query(boolQueryBuilder); request.source(builder); log.info("[搜寻语句为:{}]",request.source().toString()); return client.search(request, RequestOptions.DEFAULT); } /** * 批量导入 * @param indexName * @param isAutoId 应用主动id 还是应用传入对象的id * @param source * @return * @throws IOException */ public BulkResponse importAll(String indexName, boolean isAutoId, String source) throws IOException{ if (0 == source.length()){ //todo 抛出异样 导入数据为空 } BulkRequest request = new BulkRequest(); JsonNode jsonNode = mapper.readTree(source); if (jsonNode.isArray()) { for (JsonNode node : jsonNode) { if (isAutoId) { request.add(new IndexRequest(indexName).source(node.asText(), XContentType.JSON)); } else { request.add(new IndexRequest(indexName) .id(node.get("id").asText()) .source(node.asText(), XContentType.JSON)); } } } return client.bulk(request, RequestOptions.DEFAULT); }
创立索引,这里的settings是设置索引是否设置复制节点、设置分片个数,mappings就和数据库中的表构造一样,用来指定各个字段的类型,同时也能够设置字段是否分词(咱们这里应用ik中文分词器)、采纳什么分词形式。
@Testpublic void createIdx() throws IOException { String settings = "" + " {\n" + " \"number_of_shards\" : \"2\",\n" + " \"number_of_replicas\" : \"0\"\n" + " }"; String mappings = "" + "{\n" + " \"properties\": {\n" + " \"itemId\" : {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 64\n" + " },\n" + " \"urlId\" : {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 64\n" + " },\n" + " \"sellAddress\" : {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\", \n" + " \"search_analyzer\": \"ik_smart\",\n" + " \"fields\": {\n" + " \"keyword\" : {\"ignore_above\" : 256, \"type\" : \"keyword\"}\n" + " }\n" + " },\n" + " \"courierFee\" : {\n" + " \"type\": \"text\n" + " },\n" + " \"promotions\" : {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\", \n" + " \"search_analyzer\": \"ik_smart\",\n" + " \"fields\": {\n" + " \"keyword\" : {\"ignore_above\" : 256, \"type\" : \"keyword\"}\n" + " }\n" + " },\n" + " \"originalPrice\" : {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 64\n" + " },\n" + " \"startTime\" : {\n" + " \"type\": \"date\",\n" + " \"format\": \"yyyy-MM-dd HH:mm:ss\"\n" + " },\n" + " \"endTime\" : {\n" + " \"type\": \"date\",\n" + " \"format\": \"yyyy-MM-dd HH:mm:ss\"\n" + " },\n" + " \"title\" : {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\", \n" + " \"search_analyzer\": \"ik_smart\",\n" + " \"fields\": {\n" + " \"keyword\" : {\"ignore_above\" : 256, \"type\" : \"keyword\"}\n" + " }\n" + " },\n" + " \"serviceGuarantee\" : {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\", \n" + " \"search_analyzer\": \"ik_smart\",\n" + " \"fields\": {\n" + " \"keyword\" : {\"ignore_above\" : 256, \"type\" : \"keyword\"}\n" + " }\n" + " },\n" + " \"venue\" : {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\", \n" + " \"search_analyzer\": \"ik_smart\",\n" + " \"fields\": {\n" + " \"keyword\" : {\"ignore_above\" : 256, \"type\" : \"keyword\"}\n" + " }\n" + " },\n" + " \"currentPrice\" : {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 64\n" + " }\n" + " }\n" + "}"; clientService.createIndex("idx_item", settings, mappings);}
分词技巧:
- 索引时最小分词,搜寻时最大分词,例如"Java知音"索引时候词蕴含Java、知音、音、知等,最小粒度分词能够让咱们匹配更多的检索需要,然而咱们搜寻时应该设置最大分词,用“Java”和“知音”去匹配索引库,失去的后果更贴近咱们的目标,
- 对分词字段同时也设置keyword,便于后续排查谬误时能够准确匹配搜寻,疾速定位。
咱们向es导入十万条淘宝双11流动数据作为咱们的样本数据,数据结构如下所示
{ "_id": "https://detail.tmall.com/item.htm?id=538528948719\u0026skuId=3216546934499", "卖家地址": "上海", "快递费": "运费: 0.00元", "优惠活动": "满199减10,满299减30,满499减60,可跨店", "商品ID": "538528948719", "原价": "2290.00", "流动开始工夫": "2016-11-11 00:00:00", "流动完结工夫": "2016-11-11 23:59:59", "题目": "【天猫海内直营】 ReFa CARAT RAY 黎珐 双球滚轮波光美容仪", "服务保障": "副品保障;赠运费险;极速退款;七天退换", "会场": "进口尖货", "现价": "1950.00"}
调用下面封装的批量导入办法进行导入
@Testpublic void importAll() throws IOException { clientService.importAll("idx_item", true, itemService.getItemsJson());}
咱们调用封装的搜寻办法进行搜寻,搜寻产地为武汉、价格在11-149之间的相干酒产品,这与咱们淘宝中设置筛选条件搜寻商品操作统一。
@Testpublic void search() throws IOException { SearchResponse search = clientService.search("title", "酒", "currentPrice", "11", "149", "sellAddress", "武汉"); SearchHits hits = search.getHits(); SearchHit[] hits1 = hits.getHits(); for (SearchHit documentFields : hits1) { System.out.println( documentFields.getSourceAsString()); }}
咱们失去以下搜寻后果,其中_score为某一项的得分,商品就是依照它来排序。
{ "_index": "idx_item", "_type": "_doc", "_id": "Rw3G7HEBDGgXwwHKFPCb", "_score": 10.995819, "_source": { "itemId": "525033055044", "urlId": "https://detail.tmall.com/item.htm?id=525033055044&skuId=def", "sellAddress": "湖北武汉", "courierFee": "快递: 0.00", "promotions": "满199减10,满299减30,满499减60,可跨店", "originalPrice": "3768.00", "startTime": "2016-11-01 00:00:00", "endTime": "2016-11-11 23:59:59", "title": "酒嗨酒 西班牙原瓶原装进口红酒蒙德干红葡萄酒6只装整箱送酒具", "serviceGuarantee": "破损包退;副品保障;公益宝贝;不反对7天退换;极速退款", "venue": "食品主会场", "currentPrice": "151.00" }}
扩展性思考
- 商品搜寻权重扩大,咱们能够利用多种免费形式智能为不同店家提供减少权重,减少曝光度适应本身的营销策略。同时咱们常常发现淘宝搜寻前列的商品许多为咱们之前查看过的商品,这是通过记录用户行为,跑模型等形式智能为这些商品减少权重。
- 分词扩大,兴许因为某些商品的特殊性,咱们能够自定义扩大分词字典,更精准、人性化的搜寻。
- 高亮性能,es提供highlight高亮性能,咱们在淘宝上看到的商品展现中对搜寻关键字高亮,就是通过这种形式来实现。
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!