ElasticSearch

1、什么是ElasticSearch?


1.1、概念

Elasticsearch (ES)是一个基于Lucene 开源的高扩展的分布式全文检索引擎,能够安全可靠地获取任何来源、任何格式的数据,然后实时的对数据进行检索、分析和可视化,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据,它不但稳定、可靠、快速,而且也具有良好的水平扩展能力,是专门为分布式环境设计的,它的目的是通过简单的RESTFUL API来隐藏Lucene的复杂性,从而使全文搜索变得简单

1.2、了解ELK

ELK是ElasticsearchLogstash Kibana三大开源框架首字母大写简称。 市面上也被成为Elastic Stack。其中ES是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用ES作为底层支持框架,由此可见ES提供的搜索能力确实非常强大。Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。Kibana可以将ES的数据通过友好的页面展示出来,提供实时分析的功能

市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。

1.3、ES应用案例

  • GitHub: 2013 年初,抛弃了 Solr,采取 Elasticsearch 来做 PB 级的搜索。“GitHub 使用 Elasticsearch 搜索 20TB 的数据,包括 13 亿文件和 1300 亿行代码”
  • 维基百科:启动以 Elasticsearch 为基础的核心搜索架构
  • 百度:目前广泛使用 Elasticsearch 作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部 20 多个业务线(包括云分析、网盟、预测、文库、 直达号、钱包、风控等),单集群最大100 台机器,200 个ES节点,每天导入 30TB+ 数据
  • SoundCloud:“SoundCloud”使用Elasticsearch为 1.8亿用户提供即时而精准的音乐搜索服务
  • 新浪:使用 Elasticsearch 分析处理 32 亿条实时日志
  • 阿里:使用 Elasticsearch 构建日志采集和分析体系
  • Stack Overflow:解决 Bug 问题的网站,全英文,编程人员交流的网站

2、ElasticSearch安装


2.1、安装ElasticSearch

下载

官网

下载地址

其他版本

window安装

  1. 解压即可

  2. 了解目录

    • bin:启动文件
    • config:配置文件
    • jdk:内置 JDK目录
    • lib:相关jar包
    • logs:日志
    • modules:功能模块
    • plugins:插件
  3. 启动

    打开bin目录,双击elasticsearch.bat启动

  4. 访问测试,9200端口,注:9300端口为ES集群间组件的通信端口

安装Es可视化界面 head插件

注:前提需要有node.js环境

  1. 下载地址

  2. 启动

    cnpm install
    npm run start
    
  3. 连接测试发现端口存在跨域问题:配置elasticsearch.yml

    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
  4. 重启Es服务,再次连接测试,健康值绿色表示连接成功!

2.2、安装Kibana

Kibana是一个针对ES的开源分析及可视化平台,用来搜索、查看交互存储在ES索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )实时显示ES查询动态。

下载地址

其他版本

注:Kibana版本要与ES版本保持一致!

下载完成,解压缩即可(解压缩需要一定时间,标准的前端化工程)

启动测试

打开bin目录,双击kibana.bat启动即可

访问测试,5601端口

开发工具(PostMan、head……)

汉化,进入kibana config目录下,修改kibana.yml配置文件,zh-CN

3、ES核心概念


Elasticsearch 是面向文档型数据库,关系型数据库和elasticsearch客观的类比

Relational DBElastiSearch
DataBase(数据库)Index(索引)
table(表)Type(类型)
row(行)Documents(文档)
columns(列)Field(字段)

ES 里的 Index 可以看做一个库,而 Type 相当于表,Documents 则相当于表的行。 这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个 type,Elasticsearch 7.X 中,Type 的概念已经被删除了

ES使用JSON作为文档序列化的格式

物理设计:

elasticsearch在后台把每个索引划分成多个分片,每分分片可以在集群中的不同服务器间迁移

一个ES就是一个集群!默认的集群名称就是elasticsearch

逻辑设计:

文档

就是每条记录

之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:

  • 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value !
  • 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的!
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段

索引

就是数据库

索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。 索引存储了映射类型的字段和其他设置。然后它们被存储搭配各个分片上

映射(Mapping)

映射是用来定义一个文档,和其包含的字段,是如何存储和索引的过程,相当于关系型数据库中给列添加约束

4、IK分词器插件


4.1、什么是IK分词器?

分词:就是把一段中文或者别的划分成一个个的关键字,在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如“我爱中国会被分为"我",“爱”,“中”,“国”,这显然是不符合要求的,所以需要安装中文分词器ik来解决这个问题

IK提供了两个分词算法:ik. _smartik_ max_ word,其中ik. smart为最少切分,ik_ max_ word为最细粒度划分

4.2、安装Ik分词器

  1. 下载地址

  2. 下载完毕后,解压缩存放在es插件文件夹目录即可

  3. 重启观察es,可以看到ik分词器被加载了

  4. elasticsearch-plugin 可以通过该命令查看加载进来的插件

  5. 使用kibana测试

查看不同的分词效果

ik. smart为最少切分

ik_ max_ word为最细粒度划分,穷尽词库的可能性

如果需要自己配置分词可以在自定义的dic文件中进行配置即可

注:分词器也要和es版本对应

5、Rest风格


5.1、Rest说明

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁 ,更有层次 ,更易于实现缓存等机制

基本Rest命令说明:

mehtodurl地址描述
PUTlocalhost:9200/索引名称/类型名称/文档id创建文档(指定文档id)
POSTlocalhost:9200/索引名称/类型名称创建文档(随机文档id)
POSTlocalhost:9200/索引名称/类型名称/文档id/_update修改文档
DELETElocalhost:9200/索引名称/类型名称/文档id删除文档
GETlocalhost:9200/索引名称/类型名称/文档id通过id查询文档
POSTlocalhost:9200/索引名称/类型名称/_search查询所有数据

5.2、索引操作

创建一个索引

PUT /索引名/类型名/文档id

自动增加索引,数据成功添加

创建索引具体规则

获得索引

GET 索引名

查看默认信息

GET _cat/indices 可以获取es的很多具体相关信息

修改文档数据 方式一:直接覆盖

修改文档数据 方式二:修改指定字段

删除索引 DELETE order

删除索引文档 DELETE order/_doc/1

5.3、文档操作

5.3.1、基本操作

添加数据

PUT /xiaozhang/user/1
{
  "username":"小张",
  "gender":"男",
  "age":20,
  "birth":"2001-06-06",
  "hobby":["code","girl","music"]
}

查询数据

单查询 GET 索引名称/类型/文档id

全查询 GET 索引名称/类型/_search

更新数据 跟上述一致,可分为两种修改 PUT和POST,如果使用PUT的话没有填写的字段值均设为空,POST可以灵活设置字段 推荐使用POST

删除数据 DELETE 索引名称/类型/文档id

5.3.2、复杂操作

排序、分页、模糊查询、精准查询、高亮显示

结果过滤

排序

分页

布尔值查询

​ must (and)所有条件都需要符合

​ should(or)只需要符合一个条件即可

​ must_not(not)

匹配多个条件

精确查询

高亮查询

自定义高亮条件

6、集成SpringBoot

SpringBoot平台并没有跟随ES的更新速度进行同步更新,ES提供了High Level Client操作ES


  1. 导入依赖

    <!-- elasticsearch 的客户端 -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId
    </dependency>
    
  2. 找出对应的类(对象)

6.1、索引操作

  1. 创建索引

    
    private RestHighLevelClient client;
    
    @BeforeEach
     void setUp() {
         this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
     }
    
     @AfterEach
     void tearDown() throws IOException {
         // 关闭客户端
         this.client.close();
     }
    
    // 创建索引
    @Test
    void createIndex() throws IOException {
        // 1、创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("order");
        // 2、客户端执行请求  IndicesClient    请求后获得响应
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response.index());
        client.close();
    }
    
  2. 查看索引

    // 查询索引
    @Test
    void getIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("order");
        GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
        System.out.println(response.getSettings());
        client.close();
    }
    
  3. 删除索引

    // 删除索引
    @Test
    void deleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("order");
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println("是否删除成功:" + response.isAcknowledged());
        client.close();
    }
    

6.2、文档操作

  1. 创建文档

    
    private RestHighLevelClient client;
    
    // 文档操作
    // 创建文档
    @Test
    public void CreateDocument() throws IOException {
        Cart cart = new Cart("小米手机", 3699.00, 1000, new Date());
        // 创建请求
        IndexRequest request = new IndexRequest("cart");
        // PUT  cart/_doc/1
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
        // 将数据放入请求中 需要Json格式的
        request.source(JSON.toJSONString(cart), XContentType.JSON);
        // 客户端发送请求 获取响应结果
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        System.out.println(response.toString());
        System.out.println(response.getResult());
        client.close();
    }
    
  2. 查询文档是否存在

    // 测试文档是否存在
    @Test
    public void isExists() throws IOException {
        GetRequest request = new GetRequest("cart", "1");
        boolean exists = client.exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
        client.close();
    }
    
  3. 查询文档

    @Test
    // 获取文档信息
    public void queryDocument() throws IOException {
        GetRequest getRequest = new GetRequest("cart", "1");
        // GET cart/_doc/1
        GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
        // 获取文档的内容
        System.out.println(response.getSourceAsString());
        System.out.println(response);       // 获取全部信息
        client.close();
    }
    
  4. 修改文档

    @Test
    // 修改文档
    public void updateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("cart", "1");
        request.timeout(TimeValue.timeValueSeconds(1));
        // POST cart/_doc/1
    
        Cart cart = new Cart("华为手机", 5299.00, 5000, new Date());
        request.doc(JSON.toJSONString(cart), XContentType.JSON);
    
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        System.out.println(response.getResult());
    }
    
  5. 删除文档

    @Test
    // 删除文档
    public void deleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("cart", "1");
        // DELETE cart/_doc/1
        request.timeout(TimeValue.timeValueSeconds(1));
    
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        System.out.println(response.getResult());
        client.close();
    }
    
  6. 批量插入

    @Test
    // 批量插入
    public void bulkRequestAdd() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
    
        List<Cart> cartList = new ArrayList<>();
        cartList.add(new Cart("小米手机", 1799.00, 5000, new Date()));
        cartList.add(new Cart("华为手机", 5299.00, 2002, new Date()));
        cartList.add(new Cart("苹果手机", 6999.00, 1255, new Date()));
        cartList.add(new Cart("OPPO手机", 1999.00, 3000, new Date()));
        cartList.add(new Cart("荣耀手机", 2299.00, 3500, new Date()));
    
        for (int i = 0; i < cartList.size(); i++) {
            // 批量修改和删除,在此次修改对应请求即可
            bulkRequest.add(new IndexRequest()
                            .index("cart")
                            .id("100" + (i + 1))
                            .source(JSON.toJSONString(cartList.get(i)), XContentType.JSON)
                           );
        }
    
        BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        // 返回false  表示成功!
        System.out.println("是否失败:" + response.hasFailures());
        System.out.println("花费时间:" + response.getTook());
    }
    
  7. 搜索

    @Test
    // 搜索
    // 搜索请求:SearchRequest
    // 条件构造:SearchSourceBuilder
    public void search() throws IOException {
        SearchRequest searchRequest = new SearchRequest();
        // 构建搜索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // 查询条件
        // 精确查询:termQuery
        // 匹配所有:matchAllQuery
        MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
        builder.query(matchAllQuery);
    
        // 设置超时时长
        builder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(builder);
    
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        for (SearchHit hit : response.getHits().getHits()) {
            System.out.println(hit.getSourceAsString());
        }
    }
    

7、练习


7.1、获取数据

  1. 导入依赖

    <!--解析网页-->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.14.2</version>
    </dependency>
    
  2. 编写实体类

  3. 编写解析网页数据工具类

    @Component
    public class HtmlParseUtil {
        public static void main(String[] args) throws Exception {
            List<Content> contents = parseJD("spring");
            for (Content content : contents) {
                System.out.println("https:" + content.getImage());
                System.out.println(content.getPrice());
                System.out.println(content.getTitle());
                System.out.println(content.getShop());
            }
        }
    
        public static List<Content> parseJD(String keyword) throws Exception {
            String url = "https://search.jd.com/Search?keyword=" + keyword;
            List<Content> goodsList = new ArrayList<>();
            Document document = Jsoup.parse(new URL(url), 30000);
            Element element = document.getElementById("J_goodsList");
            Elements elements = element.getElementsByTag("li");
                for (Element el : elements) {
                String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
                String price = el.getElementsByClass("p-price").eq(0).text();
                String title = el.getElementsByClass("p-name").eq(0).text();
                String shop = el.getElementsByClass("curr-shop").eq(0).attr("title");
                Content content = new Content(img, price, title, shop);
                goodsList.add(content);
            }
            return goodsList;
        }
    }
    

7.2、获取的数据存放到Es中

  1. 编写ES客户端配置类

    @Configuration
    public class ElasticSearchClientConfig {
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            return new RestHighLevelClient(RestClient.builder(
                new HttpHost("localhost", 9200, "http"))
                                          );
        }
    }
    
  2. 编写service层

    @Service
    public class ContentService {
        @Autowired
        @Qualifier("restHighLevelClient")
        private RestHighLevelClient client;
    
        // 解析数据放入es索引中
        public Boolean parseContent(String keyword) throws Exception {
            List<Content> contents = HtmlParseUtil.parseJD(keyword);
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("15s");
    
            for (int i = 0; i < contents.size(); i++) {
                bulkRequest.add(new IndexRequest("jd_good")
                                .source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
            }
    
            BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            return !bulk.hasFailures();
        }
    }
    
  3. 实现高亮功能

    // 获取数据实现搜索高亮功能
    public List<Map<String, Object>> searchPageHighLight(String keyword, int pageNo, int pageSize) throws Exception {
        if (pageNo  <= 1) {
            pageNo = 1;
        }
        // 条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_good");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    
        // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
    
        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        // 关闭多个高亮文本     只显示第一个文本值的高亮
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style='color:red;'>");
        highlightBuilder.postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);
    
        // 精准匹配
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(queryBuilder);
        sourceBuilder.timeout(new TimeValue(20, TimeUnit.SECONDS));
    
        // 执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        // 解析结果
        List<Map<String, Object>> maps = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits()) {
            // 解析高亮的字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField title = highlightFields.get("title");
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();     // 之前的结果
            // 将原来的字段换成高亮的字段即可!
            if (title != null) {
                Text[] fragments = title.fragments();
                String newTitle = "";
                for (Text fragment : fragments) {
                    newTitle += fragment;
                }
                sourceAsMap.put("title", newTitle);     // 获得的高亮字段替换之前普通标题的字段即可
            }
            maps.add(sourceAsMap);
        }
        return maps;
    }
    
  4. 编写controller层

    @RestController
    public class ContentController {
        @Autowired
        private ContentService contentService;
    
        @GetMapping("/parse/{keyword}")
        public boolean parse(@PathVariable String keyword) throws Exception {
            return contentService.parseContent(keyword);
        }
    
        @GetMapping("/search/{keyWord}/{pageNo}/{pageSize}")
        public List<Map<String, Object>> search(@PathVariable("keyWord") String keyWord,
                                                @PathVariable("pageNo") int pageNo,
                                                @PathVariable("pageSize") int pageSize) throws Exception {
            return contentService.searchPageHighLight(keyWord, pageNo, pageSize);
        }
    }
    
  5. 测试访问

数据查询完毕,与前端联调即可!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐