Elasticsearch从入门到精通

ES搜索引擎 专栏收录该内容
1 篇文章 0 订阅

ES是如何产生的?

1.思考:大规模数据如何检索?

如:当系统数据量上了10亿、100亿条的时候,我们在做系统架构的时候通常会从以下角度去考虑问题:
1)用什么数据库好?(mysql、sybase、oracle、mongodb、hbase…)
2)如何解决单点故障;(lvs、F5、A10、Zookeeper、MQ)
3)如何保证数据安全性;(热备、冷备、异地多活)
4)如何解决检索难题;(数据库代理中间件:mysql-proxy、Cobar、MaxScale等;)
5)如何解决统计分析问题;(离线、近实时)

2.传统数据库的应对解决方案

对于关系型数据,我们通常采用以下或类似架构去解决查询瓶颈和写入瓶颈:
解决要点:
1)通过主从备份解决数据安全性问题;
2)通过数据库代理中间件心跳监测,解决单点故障问题;
3)通过代理中间件将查询语句分发到各个slave节点进行查询,并汇总结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RA8ljG1Z-1583922759881)(Elasticsearch/20160818205837877)]

3.非关系型数据库的解决方案

对于Nosql数据库,以mongodb为例,其它原理类似:
解决要点:
1)通过副本备份保证数据安全性;
2)通过节点竞选机制解决单点问题;
3)先从配置库检索分片信息,然后将请求分发到各个节点,最后由路由节点合并汇总结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nlTtgYJK-1583922759883)(Elasticsearch/20160818205852190)]

4.完全把数据放入内存怎么样?

我们知道,完全把数据放在内存中是不可靠的,实际上也不太现实,当我们的数据达到1PB=1000TB=1000*1000GB级别时,按照每个节点96G内存计算,在内存完全装满的数据情况下,我们需要的机器是:1PB=1024T=1048576G
节点数=1048576/96=10922个
实际上,考虑到数据备份,节点数往往在2.5万台左右。成本巨大决定了其不现实!

从前面讨论我们了解到,把数据放在内存也好,不放在内存也好,都不能完完全全解决问题。
全部放在内存速度问题是解决了,但成本问题上来了。
为解决以上问题,从源头着手分析,通常会从以下方式来寻找方法:
1、存储数据时按有序存储;
2、将数据和索引分离;
3、压缩数据;
这就引出了Elasticsearch

ES 基础

1.1 ES定义

ES=elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据,分析数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

1.2 Lucene与ES关系?

1)Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

2)Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

1.3 ES主要解决问题:

1)检索相关数据;
2)返回统计结果;
3)速度要快。

1.4 ES工作原理

当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。这个过程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovGV1Iq1-1583922759883)(Elasticsearch/20160818205953345)]

1.5 ES核心概念
1)Cluster:集群。

ES可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。
2)Node:节点。

形成集群的每个服务器称为节点。
3)Shard:分片。

当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较小的分片。每个分片放到不同的服务器上。
当你查询的索引分布在多个分片上时,ES会把查询发送给每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。
4)Replia:副本。

为提高查询吞吐量或实现高可用性,可以使用分片副本。
副本是一个分片的精确复制,每个分片可以有零个或多个副本。ES中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。
当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。
5)全文检索。

全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们的激情是因为什么事情来的” 可能会被分词成:“你们“,”激情“,“什么事情“,”来“ 等token,这样当你搜索“你们” 或者 “激情” 都会把这句搜出来。

1.6 ES数据架构的主要概念(与关系数据库Mysql对比)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1sr2Mukq-1583922759884)(Elasticsearch/20160818210034345)]

(1)关系型数据库中的数据库(DataBase),等价于ES中的索引(Index)
(2)一个数据库下面有N张表(Table),等价于1个索引Index下面有N多类型(Type),
(3)一个数据库表(Table)下的数据由多行(ROW)多列(column,属性)组成,等价于1个Type由多个文档(Document)和多Field组成。
(4)在一个关系型数据库里面,schema定义了表、每个表的字段,还有表和字段之间的关系。 与之对应的,在ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立、索引类型、是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处理等。
(5)在数据库中的增insert、删delete、改update、查search操作等价于ES中的增PUT/POST、删Delete、改_update、查GET.

select * from user.user_info where name=‘张三’

GET /user/user_info/_search?q=name:张三

1.7 ELK是什么?(携程使用这个作为日志的手机和分析框架)

ELK=elasticsearch+Logstash+kibana
elasticsearch:后台分布式存储以及全文检索
logstash: 日志加工、“搬运工” 和mysql进行一个数据同步的
kibana:数据可视化展示。
ELK架构为数据分布式存储、可视化查询和日志解析创建了一个功能强大的管理链。 三者相互配合,取长补短,共同完成分布式大数据处理工作。

ES特点和优势

1)分布式实时文件存储,可将每一个字段存入索引,使其可以被检索到。(类似数据库的功能)
2)实时分析的分布式搜索引擎。(实现类似数据库的一个查询)
分布式:索引分拆成多个分片,每个分片可有零个或多个副本。集群中的每个数据节点都可承载一个或多个分片,并且协调和处理各种操作;
负载再平衡和路由在大多数情况下自动完成。
3)可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。也可以运行在单台PC上(已测试)
4)支持插件机制,分词插件、同步插件、Hadoop插件、可视化插件等。

ES性能

3.1 性能结果展示

在上述硬件指标的基础上测试性能如下:
1)平均索引吞吐量: 12307docs/s(每个文档大小:40B/docs)
2)平均CPU使用率: 887.7%(16核,平均每核:55.48%)
3)构建索引大小: 3.30111 GB
4)总写入量: 20.2123 GB
5)测试总耗时: 28m 54s.

3.2 性能esrally工具(推荐)

使用参考:http://blog.csdn.net/laoyang360/article/details/52155481

为什么要用ES

4.1 ES国内外使用优秀案例

1) 2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。 “GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”。

2)维基百科:启动以elasticsearch为基础的核心搜索架构。
3)SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”。
4)百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据。

4.2 我们也需要

实际项目开发实战中,几乎每个系统都会有一个搜索的功能,当搜索做到一定程度时,维护和扩展起来难度就会慢慢变大,所以很多公司都会把搜索单独独立出一个模块,用ElasticSearch等来实现。

近年ElasticSearch发展迅猛,已经超越了其最初的纯搜索引擎的角色,现在已经增加了数据聚合分析(aggregation)和可视化的特性,如果你有数百万的文档需要通过关键词进行定位时,ElasticSearch肯定是最佳选择。当然,如果你的文档是JSON的,你也可以把ElasticSearch当作一种“NoSQL数据库”, 应用ElasticSearch数据聚合分析(aggregation)的特性,针对数据进行多维度的分析。

【知乎:热酷架构师潘飞】ES在某些场景下替代传统DB
个人以为Elasticsearch作为内部存储来说还是不错的,效率也基本能够满足,在某些方面替代传统DB也是可以的,前提是你的业务不对操作的事性务有特殊要求;而权限管理也不用那么细,因为ES的权限这块还不完善。
由于我们对ES的应用场景仅仅是在于对某段时间内的数据聚合操作,没有大量的单文档请求(比如通过userid来找到一个用户的文档,类似于NoSQL的应用场景),所以能否替代NoSQL还需要各位自己的测试。
如果让我选择的话,我会尝试使用ES来替代传统的NoSQL,因为它的横向扩展机制太方便了。

Elasticsearch的使用场景

场景—:使用Elasticsearch作为主要的后端

传统项目中,搜索引擎是部署在成熟的数据存储的顶部,以提供快速且相关的搜索能力。这是因为早期的搜索引擎不能提供耐用的存储或其他经常需要的功能,如统计。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oe5LQegp-1583922759886)(Elasticsearch/20160817083906505)]

Elasticsearch是提供持久存储、统计等多项功能的现代搜索引擎。
如果你开始一个新项目,我们建议您考虑使用Elasticsearch作为唯一的数据存储,以帮助保持你的设计尽可能简单。
此种场景不支持包含频繁更新、事务(transaction)的操作。

举例如下:新建一个博客系统使用es作为存储。
1)我们可以向ES提交新的博文;
2)使用ES检索、搜索、统计数据。

ES作为存储的优势:
如果一台服务器出现故障时会发生什么?你可以通过复制 数据到不同的服务器以达到容错的目的。
注意:
整体架构设计时,需要我们权衡是否有必要增加额外的存储。

场景二:在现有系统中增加elasticsearch

由于ES不能提供存储的所有功能,一些场景下需要在现有系统数据存储的基础上新增ES支持。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cC69ftsS-1583922759887)(Elasticsearch/20160817083948740)]

举例1:ES不支持事务、复杂的关系(至少1.X版本不支持,2.X有改善,但支持的仍然不好),如果你的系统中需要上述特征的支持,需要考虑在原有架构、原有存储的基础上的新增ES的支持。

举例2:如果你已经有一个在运行的复杂的系统,你的需求之一是在现有系统中添加检索服务。一种非常冒险的方式是重构系统以支持ES。而相对安全的方式是:将ES作为新的组件添加到现有系统中。
如果你使用了如下图所示的SQL数据库和ES存储,你需要找到一种方式使得两存储之间实时同步。需要根据数据的组成、数据库选择对应的同步插件。可供选择的插件包括:
1)mysql、oracle选择 logstash-input-jdbc 插件。
2)mongo选择 mongo-connector工具。

假设你的在线零售商店的产品信息存储在SQL数据库中。 为了快速且相关的搜索,你安装Elasticsearch。
为了索引数据,您需要部署一个同步机制,该同步机制可以是Elasticsearch插件或你建立一个自定义的服务。此同步机制可以将对应于每个产品的所有数据和索引都存储在Elasticsearch,每个产品作为一个document存储(这里的document相当于关系型数据库中的一行/row数据)。

当在该网页上的搜索条件中输入“用户的类型”,店面网络应用程序通过Elasticsearch查询该信息。 Elasticsearch返回符合标准的产品documents,并根据你喜欢的方式来分类文档。 排序可以根据每个产品的被搜索次数所得到的相关分数,或任何存储在产品document信息,例如:最新最近加入的产品、平均得分,或者是那些插入或更新信息。 所以你可以只使用Elasticsearch处理搜索。这取决于同步机制来保持Elasticsearch获取最新变化。

场景三:使用elasticsearch和现有的工具

在一些使用情况下,您不必写一行代码就能通过elasticssearch完成一项工作。很多工具都可以与Elasticsearch一起工作,所以你不必到你从头开始编写。
例如,假设要部署一个大规模的日志框架存储,搜索,并分析了大量的事件。
如图下图,处理日志和输出到Elasticsearch,您可以使用日志记录工具,如rsyslog(www.rsyslog.com),Logstash(www.elastic.co/products/logstash),或Apache Flume(http://flume.apache.org)。
搜索和可视化界面分析这些日志,你可以使用Kibana(www.elastic.co/产品/ kibana)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hwpxch41-1583922759887)(Elasticsearch/20160817084007974)]

为什么那么多工具适配Elasticsearch?主要原因如下:

1)Elasticsearch是开源的。
2)Elasticsearch提供了JAVA API接口。
3)Elasticsearch提供了RESTful API接口(不管程序用什么语言开发,任何程序都可以访问)
4)更重要的是,REST请求和应答是典型的JSON(JavaScript对象 符号)格式。通常情况下,一个REST请求包含一个JSON文件,其回复都 也是一个JSON文件。

总结:

1)新系统开发尝试使用ES作为存储和检索服务器;
2)现有系统升级需要支持全文检索服务,需要使用ES。

一线公司ES使用场景:

1)新浪ES 如何分析处理32亿条实时日志 http://dockone.io/article/505
2)阿里ES 构建挖财自己的日志采集和分析体系 http://afoo.me/columns/tec/logging-platform-spec.html
3)有赞ES 业务日志处理 http://tech.youzan.com/you-zan-tong-ri-zhi-ping-tai-chu-tan/
4)ES实现站内搜索 http://www.wtoutiao.com/p/13bkqiZ.html

如何部署ES

ES部署(无需安装)

1)零配置,开箱即用
2)没有繁琐的安装配置
3)java版本要求:最低1.7
我使用的1.8
4)下载地址:
https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/zip/elasticsearch/2.3.5/elasticsearch-2.3.5.zip
5)启动
cd /usr/local/elasticsearch-2.3.5
./bin/elasticsearch
bin/elasticsearch -d(后台运行)

ES必要的插件

必要的Head、kibana、IK(中文分词)、graph等插件的详细安装和使用。
http://blog.csdn.net/column/details/deep-elasticsearch.html
ES windows下一键安装

自写bat脚本实现windows下一键安装。
1)一键安装ES及必要插件(head、kibana、IK、logstash等)
2)安装后以服务形式运行ES。
3)比自己摸索安装节省至少2小时时间,效率非常高。

ES对外接口(开发人员关注)

1)JAVA API接口

http://www.ibm.com/developerworks/library/j-use-elasticsearch-java-apps/index.html
2)RESTful API接口

常见的增、删、改、查操作实现:
http://blog.csdn.net/laoyang360/article/details/51931981

ES遇到问题怎么办?

1)国外:https://discuss.elastic.co/
2)国内:http://elasticsearch.cn/

参考:

[1] http://www.tuicool.com/articles/7fueUbb
[2] http://zhaoyanblog.com/archives/495.html
[3]《Elasticsearch服务器开发》
[4]《实战Elasticsearch、Logstash、Kibana》
[5]《Elasticsearch In Action》
[6]《某ES大牛PPT》

《死磕 Elasticsearch 方法论》:普通程序员高效精进的 10 大狠招!(免费完整版)
https://blog.csdn.net/laoyang360/article/details/79293493

Postman调用RestAPI

1.新建索引

例如我们要创建一个叫blog的索引 ,就以put方式提交

http://127.0.0.1:9200/blog/ 

2.新建文档

以post方式提交 http://127.0.0.1:9200/blog/article

body:

{
"title":"New version of Elasticsearch released!",
"content":"Version 1.0 released today!",
"tags":["announce","elasticsearch","release"]
}

返回结果

{
    "_index": "blog",
    "_type": "article",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "created": true
}

_id是由系统自动生成的。 为了方便之后的演示,我们再次录入几条测试数据。

3.查询全部文档

查询某索引某类型的全部数据,以get方式请求

http://127.0.0.1:9200/blog/article/_search

{
    "took": 14,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 1,
        "hits": [
            {
                "_index": "blog",
                "_type": "ariticle",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "title": "New version of Elasticsearch released!",
                    "content": "Version 1.0 released today!",
                    "tags": [
                        "announce",
                        "elasticsearch",
                        "release"
                    ]
                }
            }
        ]
    }
}

4.修改文档

以put形式提交以下地址:

http://192.168.184.134:9200/blog/article/1

body:

{
"title":"New version of Elasticsearch released111!",
"content":"Version 1.0 released today111!",
"tags":["announce1","elasticsearch1","release1"]
}

返回结果

{
    "_index": "blog",
    "_type": "ariticle",
    "_id": "1",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "created": false
}

如果我们在地址中的ID不存在,则会创建新文档

5.按ID查询文档

GET方式请求

http://localhost:9200/blog/ariticle/1

6.基本匹配查询

//按照每一个次进行一个精确的查询
http://localhost:9200/blog/_search?pretty&q=title:released
http://localhost:9200/blog/_search?pretty&q=content:1.0

7.模糊查询

我们可以用*代表任意字符:

http://localhost:9200/blog/article/_search?q=title:*s*
http://localhost:9200/blog/_search?pretty&q=title:*s*

8.删除文档

根据ID删除文档,删除ID为1的文档 DELETE方式提交

http://localhost:9200/blog/article/1

Head插件的安装与使用

Head插件安装

如果都是通过rest请求的方式使用Elasticsearch,未免太过麻烦,而且也不够人性化。我

们一般都会使用图形化界面来实现Elasticsearch的日常管理,最常用的就是Head插件

步骤1:

下载head插件:https://github.com/mobz/elasticsearch-head

配套资料中已提供。 elasticsearch-head-master.zip

步骤2:

解压到任意目录,但是要和elasticsearch的安装目录区别开。

步骤3:

安装node js ,安装cnpm(Hexo博客的时候已经讲过的)

npm install ‐g cnpm ‐‐registry=https://registry.npm.taobao.org

步骤4:

将grunt安装为全局命令 。Grunt是基于Node.js的项目构建工具。它可以自动运行你所设定的任务

npm install -g grunt-cli

步骤5:安装依赖

cnpm install

步骤6:

进入head目录启动head,在命令提示符下输入命令

grunt server

步骤7:

打开浏览器,输入 http://localhost:9100

步骤8:

点击连接按钮没有任何相应,按F12发现有如下错误

No ‘Access-Control-Allow-Origin’ header is present on the requested resource

这个错误是由于elasticsearch默认不允许跨域调用,而elasticsearch-head是属于前端工

程,所以报错。

我们这时需要修改elasticsearch的配置,让其允许跨域访问。

修改elasticsearch配置文件:elasticsearch.yml,增加以下两句命令:

http.cors.enabled: true
http.cors.allow-origin: "*"

此步为允许elasticsearch跨越访问 点击连接即可看到相关信息

Head插件操作

新建索引

选择“索引”选项卡,点击“新建索引”按钮

新建或修改文档

在复合查询中提交地址,输入内容,提交方式为PUT

IK分词器

什么是IK分词器

我们在浏览器地址栏输入http://127.0.0.1:9200/_analyze?analyzer=chinese&pretty=true&text=我是聂大帅逼,浏览器显示效果如下

{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "聂",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "大",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "帅",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "逼",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    }
  ]
}

默认的中文分词是将每个字看成一个词,这显然是不符合要求的,所以我们需要安装中

文分词器来解决这个问题。

IK分词是一款国人开发的相对简单的中文分词器。虽然开发者自2012年之后就不在维护

了,但在工程应用中IK算是比较流行的一款!我们今天就介绍一下IK中文分词器的使用

IK分词器的安装

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases 下载5.6.8版5.6.8.zip

(1)先将其解压,将解压后的elasticsearch文件夹重命名文件夹为ik

(2)将ik文件夹拷贝到elasticsearch/plugins 目录下。

(3)重新启动,即可加载IK分词器

IK分词器测试

IK提供了两个分词算法ik_smart 和 ik_max_word

其中 ik_smart 为最少切分,ik_max_word为最细粒度划分

我们分别来试一下

(1)最小切分:在浏览器地址栏输入地址

http://127.0.0.1:9200/_analyze?analyzer=ik_smart&pretty=true&text=我是程序员

输出的结果为:

{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

(2)最细切分:在浏览器地址栏输入地址

http://127.0.0.1:9200/_analyze?analyzer=ik_max_word&pretty=true&text=我是程序员

输出的结果为:

{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "程序",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 4
    }
  ]
}

自定义词库

我们现在测试"优锐课",浏览器的测试效果如下:

http://127.0.0.1:9200/_analyze?analyzer=ik_smart&pretty=true&text=优锐课

结果:

{
  "tokens" : [
    {
      "token" : "优",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "锐",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "课",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "CN_CHAR",
      "position" : 2
    }
  ]
}

默认的分词并没有识别“优锐课”是一个词。如果我们想让系统识别“优锐课”是一个词,需要编辑自定义词库。

步骤:

(1)进入elasticsearch/plugins/ik/config目录

(2)新建一个my.dic文件,编辑内容:

优锐课

注意:文件一定要保存成无BOM的UTF-8格式

修改IKAnalyzer.cfg.xml(在ik/config目录下)

<properties> <comment>IK Analyzer 扩展配置</comment>
    <!‐‐用户可以在这里配置自己的扩展字典 ‐‐> 
    <entry key="ext_dict">my.dic</entry> 
    <!‐‐用户可以在这里配置自己的扩展停止词字典‐‐> 
    <entry key="ext_stopwords"></entry>
</properties>

重新启动elasticsearch,通过浏览器测试分词效果

{
  "tokens" : [
    {
      "token" : "优锐课",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    }
  ]
}

SpringBoot操作ES

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- elasticsearch -->
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <!-- <version>6.0.0</version> -->
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <!-- <version>2.1.3</version>   -->
    </dependency>
    <dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna</artifactId>
        <!-- <version>3.0.9</version> -->
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>
</dependencies>

application.properties

# elasticsearch
#节点名字,默认elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch
#节点地址,多个节点用逗号隔开
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#spring.data.elasticsearch.local=false
spring.data.elasticsearch.repositories.enable=true

Employee.java

package com.youruike.es.pojo;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;

// indexName :索引名字(对应mysql的数据库名字)
//type:类型(对应mysql的表名)
@Document(indexName = "megacorp", type = "employee", shards = 1, replicas = 0, refreshInterval = "-1")
public class Employee {

    @Id
    private String id;
    @Field
    private String firstName;
    @Field
    private String lastName;
    @Field
    private Integer age = 0;
    @Field
    private String about;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAbout() {
        return about;
    }

    public void setAbout(String about) {
        this.about = about;
    }
}

EmployeeRepository.java

package com.youruike.es.dao;

import com.youruike.es.pojo.Employee;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;

@Component
public interface EmployeeRepository extends ElasticsearchRepository<Employee,String>{

    Employee queryEmployeeById(String id);

}

ElasticSearchController.java

package com.youruike.es.controller;

import com.google.gson.Gson;
import com.youruike.es.dao.EmployeeRepository;
import com.youruike.es.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/es")
public class ElasticSearchController {

    @Autowired
    private EmployeeRepository employeeRepository;

    //增加
    @RequestMapping("/add")
    public String add(){

        Employee employee=new Employee();
        employee.setId("1");
        employee.setFirstName("Nie");
        employee.setLastName("Changan");
        employee.setAge(26);
        employee.setAbout("i am in ChangSha");
        employeeRepository.save(employee);

        System.err.println("add a obj");

        return "success";
    }

    //删除
    @RequestMapping("/delete")
    public String delete(){

        employeeRepository.deleteById("1");

        return "success";
    }

    //局部更新
    @RequestMapping("/update")
    public String update(){

        Employee employee=employeeRepository.queryEmployeeById("1");
        employee.setFirstName("哈哈");
        employeeRepository.save(employee);

        System.err.println("update a obj");

        return "success";
    }

    //查询
    @RequestMapping("/query")
    public Employee query(){

        Employee accountInfo=employeeRepository.queryEmployeeById("1");
        System.err.println(new Gson().toJson(accountInfo));

        return accountInfo;
    }
}

详细版SpringData操作ES

参考:

https://blog.csdn.net/chen_2890/article/details/83895646

2.3号复习

ES的下载和启动

es的官网

https://www.elastic.co/cn/

下载es的地址

https://www.elastic.co/cn/elasticsearch

下载kibana的地址

https://www.elastic.co/cn/kibana

下载logstash的地址

https://www.elastic.co/cn/logstash

下载ik分词器的地址

https://github.com/medcl/elasticsearch-analysis-ik

演示 postman、kibana对ES的交互

PostMan

Get 查看所有索引

localhost:9200/_all

PUT 创建索引-test

localhost:9200/test  


DEL 删除索引-test

localhost:9200/test  


PUT 创建索引-person-1

localhost:9200/person


PUT 新增数据-person-1

localhost:9200/person/_doc/1

{
  "first_name" : "John",
  "last_name" : "Smith",
  "age" : 25,
  "about" : "I love to go rock climbing",
  "interests" : [ "sports", "music" ]
}

PUT 新增数据-person-2

localhost:9200/person/_doc/2

{
  "first_name" : "Eric",
  "last_name" : "Smith",
  "age" : 23,
  "about" : "I love basketball",
  "interests" : [ "sports", "reading" ]
}

GET 搜索数据-person-id

localhost:9200/person/_doc/1

GET 搜索数据-person-name

localhost:9200/person/_doc/_search?q=first_name:john

{
  "took": 56,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.6931472,
    "hits": [
      {
        "_index": "person",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.6931472,
        "_source": {
          "first_name": "John",
          "last_name": "Smith",
          "age": 25,
          "about": "I love to go rock climbing",
          "interests": [
            "sports",
            "music"
          ]
        }
      }
    ]
  }
}

Kibana

http://localhost:5601/app/kibana

GET _search
{
  "query": {
    "match_all": {}
  }
}


GET _all

GET /person/_doc/1

POST /person/_search
{
	"query": {
  	"bool": {
	 "should": [
      	{"match": {
        		"first_name": "Eric"
        	}
   		}
      ]
  	}
}

POST /person/_search
{
	"query": {
  	"bool": {
			"should": [
      	{"match": {
        		"last_name": "Smith"
        	}
   			},
        {
        	"match": {
          	"about": "basketball"
          }
				}
      ]
  	}
  }
}


POST /person/_search
{
	"query": {
  	"bool": {
			"must": [
      	{"match": {
        		"last_name": "Smith"
        	}
   			},
        {
        	"match": {
          	"about": "basketball"
          }
				}
      ]
  	}
  }
}

博客网站全文检索

CREATE DATABASE blog;

USE blog;

CREATE TABLE `t_blog` (    
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',    
   `title` varchar(60) DEFAULT NULL COMMENT '博客标题',    
   `author` varchar(60) DEFAULT NULL COMMENT '博客作者',    
   `content` mediumtext COMMENT '博客内容',    
   `create_time` datetime DEFAULT NULL COMMENT '创建时间',    
   `update_time` datetime DEFAULT NULL COMMENT '更新时间',    
   PRIMARY KEY (`id`)    
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4


# 自己造的几条数据
INSERT INTO `blog`.`t_blog`(`id`, `title`, `author`, `content`, `create_time`, `update_time`) VALUES (1, 'Springboot 为什么这', 'Ange', '没错 Springboot ', '2020-02-03 16:44:29', '2020-02-03 16:44:34');
INSERT INTO `blog`.`t_blog`(`id`, `title`, `author`, `content`, `create_time`, `update_time`) VALUES (3, 'Springboot 中 Redis', 'Ange', 'Spring Boot', '2020-02-03 16:44:29', '2020-02-03 16:44:29');
INSERT INTO `blog`.`t_blog`(`id`, `title`, `author`, `content`, `create_time`, `update_time`) VALUES (4, 'Springboot 中如何优化', 'Ange', NULL, '2020-02-03 16:44:29', '2020-02-03 16:44:29');
INSERT INTO `blog`.`t_blog`(`id`, `title`, `author`, `content`, `create_time`, `update_time`) VALUES (5, 'Springboot 消息队列', 'Ange', NULL, '2020-02-03 16:44:29', '2020-02-03 16:44:29');
INSERT INTO `blog`.`t_blog`(`id`, `title`, `author`, `content`, `create_time`, `update_time`) VALUES (6, 'Docker Compose + Springboot', 'Ange', NULL, '2020-02-03 16:44:29', '2020-02-03 16:44:29');

SELECT * FROM t_blog WHERE title LIKE "%spring%" or content LIKE "%spring%"

Mysql、ES 数据同步

1.binlog订阅

2.开源中间件:go-mysql-elasticsearch

​ 不足:不支持 ES6.X 以上、Mysql 8.X 以上

3.开源中间件:logstash

logstash全量、增量同步解决方案

<https://www.elastic.co/cn/downloads/logstash

mysql.conf

input{
    jdbc{
        # jdbc驱动包位置
        jdbc_driver_library => "logstash-7.5.0/mysql-connector-java-5.1.31.jar"
        # 要使用的驱动包类
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        # mysql数据库的连接信息
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/blog"
        # mysql用户
        jdbc_user => "root"
        # mysql密码
        jdbc_password => "root"
        # 定时任务,多久执行一次查询,默认一分钟,如果想要没有延迟,可以使用 schedule => "* * * * * *"
        schedule => "* * * * *"
        # 清空上传的sql_last_value记录
        clean_run => true
        # 你要执行的语句
        statement => "select * FROM t_blog WHERE update_time > :sql_last_value AND update_time < NOW() ORDER BY update_time desc"
    }
}

output {
    elasticsearch{
        # es host : port
        hosts => ["127.0.0.1:9200"]
        # 索引
        index => "blog"
        # _id
        document_id => "%{id}"
    }
}

运行:

bin/logstash -f config/mysql.conf

会报错,错误信息:

Unable to find driver class via URLClassLoader in given driver jars: com.mysql.jdbc.Driver and com.mysql.jdbc.Driver

这里只是一个解决方法。只需将驱动程序Jar文件复制到<logstash_install_dir>/logstash-core/lib/jars/目录。

然后删除conf里的jdbc驱动包配置

input{
    jdbc{
        # jdbc驱动包位置
        #jdbc_driver_library => "D:\\youruike\\Elasticsearch\\logstash-7.5.0\\mysql-connector-java-8.0.13.jar"
        # 要使用的驱动包类
        jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
        # mysql数据库的连接信息
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"
        # mysql用户
        jdbc_user => "root"
        # mysql密码
        jdbc_password => "root"
        # 定时任务,多久执行一次查询,默认一分钟,如果想要没有延迟,可以使用 schedule => "* * * * * *"
        schedule => "* * * * *"
        # 清空上传的sql_last_value记录
        clean_run => true
        # 你要执行的语句
        statement => "select * FROM t_blog WHERE update_time > :sql_last_value AND update_time < NOW() ORDER BY update_time desc"
    }
}

output {
    elasticsearch{
        # es host : port
        hosts => ["127.0.0.1:9200"]
        # 索引
        index => "blog"
        # _id
        document_id => "%{id}"
    }
}

logstash -f ..\config\mysql.conf

IK分词器的安装和使用

POST _analyze
{
  "analyzer": "standard",
  "text" : "hello youruike"
}

POST _analyze
{
  "analyzer": "standard",
  "text" : "我是中国人"
}

POST _analyze
{
  "analyzer": "ik_max_word",
  "text" : "我是中国人"
}

POST _analyze
{
  "analyzer": "ik_smart",
  "text" : "我是中国人"
}

POST _analyze
{
  "analyzer": "ik_max_word",
  "text" : "我是优锐课"
}

博客系统的构建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wmaoBUVo-1583922759889)(Elasticsearch/1580730299060.png)]

application.yml

server:
  port: 8081
spring:
  #数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    # hikari 数据源专用配置
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5

  # jpa 相关配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    # 数据库方言
    database-platform: org.hibernate.dialect.MySQLDialect
  # es 配置

  # mvc 静态资源映射
  mvc:
    static-path-pattern: /**

  # 静态资源热部署
  devtools:
    livereload:
      enabled: true
    restart:
      additional-paths: static/**

  # 日期格式化
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

MysqlBlog.java

package com.youruike.es2.entity.mysql;

import lombok.Data;

import javax.persistence.*;
import java.util.Date;

@Data
@Table(name = "t_blog")
@Entity
public class MysqlBlog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String title;
    private String author;
    @Column(columnDefinition = "mediumtext")
    private String content;
    private Date createTime;
    private Date updateTime;
}

MysqlBlogRepository.java

package com.youruike.es2.repository;

import com.youruike.es2.entity.mysql.MysqlBlog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface MysqlBlogRepository extends JpaRepository<MysqlBlog, Integer> {

    /**
     * 创建时间倒序查询博客
     *
     * @return
     */
    @Query("select e from MysqlBlog e order by e.createTime desc ")
    List<MysqlBlog> queryAll();

    /**
     * 模糊查询
     *
     * @param keyword
     * @return
     */
    @Query("select e from MysqlBlog e where e.title like concat('%',:keyword,'%') or e.content like concat('%',:keyword,'%')")
    List<MysqlBlog> queryBlog(@Param("keyword") String keyword);

}


IndexController.java

package com.youruike.es2.controller;

import com.youruike.es2.entity.mysql.MysqlBlog;
import com.youruike.es2.repository.MysqlBlogRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@CrossOrigin
@Controller
@Slf4j
public class IndexController {
    @Autowired
    MysqlBlogRepository mysqlBlogRepository;

    @GetMapping("/")
    public String index() {
        List<MysqlBlog> all = mysqlBlogRepository.findAll();
        log.info("【查询所有的博客数据】all={}", all.size());
        return "index.html";
    }
}

EsBlog.java

package com.youruike.es2.entity.es;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;

@Data
/**
 * useServerConfiguration = true 使用线上的配置,createIndex 在项目启动的时候不要创建索引,通常在 kibana 中已经配置过了
 */
@Document(indexName = "blog", type = "_doc", useServerConfiguration = true, createIndex = false)
public class EsBlog {

    @Id
    private Integer id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String author;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
    private Date createTime;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
    private Date updateTime;

}

EsBlogRepository.java

package com.youruike.es2.repository;

import com.youruike.es2.entity.es.EsBlog;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, Integer> {
}

DataController.java

package com.youruike.es2.controller;

import com.youruike.es2.entity.es.EsBlog;
import com.youruike.es2.entity.mysql.MysqlBlog;
import com.youruike.es2.repository.EsBlogRepository;
import com.youruike.es2.repository.MysqlBlogRepository;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@CrossOrigin
@RestController
@Slf4j
public class DataController {

    private static final String MYSQL = "mysql";
    private static final String ES = "es";

    @Autowired
    MysqlBlogRepository mysqlBlogRepository;

    @Autowired
    EsBlogRepository esBlogRepository;

    @GetMapping("/blogs")
    public Object blogList() {
        List<MysqlBlog> mysqlBlogs = mysqlBlogRepository.queryAll();
        return mysqlBlogs;
    }

    @PostMapping("/search")
    public Object search(@RequestBody Param param) {
        Map<String, Object> map = new HashMap<>();
        // 统计耗时
        StopWatch watch = new StopWatch();
        watch.start();
        String type = param.getType();
        // mysql 的搜索
        if (MYSQL.equals(type)) {
            List<MysqlBlog> mysqlBlogs = mysqlBlogRepository.queryBlog(param.getKeyword());
            map.put("list", mysqlBlogs);
            // es 的搜索
        } else if (ES.equals(type)) {
            BoolQueryBuilder builder = QueryBuilders.boolQuery();
            builder.should(QueryBuilders.matchPhraseQuery("title", param.getKeyword()));
            builder.should(QueryBuilders.matchPhraseQuery("content", param.getKeyword()));
            String s = builder.toString();
            log.info("s={}", s);
            Page<EsBlog> search = (Page<EsBlog>) esBlogRepository.search(builder);
            List<EsBlog> content = search.getContent();
            map.put("list", content);
        } else {
            return "你要啥呢小老弟";
        }
        watch.stop();
        // 计算耗时
        long millis = watch.getTotalTimeMillis();
        map.put("duration", millis);
        return map;
    }

    @GetMapping("/blog/{id}")
    public Object blog(@PathVariable Integer id) {
        Optional<MysqlBlog> byId = mysqlBlogRepository.findById(id);
        return byId.get();
    }

    @Data
    private static class Param {
        private String type;
        private String keyword;
    }

}


CorsConfig.java

package com.youruike.es2.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;

import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 解决前端跨域问题
 *
 */
@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值