我虽然穷,但是我爱学习呀。
当然,如果我有一个亿,我愿意放弃这个爱好,去玩耍。
开场白:
Lucene:一个基于Java的搜索库,采用了基于倒排表的设计原理,高效地实现文本查找。底层采用了分段的存储模式,在读写时避免了锁的出现
Elasticsearch:对 Lucene 进行了封装,对外提供 RESTful API
Kibana:负责以图表的形式呈现数据
Logstash:动态数据收集管道
Beats:轻量型数据采集器,负责从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据
1 为什么要用 Elasticsearch?
1.1 站内搜索用SQL就能实现,为什么要用 Elasticsearch 呢?
先来看看我们用SQL来搜索的话,有什么缺点:
- SQL没有相关度排名,如综合排序。而ES内部是有一个打分机制的。
- SQL搜索结果没有关健字高亮显示
- SQL搜索有时较慢,尤其是数据库不在本地时,超慢
1.2 模糊查询
假设有一张书籍表book,根据书籍名称从中检索一些书籍时,用MySQL实现,你可能会这样写SQL:
SELECT * FROM book WHERE book_name LIKE '%关键词%'
这样做,理论上是可以搜到一些数据的,比如和用户输入的关键词完全匹配的就可以,如果 颠倒了词的顺序,LIKE关键词肯定是匹配不到了。
例:
搜索一本书籍名称为 “黑萌影帝妙探妻” 的书,使用 SQL
select * from r_read_book where book_name like '%影帝黑%'
搜索结果为空。而使用 ES 可以搜索出相应的书籍
GET book/_search
{
"query": {
"match": {
"book_name": "影帝黑"
}
}
}
另外,LIKE是全表扫描的一个操作,如果你的数据量较小,还好说,但如果你数据量在百万、千万甚至更多的时候,耗时将是不可想象的,而且 DB 的 like操作会导致 SQL 注入黑洞。如:
select * from book where book_name like '%1%';drop table book;#%'
标红的为输入值,#表示注释
注入的独立语句
drop table book;
会造成删表
2 有了 ElasticSearch 还需要 MySQL 吗?
通常,我们可以使用 ES 来实现自己的站内搜索引擎,但还是推荐大家使用 MySQL 来做原始数据的存储,然后基于 MySQL 在上层部署我们的 ES 中间件来实现我们的搜索引擎。主要原因是,MySQL 虽然在数据全文检索方面显得有些力不从心,但是因为它的事务功能特性,可以保证不会出现脏数据。而ES对事务方面并无建树,所以不是很适合存储原始数据。当然,你可以运用双写的策略,一方面利用 MySQL 保证原始数据的安全性,另一方面,利用ES的搜索力量。更推荐的是将两个中间件直接结合起来,同时使用 ES 查询数据,并结合 MySQL 做数据的增删差改,具体实现细节,要根据实际的需求来制定最优的解决方案。
2.1 ElasticSearch 作为主要的后端系统
适用于新启动的项目
2.2 将 Elasticsearch 添加到现有的系统
对于一个正在运作的复杂系统,但想加入搜索功能,可以采用该方式
例子:
有一家在线零售商店,商品的信息都存在 SQL 数据库中,需要快速且相关性良好的搜索,于是安装了 Elasticsearch。为了索引数据,需要部署同步机制,既可以是 Elasticsearch 的插件,也可以是自行构建的定制化服务。同步的机制可以将每个商品对应的数据拉取出来,并将其索引到 Elasticsearch 中,每个商品存储为一篇 Elasticsearch 的文档。信息的插入或更新仍可以在 SQL 数据库上进行,可以使用 Elasticsearch 仅来处理搜索。保持 Elasticsearch 更新最近的变化取决于同步的机制。
3 开始动手了
3.1 安装(WIndows)
下载压缩包,解压,进入bin,运行 elasticsearch.bat
访问:Elasticsearch 9200 端口,Kibana 5601端口。Kibana 控制台地址
3.2 基本概念
3.2.1 Index
An index is a collection of documents that have somewhat similar characteristics. For example, you can have an index for customer data, another index for a product catalog, and yet another index for order data.
ES 索引是文档的集合,类似于数据库。
3.2.2 Document
Elasticsearch 是面向文档型数据库,Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。用 JSON 作为文档序列化的格式,如:
{ "name" : "John",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ] }
3.2.3 Type
Document 可以分组,比如 book
这个 Index 里面,可以按类型分组(言情和玄幻),也可以按作者分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。
如上,type 类型中的 book_name 字段与 author 类型中的 book_name 字段是被存储在同一个字段中,而且两个 book_name 字段在这两种映射类型中都有相同的定义,如类型都是 text。
当你希望在一个索引中的两个映射类型,一个映射类型中的 deleted 字段映射为一个日期数据类型的字段,而在另一个映射类型中的 deleted 字段映射为一个布尔数据类型的字段,这就会失败。
而在一个关系型数据库中,表之间是相互独立的。一个表中的列与另一个表中同名的列没有关系。
版本 8.x 后,废弃了类型 Type,回归到一般意义上的索引定义,索引定位文档。
Elasticsearch和关系型数据术语大致的对照表
关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)
Elasticsearch ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields)
3.3 基本操作
(1) 查看 ES 的基本信息
GET /
(2) 新建一个 Index,并指定需要分词的字段。
PUT /book
{
"mappings": {
"properties": {
"book_name": { "analyzer": "standard","type": "text"},
"author": { "type": "keyword" },
"desc": { "analyzer": "standard","type": "text"},
"comment_count":{"type":"integer"}
}
}
}
上面代码中,首先新建一个名称为book
的 Index,里面有 3 个字段
bookName:类型为text文本,指定了一个标准的分词器,用于全文搜索。
author:类型为keyword关键字,不需要分词,用于聚合查询,统计分析。
desc:类型为text文本,指定了一个标准的分词器,用于全文搜索。
(3) 添加文档,book 为索引,_doc 为类型。7.x 版本,一个索引只有一个类型。
POST book/_doc/1
{
"book_name": "大人物的小萌妻",
"author": "秋如意",
"desc": "她跟他相差了十岁,她,在学校,视做平庸无奇的眼镜妹;在家族里,被人笑没人要的书呆子;他,商界大佬对他俯首称臣,帝国上将奉他为座上宾,偏偏有了交集!大叔,我会好好做你的小萌妻!你可不能反悔!大叔,爱你无悔!",
"comment_count":57
}
POST book/_doc/2
{
"book_name": "黑萌影帝妙探妻",
"author": "犬犬",
"desc": "第一回合,他被她推得头破血流,第二回合,他被她摔得满目晕眩,但他始终知道,她就是那个他要的女人。",
"comment_count":107
}
POST book/_doc/3
{
"book_name": "西晋小厨师",
"author": "乱石兰竹",
"desc": "乱世将临,且看卫平揽财富,聚美人,收天下英雄,扶大厦于将倾。",
"comment_count":11
}
(4) 根据 id 查看文档
GET book/_doc/1
(5) 修改文档用 PUT,修改文档 id 为 1 的文档
PUT book/_doc/1
{
"book_name": "大人物的小萌妻",
"author": "秋如意",
"desc": "她跟他相差了十岁",
"comment_count":57
}
(6) 根据字段匹配文档,再做修改。如下:找到 “author” 为 “秋如意” 的文档,再修改其 “desc” 字段值。
POST book/_update_by_query
{
"script":{
"source":"ctx._source.desc = params.desc",
"lang":"painless",
"params":{
"desc":"她跟他相差了十岁"
}
},
"query":{
"match":{
"author":"秋如意"
}
}
}
(7) 根据书籍名称匹配查询
GET book/_search
{
"query": {
"match": {
"book_name": "影帝黑"
}
}
}
(8) 多个字段匹配查询
GET book/_search
{
"query": {
"bool":{
"must":[
{"match":{
"author":"秋如意"
}},{
"match":{
"book_name":"小"
}
}
]
}
}
}
(9) 统计数量
GET book/_count
{
"query": {
"bool":{
"must":[
{"match":{
"author":"秋如意"
}},{
"match":{
"book_name":"小"
}
}
]
}
}
}
(10) 按某字段排序
GET user/_search
{
"query": {
"range":{
"age":{
"gte":30,
"lte":40
}
}
},"sort":[
{
"age":{
"order":"desc"
}
}
]
}
(11) 查询结果高亮显示
GET book/_search
{
"query" : {
"match": { "book_name": "影帝黑" }
},
"highlight" : {
"fields" : {
"book_name" : {}
}
}
}
(12) 聚合查询
GET book/_search
{
"size": 0,
"aggs": {
"range": {
"field": "comment_count",
"ranges": [
{
"from": 100,
"to": 110
},
{
"from": 50,
"to": 60
}
]
}
}
}
(13) 分组查询,如下按 “author” 分组查询
GET book/_search
{
"size": 0,
"aggs": {
"author": {
"terms":{
"field":"author"
}
}
}
}
3.4 SpringBoot 集成 Elasticsearch
参考:
elasticsearch入门 springboot2集成elasticsearch 实现全文搜索
结合spring boot和mysql快速实现elasticsearch7简单功能
3.5 MySQL的数据同步到 ES
全量同步:搭建好 ES 后,把 MySQL 的数据一次性同步过去
增量同步:MySQL 产生新的数据后同步到 ES,包括:新插入的数据,更新了老数据,删除了老数据
灵魂拷问:你会怎样去实现捏?
方式一:
- select * from table,然后在程序中遍历,对应生成一条条文档插入到 ES。中途又有了新数据,咋办捏?每一次同步都保留最大的 last_update_time,select * from table where updat_time >= last_update_time。
方式二:
- 在操作数据库的 CRUD 方法里对应加上 ES 的CRUD,这会导致代码,业务,数据耦合度过高
方式三:召唤中间件
3.5.1 Binlog 订阅
二进制日志 binlog:binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。binlog不会记录SELECT和SHOW这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看MySQL执行过的所有语句。
阿里巴巴 MySQL binlog 增量订阅&消费组件Canal
3.5.2 Logstash
Logstash 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中。官网
下面表演将 house 表及其关联表的数据同步到 ES 的 house 索引
看到了这里,依然很穷吧,不慌,还有秘笈: