zoukankan      html  css  js  c++  java
  • (九)整合 ElasticSearch框架,实现高性能搜索引擎

    ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。

    1、SpringBoot整合ElasticSearch

    1.1 核心依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        <version>${spring-boot.version}</version>
    </dependency>
    

    1.2 配置文件

    spring:
      application:
        name: ware-elastic-search
      data:
        elasticsearch:
          # 默认 elasticsearch
          cluster-name: elasticsearch
          # 9200作为Http协议,主要用于外部通讯
          # 9300作为Tcp协议,jar之间就是通过tcp协议通讯
          cluster-nodes: 192.168.72.130:9300
    

    1.3 实体类配置

    Document 配置

    加上了@Document注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词。
    indexName索引名称 理解为数据库名 限定小写
    type 理解为数据库的表名称
    shards = 5 默认分区数
    replicas = 1 每个分区默认的备份数
    refreshInterval = "1s" 刷新间隔
    indexStoreType = "fs" 索引文件存储类型
    

    源码配置

    @Document(indexName = "requestlogindex",type = "requestlog")
    public class RequestLog {
        //Id注解Elasticsearch里相应于该列就是主键,查询时可以使用主键查询
        @Id
        private Long id;
        private String orderNo;
        private String userId;
        private String userName;
        private String createTime;
    }
    

    1.4 数据交互层

    实现ElasticsearchRepository接口。

    public interface RequestLogRepository 
    extends ElasticsearchRepository<RequestLog,Long> {
    }
    

    1.5 演示案例

    数据增加,修改,查询,排序,多条件查询。

    @Service
    public class RequestLogServiceImpl implements RequestLogService {
        @Resource
        private RequestLogRepository requestLogRepository ;
        @Override
        public String esInsert(Integer num) {
            for (int i = 0 ; i < num ; i++){
                RequestLog requestLog = new RequestLog() ;
                requestLog.setId(System.currentTimeMillis());
                requestLog.setOrderNo(DateUtil.formatDate(new Date(),DateUtil.DATE_FORMAT_02)+System.currentTimeMillis());
                requestLog.setUserId("userId"+i);
                requestLog.setUserName("张三"+i);
                requestLog.setCreateTime(DateUtil.formatDate(new Date(),DateUtil.DATE_FORMAT_01));
                requestLogRepository.save(requestLog) ;
            }
            return "success" ;
        }
        @Override
        public Iterable<RequestLog> esFindAll (){
            return requestLogRepository.findAll() ;
        }
        @Override
        public String esUpdateById(RequestLog requestLog) {
            requestLogRepository.save(requestLog);
            return "success" ;
        }
        @Override
        public Optional<RequestLog> esSelectById(Long id) {
            return requestLogRepository.findById(id) ;
        }
        @Override
        public Iterable<RequestLog> esFindOrder() {
            // 用户名倒序
            // Sort sort = new Sort(Sort.Direction.DESC,"userName.keyword") ;
            // 创建时间正序
            Sort sort = new Sort(Sort.Direction.ASC,"createTime.keyword") ;
            return requestLogRepository.findAll(sort) ;
        }
        @Override
        public Iterable<RequestLog> esFindOrders() {
            List<Sort.Order> sortList = new ArrayList<>() ;
            Sort.Order sort1 = new Sort.Order(Sort.Direction.ASC,"createTime.keyword") ;
            Sort.Order sort2 = new Sort.Order(Sort.Direction.DESC,"userName.keyword") ;
            sortList.add(sort1) ;
            sortList.add(sort2) ;
            Sort orders = Sort.by(sortList) ;
            return requestLogRepository.findAll(orders) ;
        }
        @Override
        public Iterable<RequestLog> search() {
            // 全文搜索关键字
            /*
            String queryString="张三";
            QueryStringQueryBuilder builder = new QueryStringQueryBuilder(queryString);
            requestLogRepository.search(builder) ;
            */
            /*
             * 多条件查询
             */
             QueryBuilder builder = QueryBuilders.boolQuery()
                    // .must(QueryBuilders.matchQuery("userName.keyword", "历张")) 搜索不到
                   .must(QueryBuilders.matchQuery("userName", "张三")) // 可以搜索
                   .must(QueryBuilders.matchQuery("orderNo", "20190613736278243"));
            return requestLogRepository.search(builder) ;
        }
    }
    

    2、Spring Boot 集成 Elasticsearch 实战

    原文作者:武培轩
    来源:微信公众号
    完整代码Github仓库

    Spring Boot 集成 ES 主要分为以下三步:

    1. 加入 ES 依赖
    2. 配置 ES
    3. 演示 ES 基本操作

    2.1 加入依赖

    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>7.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.1.0</version>
    </dependency>
    

    2.2 创建ES配置

    在配置文件 application.properties 中配置 ES 的相关参数,具体内容如下:

    elasticsearch.host=localhost
    elasticsearch.port=9200
    elasticsearch.connTimeout=3000
    elasticsearch.socketTimeout=5000
    elasticsearch.connectionRequestTimeout=500
    

    其中指定了 ES 的 host 和端口以及超时时间的设置,另外我们的 ES 没有添加任何的安全认证,因此 username 和 password 就没有设置。

    然后在 config 包下创建 ElasticsearchConfiguration 类,会从配置文件中读取到对应的参数,接着申明一个 initRestClient 方法,返回的是一个 RestHighLevelClient,同时为它添加 @Bean(destroyMethod = “close”) 注解,当 destroy 的时候做一个关闭,这个方法主要是如何初始化并创建一个 RestHighLevelClient。

    @Configuration
    public class ElasticsearchConfiguration {
    
        @Value("${elasticsearch.host}")
        private String host;
    
        @Value("${elasticsearch.port}")
        private int port;
    
        @Value("${elasticsearch.connTimeout}")
        private int connTimeout;
    
        @Value("${elasticsearch.socketTimeout}")
        private int socketTimeout;
    
        @Value("${elasticsearch.connectionRequestTimeout}")
        private int connectionRequestTimeout;
    
        @Bean(destroyMethod = "close", name = "client")
        public RestHighLevelClient initRestClient() {
            RestClientBuilder builder = RestClient.builder(new HttpHost(host, port))
                    .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder
                            .setConnectTimeout(connTimeout)
                            .setSocketTimeout(socketTimeout)
                            .setConnectionRequestTimeout(connectionRequestTimeout));
            return new RestHighLevelClient(builder);
        }
    }
    

    2.3 定义文档实体类

    首先在 constant 包下定义常量接口,在接口中定义索引的名字为 user:

    public interface Constant {
        String INDEX = "user";
    }
    

    然后在 document 包下创建一个文档实体类:

    public class UserDocument {
        private String id;
        private String name;
        private String sex;
        private Integer age;
        private String city;
        // 省略 getter/setter
    }
    

    2.4 ES基本操作

    在这里主要介绍 ES 的索引、文档、搜索相关的简单操作,在 service 包下创建 UserService 类。

    索引操作
    在这里演示创建索引和删除索引:

    2.4.1 创建索引

    在创建索引的时候可以在 CreateIndexRequest 中设置索引名称、分片数、副本数以及 mappings,在这里索引名称为 user,分片数 number_of_shards 为 1,副本数 number_of_replicas 为 0,具体代码如下所示:

    public boolean createUserIndex(String index) throws IOException {
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(index);
        createIndexRequest.settings(Settings.builder()
                .put("index.number_of_shards", 1)
                .put("index.number_of_replicas", 0)
        );
        createIndexRequest.mapping("{
    " +
                "  "properties": {
    " +
                "    "city": {
    " +
                "      "type": "keyword"
    " +
                "    },
    " +
                "    "sex": {
    " +
                "      "type": "keyword"
    " +
                "    },
    " +
                "    "name": {
    " +
                "      "type": "keyword"
    " +
                "    },
    " +
                "    "id": {
    " +
                "      "type": "keyword"
    " +
                "    },
    " +
                "    "age": {
    " +
                "      "type": "integer"
    " +
                "    }
    " +
                "  }
    " +
                "}", XContentType.JSON);
        CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        return createIndexResponse.isAcknowledged();
    }
    

    通过调用该方法,就可以创建一个索引 user,索引信息如下:

    2.4.2 删除索引

    在 DeleteIndexRequest 中传入索引名称就可以删除索引,具体代码如下所示:

    public Boolean deleteUserIndex(String index) throws IOException {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
        AcknowledgedResponse deleteIndexResponse = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
        return deleteIndexResponse.isAcknowledged();
    }
    

    2.5 文档操作

    2.5.1 创建文档

    创建文档的时候需要在 IndexRequest 中指定索引名称,id 如果不传的话会由 ES 自动生成,然后传入 source,具体代码如下:

    public Boolean createUserDocument(UserDocument document) throws Exception {
        UUID uuid = UUID.randomUUID();
        document.setId(uuid.toString());
        IndexRequest indexRequest = new IndexRequest(Constant.INDEX)
                .id(document.getId())
                .source(JSON.toJSONString(document), XContentType.JSON);
        IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
        return indexResponse.status().equals(RestStatus.OK);
    }
    

    下面通过调用这个方法,创建两个文档,具体内容如下:

    2.5.2 批量创建文档

    在一个 REST 请求中,重新建立网络开销是十分损耗性能的,因此 ES 提供 Bulk API,支持在一次 API 调用中,对不同的索引进行操作,从而减少网络传输开销,提升写入速率。

    下面方法是批量创建文档,一个 BulkRequest 里可以添加多个 Request,具体代码如下:

    public Boolean bulkCreateUserDocument(List<UserDocument> documents) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        for (UserDocument document : documents) {
            String id = UUID.randomUUID().toString();
            document.setId(id);
            IndexRequest indexRequest = new IndexRequest(Constant.INDEX)
                    .id(id)
                    .source(JSON.toJSONString(document), XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        return bulkResponse.status().equals(RestStatus.OK);
    }
    

    下面通过该方法创建些文档,便于下面的搜索演示。

    2.5.3 查看文档

    查看文档需要在 GetRequest 中传入索引名称和文档 id,具体代码如下所示:

    public UserDocument getUserDocument(String id) throws IOException {
        GetRequest getRequest = new GetRequest(Constant.INDEX, id);
        GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
        UserDocument result = new UserDocument();
        if (getResponse.isExists()) {
            String sourceAsString = getResponse.getSourceAsString();
            result = JSON.parseObject(sourceAsString, UserDocument.class);
        } else {
            logger.error("没有找到该 id 的文档");
        }
        return result;
    }
    

    下面传入文档 id 调用该方法,结果如下所示:

    2.5.4 更新文档

    更新文档则是先给 UpdateRequest 传入索引名称和文档 id,然后通过传入新的 doc 来进行更新,具体代码如下:

    public Boolean updateUserDocument(UserDocument document) throws Exception {
        UserDocument resultDocument = getUserDocument(document.getId());
        UpdateRequest updateRequest = new UpdateRequest(Constant.INDEX, resultDocument.getId());
        updateRequest.doc(JSON.toJSONString(document), XContentType.JSON);
        UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
        return updateResponse.status().equals(RestStatus.OK);
    }
    

    下面将文档 id 为 9b8d9897-3352-4ef3-9636-afc6fce43b20 的文档的城市信息改为 handan,调用方法结果如下:

    2.5.5 除文档

    删除文档只需要在 DeleteRequest 中传入索引名称和文档 id,然后执行 delete 方法就可以完成文档的删除,具体代码如下:

    public String deleteUserDocument(String id) throws Exception {
        DeleteRequest deleteRequest = new DeleteRequest(Constant.INDEX, id);
        DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
        return response.getResult().name();
    }
    

    介绍完文档的基本操作,接下来对搜索进行简单介绍:

    2.5.6 搜索操作

    简单的搜索操作需要在 SearchRequest 中设置将要搜索的索引名称(可以设置多个索引名称),然后通过 SearchSourceBuilder 构造搜索源,下面将 TermQueryBuilder 搜索查询传给 searchSourceBuilder,最后将 searchRequest 的搜索源设置为 searchSourceBuilder,执行 search 方法实现通过城市进行搜索,具体代码如下所示:

    public List<UserDocument> searchUserByCity(String city) throws Exception {
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(Constant.INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("city", city);
        searchSourceBuilder.query(termQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getSearchResult(searchResponse);
    }
    

    该方法的执行结果如图所示:

    2.5.7 聚合搜索

    聚合搜索就是给 searchSourceBuilder 添加聚合搜索,下面方法是通过 TermsAggregationBuilder 构造一个先通过城市就行分类聚合,其中还包括一个子聚合,是对年龄求平均值,然后在获取聚合结果的时候,可以使用通过在构建聚合时的聚合名称获取到聚合结果,具体代码如下所示:

    public List<UserCityDTO> aggregationsSearchUser() throws Exception {
        SearchRequest searchRequest = new SearchRequest(Constant.INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_city")
                .field("city")
                .subAggregation(AggregationBuilders
                        .avg("average_age")
                        .field("age"));
        searchSourceBuilder.aggregation(aggregation);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        Aggregations aggregations = searchResponse.getAggregations();
        Terms byCityAggregation = aggregations.get("by_city");
        List<UserCityDTO> userCityList = new ArrayList<>();
        for (Terms.Bucket buck : byCityAggregation.getBuckets()) {
            UserCityDTO userCityDTO = new UserCityDTO();
            userCityDTO.setCity(buck.getKeyAsString());
            userCityDTO.setCount(buck.getDocCount());
            // 获取子聚合
            Avg averageBalance = buck.getAggregations().get("average_age");
            userCityDTO.setAvgAge(averageBalance.getValue());
            userCityList.add(userCityDTO);
        }
        return userCityList;
    }
    

    下面是执行该方法的结果:

  • 相关阅读:
    并查集基本操作及其优化
    POJ-3159.Candies.(差分约束 + Spfa)
    差分约束和最短路径(算法导论)
    POJ-3660.Cow Contest(有向图的传递闭包)
    Floyd-Warshall算法计算有向图的传递闭包
    深入理解链式前向星
    POJ-1860.CurrencyExchange(Spfa判断负环模版题)
    HDU-4725.TheShortestPathinNyaGraph(最短路 + 建图)
    POJ-3268.SilverCowParty.(最短路 + 图的转置)
    POJ-1797.HeavyTransportation(最长路中的最小权值)
  • 原文地址:https://www.cnblogs.com/aixing/p/13327097.html
Copyright © 2011-2022 走看看