SpringBoot整合Elasticsearch

     背景

        近期我们团队接到这样一个需求,用户通过附件上传,附件解析,将word文档上传到服务器,且把word里的内容存储到了数据库里。用户希望根据文档内容里的某些字、词来进行搜索,确定哪些文档里包含这些内容。

        针对这个搜索的需求,我当时考虑的方案有两个:

                1、全文索引,

                2、Elasticsearch(ES),

        两者都可以实现内容的全文检索,不过全文索引是关系型数据库中常用的。而ES主要用于做搜索引擎。综合考虑数据的及时性,服务的可扩展性,搜索的高效性后,我决定采用ES。因为相比于关系型数据库的索引,ES的性能更好,易扩展,数据入库后即可进行搜索,而全文索引则需要在数据入库后对索引进行一次重建,才能查询新入库的数据。

     Elasticsearch介绍

        Elasticsearch是一个开源的分布式全文搜索和分析引擎,基于Apache Lucene项目构建。它提供了一个分布式的、多租户的全文搜索引擎,可以快速地进行数据检索和分析。

        Elasticsearch可以帮助用户处理大量的结构化和非结构化数据,支持实时搜索、分析和可视化等功能。它可以处理大数据量,支持分布式架构,可以横向扩展以满足不同的需求。Elasticsearch还提供了丰富的API,可以与其他应用程序进行集成,支持多种编程语言。

        Elasticsearch的核心功能包括:

         1)分布式搜索和分析:支持实时搜索和分析大量的数据。

         2)多租户支持:可以为不同的用户或应用程序提供独立的搜索和分析功能。

         3)高可用性和容错性:支持自动故障转移和自动恢复功能,确保系统的高可用性和容错性。

         4)数据安全:支持数据加密、身份验证和访问控制等功能,确保用户数据的安全性和隐私性。

         5)可扩展性:支持分布式架构,可以根据需要添加新的节点和服务器,以满足不同的需求。

        除了以上核心功能,Elasticsearch还提供了一些高级功能,如聚合分析、自动完成、拼写纠正、近义词搜索、地理位置搜索等。

        总之,Elasticsearch是一个功能强大的全文搜索和分析引擎,可以帮助用户处理大量的结构化和非结构化数据,支持实时搜索、分析和可视化等功能。它是一个开源的项目,拥有活跃的社区和丰富的文档,可以满足不同用户的需求。

        ES与关系型数据库之间的名词对应关系如下

     Elasticsearch实践

      1、ES的Linux安装

        1、下载Elasticsearch安装包,下载路径如下:ES发装包链接

        2、下载到本地之后,上传到linux服务器的文件夹下,通过以下命令解压:tar -zxvf elasticsearch-7.8.1-linux-x86_64.tar.gz

        3、创建用户并授权。(我的用户是esuser)

             useradd esuser

             passwd esuser  ****(这里是密码)

             chown -R esuser:esuser /home/esuser/elasticsearch7.8  

        4、更改解压的文件夹下elasticsearch.yml里的配置:

                cluster.name: elasticsearch       #集群名字

                node.name: es-leixi-01        #节点名称

                path.data: /home/esuser/elasticsearch7.8/data     #数据存放位置

                path.logs: /home/esuser/elasticsearch7.8/logs      #日志存放位置

                http.port: 19200            #端口号

                network.host: 0.0.0.0

                cluster.initial_master_nodes: ["es-leixi-01"]      #当前集群包含的节点

          5、把linux里的端口号19200打开,这样就可以在其他服务访问linux的es服务了(不同的linux服务器该命令会有所不同,我的是RadHat)

                firewall-cmd --zone=public --add-port=19200/tcp --permanent

                firewall-cmd --reload

        6、修改sysctl.conf文件,

                vim /etc/sysctl.conf

                vm.max_map_count = 262145

        如果不修改会报以下错误:

        7、进入es服务器路径下/bin, 执行命令./elasticsearch 即可启动es

        8、启动es之后,退出linux黑窗口,发现es服务也停了,这时候需要用以下命令,即使关了黑窗口,es也可以正常运行。

                nohup ./elasticsearch >/home/esuser/es.log 2>&1 &

       

     2、浏览器实现ES数据的操作

        推荐一个插件:elasticsearch-head。它是一个开源的Elasticsearch集群管理工具。它提供了一个基于Web的用户界面,可以方便地管理Elasticsearch集群,包括索引、文档、节点和分片等。把它安装在浏览器上,就可以通过浏览器对es进行增删改查了。安装方式和操作方法见:elasticsearch-head浏览器插件安装使用

        我在初次使用Elasticsearch-head进行查询时,报406 elasticsearch-head "error": "Content-Type header [application/x-www-form-urlencoded] is not supported", , 使用的是以下方式解决:

        1、进入head安装目录;

        2、打开文件夹\elasticsearch-head\

        3、编辑vendor.js 共有两处

      ①. 6886行 contentType: "application/x-www-form-urlencoded 改成

         contentType: "application/json;charset=UTF-8"

      ②. 7574行 var inspectData = s.contentType === "application/x-www-form-urlencoded" && 改成

       var inspectData = s.contentType === "application/json;charset=UTF-8" &&

        以下是我用elasticsearch-head的一些实践示例:

        PUT 索引名 新增索引

        DELETE 索引名  删除索引

        GET 索引名/_mapping 查看索引的结构

        GET 索引名/_search 查询索引信息

        GET 索引名/settings 查看索引配置

        PUT 索引名/_mapping 添加字段

        POST 索引名/_doc/主键 添加数据

        POST 索引名/_update/主键 修改数据

        DELETE 索引名/_doc/主键 删除数据

        POST 索引名/_search 查询索引信息     --注意,如果查询索引时使用查询条件,则需要用POST的形式

      3、Springboot实现ES数据的操作

        接下来,咱们尝试使用Springboot来对Elasticsearch进行一些基础的操作。这个实践我照抄 参考了以下博客:从零搭建springcloud项目- elasticsearch(7)_springcloud nacos配置 resthighlevelclient 多个节点-CSDN博客文章浏览阅读1.1k次。springcloud集成elasticsearch_springcloud nacos配置 resthighlevelclient 多个节点https://blog.csdn.net/qq_29673919/article/details/122859463

        雷袭在这里感谢大佬指路了。

        1、添加maven依赖

       <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.elasticsearch</groupId>
                    <artifactId>elasticsearch</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.elasticsearch.client</groupId>
                    <artifactId>elasticsearch-rest-client</artifactId>
                </exclusion>
            </exclusions>
            <version>7.1.1</version>
        </dependency>

        2、application.yml配置

elasticSearch:
  # es集群地址
  host: 10.0.6.183
  port: 19200
  username:
  password:

        3、config文件配置

@Configuration
@Primary
public class EsConfig extends AbstractFactoryBean<Object> {
    private static final Logger LOG = LoggerFactory.getLogger(EsConfig.class);

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Value("${elasticsearch.username}")
    private String username;

    @Value("${elasticsearch.password}")
    private String password;

    private RestHighLevelClient restHighLevelClient;

    @Override
    public void destroy() throws Exception {
        // 关闭Client
        if (restHighLevelClient != null) {
            restHighLevelClient.close();
        }
    }

    @Override
    public Class<RestHighLevelClient> getObjectType() {
        return RestHighLevelClient.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    @Override
    protected Object createInstance() throws Exception {
        try {
            CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
            // 如果有多个节点,构建多个HttpHost
            RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, "http"))
                    .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                        @Override
                        public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                        }
                    });
            restHighLevelClient = new RestHighLevelClient(builder);
        } catch (Exception e) {
            LOG.error(e.getMessage());
        }
        return restHighLevelClient;
    }
}

        4、编写操作es的工具类,测试的Entity

// 服务层
/**
 *
 * @author leixiyueqi
 * @since 2023/12/28 22:00
 */
@Service
public class ElasticService {
    @Autowired
    private RestHighLevelClient client;
    
    /**
     * 功能描述: 创建索引
     *
     * @param index 索引名称
     * @param source 配置
     * @author leixi
     * @return boolean
     */
    public boolean createIndex(String index, String source) {
        try {
            CreateIndexRequest request = new CreateIndexRequest(index);
            request.mapping(source, XContentType.JSON);
            CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
            return createIndexResponse.isAcknowledged();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean deleteIndex(String index) {
        try {
            DeleteIndexRequest request = new DeleteIndexRequest(index);
            AcknowledgedResponse response= client.indices().delete(request, RequestOptions.DEFAULT);
            return response.isAcknowledged();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 功能描述: 判断索引是否存在
     *
     * @param index 索引名称
     * @author leixi
     * @return boolean
     */
    public boolean indexExist(String index) {
        try {
            GetIndexRequest request = new GetIndexRequest(index);
            request.local(false);
            request.humanReadable(true);
            request.includeDefaults(false);
            return client.indices().exists(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 功能描述: 插入数据
     *
     * @param index 索引名称
     * @param entity 数据类
     * @author leixi
     * @return boolean
     */
    public boolean insertOne(String index, ElasticEntity entity) {
        IndexRequest indexRequest = new IndexRequest(index);
        String userJson = JSONObject.toJSONString(entity.getData());
        indexRequest.source(userJson, XContentType.JSON).id(entity.get_id());
        try {
            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
            if (indexResponse != null) {
                if (indexResponse.getResult() == DocWriteResponse.Result.CREATED || indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 功能描述: 查询数据
     *
     * @param indexArray 索引数组,用于多个索引一起查询
     * @param searchSourceBuilder 查询builder
     * @param c 参数类型
     * @author leixi
     * @return * @return: null
     * @throws Exception 异常
     */
    public <T> List<T> listByBuilder(String[] indexArray, SearchSourceBuilder searchSourceBuilder, Class<T> c) {
        SearchRequest request = new SearchRequest(indexArray);
        searchSourceBuilder.trackTotalHits(true);
        request.source(searchSourceBuilder);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = response.getHits().getHits();
            List<T> res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                String json = JSON.toJSONString(sourceAsMap);
                res.add(JSON.parseObject(json, c));
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

//实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ElasticEntity<T> {
    private String id;
    private String _id;
    private T data;
}

//测试类
/**
 *
 * @author leixiyueqi
 * @since 2023/12/28 22:00
 */
@Data
public class EsTestEntity {
    private long id;
    private String age;
    private String name;
    private String sex;
    private Integer height;
}

        5、添加controller

@RestController
public class EsController {
    @Autowired
    private ElasticService esService;
    
    @GetMapping("/createIndex")
    public Object createIndex() {
        String source = "{\"properties\":{\"sex\":{\"type\":\"text\"},\"name\":{\"type\":\"text\"},\"id\":{\"type\":\"long\"},\"age\":{\"type\":\"text\"},\"height\":{\"type\":\"integer\"}}}";
        JSONObject json = JSONObject.parseObject(source);
        String str = JSONObject.toJSONString(json);
        return esService.createIndex("es_test_2023", str);
    }

    @PostMapping("/insertDataIntoEs")
    public Object insertDataIntoEs(@RequestBody EsTestEntity entity) {
        ElasticEntity po = new ElasticEntity();
        po.setData(entity);
        return esService.insertOne("es_test_2023", po);
    }


    @PostMapping("/queryEsData")
    public Object queryEsData() {
        // 使用分词,查询name中含有张三的
        QueryBuilder nameQuery = QueryBuilders.matchQuery("name", "张三");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(nameQuery);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.explain(true);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        sourceBuilder.query(boolQueryBuilder);
        String[] indexArray = new String[]{"es_test_2023"};
        return esService.listByBuilder(indexArray, sourceBuilder, EsTestEntity.class);
    }
}

        6、通过Postman测分别进行创建索引,添加数据,查询数据的测试,结果如下:

        最后,再次感觉在我实践过程中给予我指点和帮助的大佬们,码文不易,希望这篇文章可以帮助到后来者们。

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值