ElasticSearch
目录
Elasticsearch简单介绍
Elasticsearch (ES)是一个基于Lucene构建的开源、分布式、RESTful 接口全文搜索引擎。Elasticsearch 还是一个分布式文档数据库,其中每个字段均是被索引的数据且可被搜索,它能够扩展至数以百计的服务器存储以及处理PB级的数据。它可以在很短的时间内在储、搜索和分析大量的数据。它通常作为具有复杂搜索场景情况下的核心发动机。
Elasticsearch就是为高可用和可扩展而生的。可以通过购置性能更强的服务器来完成。
Elasticsearch优势
横向可扩展性:只需要增加台服务器,做一点儿配置,启动一下Elasticsearch就可以并入集群。
分片机制提供更好的分布性:同一个索引分成多个分片(sharding), 这点类似于HDFS的块机制;分而治之的方式可提升处理效率。
高可用:提供复制( replica) 机制,一个分片可以设置多个复制,使得某台服务器在宕机的情况下,集群仍旧可以照常运行,并会把服务器宕机丢失的数据信息复制恢复到其他可用节点上。
口使用简单:共需一条命令就可以下载文件,然后很快就能搭建一一个站内搜索引擎。
Elasticsearch应用场景
大型分布式日志分析系统ELK elasticsearch(存储日志)+logstash(收集日志)+kibana(展示数据)
大型电商商品搜索系统、网盘搜索引擎等。
Elasticsearch存储结构
Elasticsearch是文件存储,Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据:
{ "name" : "yushengjun", "sex" : 0, "age" : 25 } |
关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)
Elasticsearch ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields)
Linux环境安装Elasticsearch
- 安装JDK环境变量
export JAVA_HOME=/usr/local/jdk1.8.0_181 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
source /etc/profile |
- 下载elasticsearch安装包
下载elasticsearch安装包
官方文档https://www.elastic.co/downloads/elasticsearch
注意:linux安装内存建议1g内存以上
- 上传elasticsearch安装包
- 解压elasticsearch
tar -zxvf elasticsearch-6.4.3.tar.gz |
- 修改elasticsearch.yml
network.host: 192.168.212.151
http.port: 9200
- 启动elasticsearch报错
Cd /usr/local/elasticsearch-6.4.3/bin
./elasticsearch
can not run elasticsearch as root 解决方案: 因为安全问题elasticsearch
不让用root用户直接运行,所以要创建新用户 第一步:liunx创建新用户 adduser XXX 然后给创建的用户加密码 passwd XXX 输入两次密码。 第二步:切换刚才创建的用户 su XXX 然后执行elasticsearch 会显示Permission denied 权限不足。 第三步:给新建的XXX赋权限,chmod 777 * 这个不行,因为这个用户本身就没有权限,肯定自己不能给自己付权限。所以要用root用户登录付权限。 第四步:root给XXX赋权限,chown -R XXX /你的elasticsearch安装目录。 然后执行成功。
创建一个分组 groupadd esmayikt useradd esyushengjun -g esmayikt -p 123456 chown -R esyushengjun:esmayikt elasticsearch-6.4.3 su esyushengjun 切换用户
继续报错 bootstrap checks failed max virtual memory areas vm.max_map_count [65530] is
vi /etc/sysctl.conf vm.max_map_count=655360 sysctl p
max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536] vi /etc/security/limits.conf * soft nofile 65536 * hard nofile 131072 * soft nproc 2048 * hard nproc 4096
重启服务器即可
|
- 访问elasticsearch
关闭防火墙 systemctl stop firewalld.service
http://192.168.212.151:9200
9300与9200区别
9300端口: ES节点之间通讯使用
9200端口: ES节点 和 外部 通讯使用
Linux环境安装Kibana
Kibana是一个开源的分析和可视化平台,设计用于和Elasticsearch一起工作。
你用Kibana来搜索,查看,并和存储在Elasticsearch索引中的数据进行交互。
你可以轻松地执行高级数据分析,并且以各种图标、表格和地图的形式可视化数据。
Kibana使得理解大量数据变得很容易。它简单的、基于浏览器的界面使你能够快速创建和共享动态仪表板,实时显示Elasticsearch查询的变化。
Kibana环境安装
Tar -zxvf kibana-6.4.3-linux-x86_64.tar.gz
vim config/kibana.yml
# 将默认配置改成如下:
server.port: 5601
server.host: "192.168.212.151"
elasticsearch.url: "http:// 192.168.212.151:9200"
启动Kibana
./bin/kibana
http://192.168.212.179:5601/app/kibana
Kibana实现增删改查
###创建索引
PUT /mymayikt
####查询索引 GET /mymayikt
####添加文档 /索引名称/类型/id PUT /mymayikt/user/1 { "name":"yushengjun", "sex":0, "age":22 }
###查询文档 GET /mymayikt/user/1
###删除索引 DELETE /mymayikt |
Elasticsearch版本控制
1.为什么要进行版本控制
为了保证数据再多线程操作下的准确性
2.悲观锁和乐观锁
悲观锁:假设会发生并发冲突,屏蔽一切可能违反数据准确性的操作
悲观锁:假设不会发生并发冲突,只在提交操作是检查是否违反数据完整性。
3.内部版本控制和外部版本控制
内部版本控制:_version自增长,修改数据后,_version会自动的加1
外部版本控制:为了保持_version与外部版本控制的数值一致
使用version_type=external检查数据当前的version值是否小于请求中的version值
PUT /mymayikt/user/1?version=2 { "name":"yushengjun", "sex":0, "age":22 } |
9300与9200区别
9300与9200区别
9300端口: ES节点之间通讯使用
9200端口: ES节点 和 外部 通讯使用
9300是TCP协议端口号,ES集群之间通讯端口号
9200端口号,暴露ES RESTful接口端口号
倒排索引
正向索引
正排表是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为该文档建立一个新的索引块,挂接在原来索引文件的后面。若是有文档删除,则直接找到该文档号文档对应的索引信息,将其直接删除。但是在查询的时候需对所有的文档进行扫描以确保没有遗漏,这样就使得检索时间大大延长,检索效率低下。
尽管正排表的工作原理非常的简单,但是由于其检索效率太低,除非在特定情况下,否则实用性价值不大。
倒排索引
倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。
由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。
正排索引是从文档到关键字的映射(已知文档求关键字),倒排索引是从关键字到文档的映射(已知关键字求文档)。
文档内容:
序号 |
文档内容 |
1 |
小俊是一家科技公司创始人,开的汽车是奥迪a8l,加速爽。 |
2 |
小薇是一家科技公司的前台,开的汽车是保时捷911 |
3 |
小红买了小薇的保时捷911,加速爽。 |
4 |
小明是一家科技公司开发主管,开的汽车是奥迪a6l,加速爽。 |
5 |
小军是一家科技公司开发,开的汽车是比亚迪速锐,加速有点慢 |
倒排索引会对以上文档内容进行关键词分词,可以使用关键次直接定位到文档内容。
单词ID |
单词 |
倒排列表docId |
1 |
小 |
1,2,3,4,5 |
2 |
一家 |
1,2,4,5 |
3 |
科技公司 |
1,2,4,5 |
4 |
开发 |
4,5 |
5 |
汽车 |
1,2,4,5 |
6 |
奥迪 |
1,4 |
7 |
加速爽 |
1,3,4 |
8 |
保时捷 |
2,3 |
9 |
保时捷911 |
2 |
10 |
比亚迪 |
5 |
高级查询
根据id进行查询
GET /mymayikt/user/12
查询当前所有类型的文档
GET /mymayikt/user/_search
根据多个ID批量查询
查询多个id分别为1、2
GET /mymayikt/user/_mget
{
"ids":["1","2"]
}
复杂条件查询
查询年龄为年龄21岁
GET /mymayikt/user/_search?q=age:21
查询年龄30岁-60岁之间
GET /mymayikt/user/_search?q=age[30 TO 60]
注意:TO 一定要大写
查询年龄30岁-60岁之间 并且年龄降序、从0条数据到第1条数据
GET /mymayikt/user/_search?q=age[30 TO 60]&sort=age:desc&from=0&size=1
查询年龄30岁-60岁之间 并且年龄降序、从0条数据到第1条数据,展示name和age字段
GET /mymayikt/user/_search?q=age[30 TO 60]&sort=age:desc&from=0&size=1
&_source=name,age
Dsl语言查询与过滤
什么是DSL语言
es中的查询请求有两种方式,一种是简易版的查询,另外一种是使用JSON完整的请求体,叫做结构化查询(DSL)。
由于DSL查询更为直观也更为简易,所以大都使用这种方式。
DSL查询是POST过去一个json,由于post的请求是json格式的,所以存在很多灵活性,也有很多形式。
根据名称精确查询姓名
GET mymayikt/user/_search { "query": { "term": { "name": "xiaoming" }
}
} |
##term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
根据汽车名称模糊查询
GET /mymayikt/user/_search { "from": 0, "size": 2, "query": { "match": {
"car": "奥迪" } } } |
####match查询相当于模糊匹配,只包含其中一部分关键词就行
Term与Match区别
Term查询不会对字段进行分词查询,会采用精确匹配。
Match会根据该字段的分词器,进行分词查询。
使用filter过滤年龄
GET /mymayikt/user/_search { "query": { "bool": { "must": [{ "match_all": {} }], "filter": { "range": { "age": { "gt": 21, "lte": 51 } }
}
}
}, "from": 0, "size": 10, "_source": ["name", "age"]
} |
分词器
什么是分词器
因为Elasticsearch中默认的标准分词器分词器对中文分词不是很友好,会将中文词语拆分成一个一个中文的汉子。因此引入中文分词器-es-ik插件
演示传统分词器
http://192.168.212.181:9200/_analyze { "analyzer": "standard", "text": "奥迪a4l" }
{ "tokens": [ { "token": "奥", "start_offset": 0, "end_offset": 1, "type": "<IDEOGRAPHIC>", "position": 0 }, { "token": "迪", "start_offset": 1, "end_offset": 2, "type": "<IDEOGRAPHIC>", "position": 1 }, { "token": "a4l", "start_offset": 2, "end_offset": 5, "type": "<ALPHANUM>", "position": 2 } ] }
|
下载地址: https://github.com/medcl/elasticsearch-analysis-ik/releases
注意: es-ik分词插件版本一定要和es安装的版本对应
第一步:下载es的IK插件(资料中有)命名改为ik插件 第二步: 上传到/usr/local/elasticsearch-6.4.3/plugins 第三步: 重启elasticsearch即可
|
http://192.168.212.181:9200/_analyze { "analyzer": "ik_smart", "text": "奥迪" }
{ "tokens": [ { "token": "奥迪", "start_offset": 0, "end_offset": 2, "type": "CN_WORD", "position": 0 }, { "token": "a4l", "start_offset": 2, "end_offset": 5, "type": "LETTER", "position": 1 } ] }
|
自定义扩展字典
在/usr/local/elasticsearch-6.4.3/plugins/ik/config目录下
vi custom/new_word.dic
老铁
王者荣耀
洪荒之力
共有产权房
一带一路
vi IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict">custom/new_word.dic</entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords"></entry> <!--用户可以在这里配置远程扩展字典 --> <!-- <entry key="remote_ext_dict">words_location</entry> --> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties> |
文档映射
已经把ElasticSearch的核心概念和关系数据库做了一个对比,索引(index)相当于数据库,类型(type)相当于数据表,映射(Mapping)相当于数据表的表结构。ElasticSearch中的映射(Mapping)用来定义一个文档,可以定义所包含的字段以及字段的类型、分词器及属性等等。
文档映射就是给文档中的字段指定字段类型、分词器。
使用GET /mymayikt/user/_mapping
映射的分类
动态映射
我们知道,在关系数据库中,需要事先创建数据库,然后在该数据库实例下创建数据表,然后才能在该数据表中插入数据。而ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。
静态映射
在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。
ES类型支持
基本类型
符串:string,string类型包含 text 和 keyword。
text:该类型被用来索引长文本,在创建索引前会将这些文本进行分词,转化为词的组合,建立索引;允许es来检索这些词,text类型不能用来排序和聚合。
keyword:该类型不需要进行分词,可以被用来检索过滤、排序和聚合,keyword类型自读那只能用本身来进行检索(不可用text分词后的模糊检索)。
注意: keyword类型不能分词,Text类型可以分词查询
数指型:long、integer、short、byte、double、float
日期型:date
布尔型:boolean
二进制型:binary
数组类型(Array datatype)
复杂类型
地理位置类型(Geo datatypes)
地理坐标类型(Geo-point datatype):geo_point 用于经纬度坐标
地理形状类型(Geo-Shape datatype):geo_shape 用于类似于多边形的复杂形状
特定类型(Specialised datatypes)
Pv4 类型(IPv4 datatype):ip 用于IPv4 地址
Completion 类型(Completion datatype):completion 提供自动补全建议
Token count 类型(Token count datatype):token_count 用于统计做子标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少
mapper-murmur3 类型:通过插件,可以通过_murmur3_来计算index的哈希值
附加类型(Attachment datatype):采用mapper-attachments插件,可支持_attachments_索引,例如 Microsoft office 格式,Open Documnet 格式, ePub,HTML等
Analyzer 索引分词器,索引创建的时候使用的分词器 比如ik_smart
Search_analyzer 搜索字段的值时,指定的分词器
创建文档类型并且指定类型
POST /mymayikt/_mapping/user { "user":{ "properties":{ "age":{ "type":"integer" }, "sex":{ "type":"integer" }, "name":{ "type":"text", "analyzer":"ik_smart", "search_analyzer":"ik_smart" }, "car":{ "type":"keyword"
} } }
} |
http://192.168.212.183:9200/_cat/nodes?pretty
请求地址/索引/_settings
http://192.168.212.181:9200/mymayikt/_settings
ES集群管理
ES是如何解决高并发
ES是一个分布式全文检索框架,隐藏了复杂的处理机制,核心内容 分片机制、集群发现、分片负载均衡请求路由。
ES基本概念名词
Cluster
代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。
Shards
代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
replicas
代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
Recovery
代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
ES为什么要实现集群
ES集群中索引可能由多个分片构成,并且每个分片可以拥有多个副本。通过将一个单独的索引分为多个分片,我们可以处理不能在一个单一的服务器上面运行的大型索引,简单的说就是索引的大小过大,导致效率问题。不能运行的原因可能是内存也可能是存储。由于每个分片可以有多个副本,通过将副本分配到多个服务器,可以提高查询的负载能力。
GET _cat/health
ES集群核心原理分析:
数据存储。
1、每个索引会被分成多个分片shards进行存储,默认创建索引是分配5个分片进行存储。
每个分片都会分布式部署在多个不同的节点上进行部署,该分片成为primary shards。
注意:索引的主分片primary shards定义好后,后面不能做修改。
2、为了实现高可用数据的高可用,主分片可以有对应的备分片replics shards,replic shards分片承载了负责容错、以及请求的负载均衡。
注意: 每一个主分片为了实现高可用,都会有自己对应的备分片,主分片对应的备分片不能存放同一台服务器上。,主分片primary shards可以和其他replics shards存放在同一个node节点上。
ES集群环境搭建
服务器环境
准备三台服务器集群
服务器名称 |
IP地址 |
node-1 |
192.168.212.182 |
node-2 |
192.168.212.183 |
node-3 |
192.168.212.184 |
服务集群配置
vi elasticsearch.yml cluster.name: myes ###保证三台服务器节点集群名称相同 node.name: node-1 #### 每个节点名称不一样 其他两台为node-1 ,node-2 network.host: 192.168.212.180 #### 实际服务器ip地址 discovery.zen.ping.unicast.hosts: ["192.168.212.184", "192.168.212.185","192.168.212.186"]##多个服务集群ip discovery.zen.minimum_master_nodes: 1
|
关闭防火墙 systemctl stop firewalld.service
默认底层开启9300 集群
验证集群效果
http://192.168.212.185:9200/_cat/nodes?pretty
注意克隆data文件会导致数据不同步
报该错误解决办法
failed to send join request to master
因为克隆导致data文件也克隆呢,直接清除每台服务器data文件。
*号表示为master节点
SpringBoot整合ES
SpringBoot整合ES实现网盘搜索引擎
项目环境搭建
数据结构分析
POST /clouddisk/_mapping/disk { "disk": { "properties": { "baiduaddres": { "type": "keyword" }, "browsetimes": { "type": "long" }, "collectiontime": { "type": "date" }, "describe": { "type": "text", "analyzer": "ik_smart" }, "filesize": { "type": "float" }, "name": { "type": "text", "analyzer": "ik_smart" }, "sharpeople": { "type": "keyword" }, "shartime": { "type": "date" }, "source": { "type": "keyword" } } }
} |
Maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
|
application信息
spring: data: elasticsearch: #多实例集群扩展时需要配置以下两个参数
####集群名称 cluster-name: myes ####地址 cluster-nodes: 192.168.212.180:9300
|
三层结构
实体类层
@Data @Document(indexName = "clouddisk", type = "disk") public class CloudDiskEntity { /** * { "name": "2018史上最全SpringBoot", "source": "百度云盘", "describe": * "2018史上最全的SpringBoot课程", "shartime": "2018-10-10", "browsetimes": 10, * "filesize": 4.35, "sharpeople": "余胜军644", "collectiontime": "2018-11-24", * "addres": "address" * * } * */ @Id private String id; // 名称 private String name; // 来源 private String source; // 描述 private String describe; // 分享时间 private Date shartime; // 浏览次数 private Long browsetimes; // 文件大小 private Double filesize; // 分享人 private String sharpeople; // 收录时间 private String collectiontime; // 地址 private String baiduaddres;
} |
注解说明:
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
@Document 作用在类,标记实体类为文档对象,一般有两个属性
indexName:对应索引库名称
type:对应在索引库中的类型
shards:分片数量,默认5
replicas:副本数量,默认1
@Id 作用在成员变量,标记一个字段作为id主键
@Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等
text:存储数据时候,会自动分词,并生成索引
keyword:存储数据时候,不会分词建立索引
Numerical:数值类型,分两类
基本数据类型:long、interger、short、byte、double、float、half_float
浮点数的高精度类型:scaled_float
需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
Date:日期类型
elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称,这里的ik_max_word即使用ik分词器
repository层
public interface CloudDiskDao extends ElasticsearchRepository<CloudDiskEntity, String> {
} |
控制层
@RestController public class CloudDiskController { @Autowired private CloudDiskDao cloudDiskDao;
// springboot 整合 es 查询 // 根据id查询文档信息 @RequestMapping("/findById/{id}") public Optional<CloudDiskEntity> findById(@PathVariable String id) { return cloudDiskDao.findById(id);
}
// 实现分页查询 @RequestMapping("/search") public List<CloudDiskEntity> search(String name, String describe, @PageableDefault(page = 0, value = 2) Pageable pageable) { // 1.创建查询对象 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); if (!StringUtils.isEmpty(name)) { MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", name); boolQuery.must(matchQuery); } if (!StringUtils.isEmpty(describe)) { MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("describe", describe); boolQuery.must(matchQuery); } // 2.调用查询接口 Iterable<CloudDiskEntity> search = cloudDiskDao.search(boolQuery, pageable); // 3.将迭代器转换为集合 return Lists.newArrayList(search); } }
|
@Controller public class SearchController { @Autowired private CloudDiskDao cloudDiskDao;
@RequestMapping("/search") public String search(String keyword, String describe, @PageableDefault(page = 0, value = 5) Pageable pageable, HttpServletRequest req) { Long startTime = System.currentTimeMillis(); // 1.创建查询对象 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); if (!StringUtils.isEmpty(keyword)) { MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword); boolQuery.must(matchQuery); } if (!StringUtils.isEmpty(describe)) { MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("describe", describe); boolQuery.must(matchQuery); } // 2.调用查询接口 Page<CloudDiskEntity> page = cloudDiskDao.search(boolQuery, pageable);
req.setAttribute("page", page); // 记录总数 req.setAttribute("total", page.getTotalElements()); req.setAttribute("keyword", keyword); // 计算分页总数 int totalPage = (int) ((page.getTotalElements() - 1) / pageable.getPageSize() + 1); // 分页总数 req.setAttribute("totalPage", totalPage); Long emdTime = System.currentTimeMillis(); req.setAttribute("time", emdTime - startTime); return "search"; }
} |
访问链接
http://127.0.0.1:8080/findById/J9LfRmcBJUiOjXccw4Zp