ES
ES(Elasticsearch)
核心概念
- ES -> 数据库
- 索引index -> 表
- 类型type -> 表逻辑类型(早期版本中存在)
- 文档document -> 行(记录)
- 字段fields -> 列
- 映射mapping -> 表结构定义
- 近实时NRT(near real time)
集群相关
- 节点node(服务器)
- shard replica(数据分片与备份)
- 分片
将索引库拆分为多份,分别放在不同的节点上。目的为了水平扩展,提高吞吐量 - 备份:每个shard的备份
倒排索引
倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和包含该属性值的各个记录地址。由于不是根据记录来确定属性,而是根据属性来确定记录的位置,所以称之为倒排索引。
安装
- 下载解压
tar -zxvf elasticsearch-...
- ES目录
- bin:可执行文件,运行es的命令就在目录下,包含了一些脚本等
- config:配置文件目录
- jdk:java环境
- lib:依赖的jar,类库
- logs:日志文件
- modules:es相关的模块
- plugins:可以自己开发的插件
- data:自身不携带,作为索引目录
- 核心配置文件(elasticsearch.yml)
# 集群名称,默认是elasticsearch
cluster.name: elasticsearch
# es节点名称
node.name: es-node0
# data数据保存地址
path.data: /usr/local/es/data
# 日志数据保存地址
path.logs: /usr/local/es/logs
# 绑定es网络IP
network.host: 0.0.0.0
# 端口号
http.port: 9200
# 集群节点
cluster.initial_master_nodes: ["es-node0"]
- 添加用户(es不允许使用root操作es)
useradd esuser
chown -R esuser:esuser /usr/local/es
- 启动
./elasticsearch
- 测试:访问服务器地址+端口号
基本操作
索引操作
- 查看集群健康
GET /_cluster/health
- 创建索引
PUT /index
{
"settings": {
"index": {
"number_of_shards: "2",
"number_of_replicas": "0"
}
}
}
- 查看索引
GET _cat/indices?v
- 删除索引
DELETE /index
索引的mappings映射
- 索引分词概念
index:默认true,设置为false的话,那么这个字段不会被索引 - 创建索引的同时创建mappings
PUT /index
{
"mappings": {
"properties: {
"realname": {
"type": "text",
"index":true
},
"username": {
"type":"keyword",
"index":false
}
}
}
}
- 查看分词效果
GET /index_mapping/_analyze
{
"field":"realname",
"text":"java is good"
}
- 为已存在的索引创建或创建mappings
# 注:某个属性一旦被建立就不能修改了,但是可以新增额外属性
POST /index/_mapping
{
"properties": {
"id":{
"type":""long
},
"age":{
"type":"integer"
}
}
}
- 主要数据类型
- text,keyword
- long,integer,short,byte
- double,float
- boolean
- date
- object
- 数组不能混,类型一致
- 字符串
- text:文字类需要被分词倒排索引的内容
- keyword:不会被分词,不会被倒排索引,直接匹配搜索
添加文档
- 添加文档数据
POST /index/_doc/1 ->{索引名}/_doc/{索引id}(es中的id,非记录id)
{
"id":1001,
"name":"es",
"desc":"elasticsearch"
}
- 如果索引没有手动建立mappings,那么插入文档数据时会根据文档类型自动设置属性类型,即动态映射
删除文档
文档删除不是立即删除,文档还是保存在磁盘上,索引增长越来越多才会把那些曾经标识过删除的进行清理,从磁盘上移出去
DELETE index/_doc/1
修改文档
- 局部
POST /index/_doc/1/_update
{
"doc": {
"name":"update"
}
}
- 全量替换
PUT /index/_doc/1
{
"id":100,
"name":"swap",
"desc":"swap update"
}
- 每次修改后,version会更改
文档基本查询
- 查询文档
GET /index/_doc/1
GET /index/_doc/_search
- 元数据
- _index:文档数据所属索引
- _type:文档类型
- _id:文档数据的唯一标识,可以自动生成或者手动指定
- _score:查询相关度,是否契合用户匹配,分数越高用户的搜索体验越高
- _version:版本号
- _source:文档数据(json格式)
- 定制结果集
GET /index/_doc/1?_source=id,name
GET /index/_doc/_search?_source=id,name
- 判断文档是否存在
HEAD /index/_doc/1
- 文档乐观锁
POST /index/_doc/{_id}/_update?if_seq_no={数值}&if_primary_term={数值}
* 版本元数据
* if_seq_no:文档版本号,作用同_version
* if_primary_term:文档所在位置
分词
把文本转为一个一个的单词,称之为分词(analysis)。es默认只对英文语句做分词,中文不支持,每个中文都会被拆分为独立的个体
- es内置分词
- standard:默认分词,单词会被拆分,大写会被转为小写
- simple:按照非字母分词,大写转为小写
- whitespace:按照空格分词,忽略大小写
- stop:去除无意义单词,比如
the/a/an/is
- keyword:不做分词。将整个文本作为一个单独的关键词
- ik中文分词
- 自定义中文词库
- 在{es}/plugins/ik/config下,创建
vim custom.dic
- 添加内容
- 配置自定义扩展词典
<entry key="ext_dic">custom.dic</entry>
- 重启
- 在{es}/plugins/ik/config下,创建
dsl搜索
入门语法
- QueryString(查询[字段]包含[内容]的文档)
GET /index/_doc/_search?q=desc:test
GET /index/_doc/_search?q=nickname:test&q=age:25
GET /index/_doc/_search?q=nickname:this is test
- dsl基本语法
- QueryString用的比较少,参数复杂时难以构建
- 语法格式为一个json object,内容都是key-value键值对,json可以嵌套
- key可以是一些es的关键字,也可以是某个field字段
查询所有
- match_all
GET /index/_doc/_search
或者
POST /index/_doc/_search
{
"query":{
"match_all":{}
},
"_source":["id","nickname","age"]
}
- 分页查询
可以添加from和size
"from":5,
"size":10
term与match
- term精确搜索(将搜索内容作为一整个关键词搜索)
POST /index/_doc/_search
{
"query":{
"term":{
"desc":"test"
}
}
}
# 多个词语匹配搜索
POST /index/_doc/_search
{
"query":{
"terms":{
"desc":["test1","test2","test3"]
}
}
}
- match分词搜索(先将搜索内容分词,即全文检索,之后再查询)
POST /index/_doc/_search
{
"query":{
"match":{
""desc:"test"
}
}
}
match_phrase
- 短语匹配
- match分词后只要有匹配就返回,match_phrase分词结果必须在text字段分词中都包含,且顺序必须相同,且连续
# slop允许词语跳过的数量
POST /index/_doc/_search
{
"query":{
"match_phrase":{
"desc":{
"query":"test1 test testfor",
"slop":2
}
}
}
}
match(operator)/ids
- match扩展
- operator
# or搜索内容分词后,只要存在一个词语匹配就展示结果(operator默认为or)
# and搜索内容分词后,都要满足词语匹配
POST /index/_doc/_search
{
"query":{
"match":{
"desc":{
"query":"test",
"operator":"or"
}
}
}
}
# minimum_should_match最低匹配精度,至少有[分词后的词语个数]*百分百,
得出值取整;也可以设置具体的值,表示个数
{
"query":{
"match":{
"desc":{
"query":"test",
"minimum_should_match":"60%"
}
}
}
}
- 根据文档主键ids搜索
POST /index/_doc/_search
{
"query":{
"ids":{
"type":"_doc",
"values":["1001","1002","1003"]
}
}
}
multi_match/boost
- multi_match(满足使用match在多个字段中进行查询的需求)
POST /index/_dodc/_search
{
"query":{
"multi_match":{
"query":"perter test a test",
"fields":["desc","nickname"]
}
}
}
- boost(权重,为某个字段设置权重,权重越高文档相关性得分越高)
# nickname^10代表搜索提高10倍相关性
{
"query":{
"multi_match":{
"query":"perter test a test",
"fields":["desc","nickname^10"]
}
}
}
布尔查询
- 可以组合多重查询
- must查询必须匹配搜索条件
- should查询匹配满足1个一上条件
- must_not不匹配搜索条件
- 示例
POST /index/_doc/_search
{
"query"{
"bool":{
"must":[
"multi_match":{
"query":"test",
"fields":["desc","nickname"]
},
"term":{
"sex":1
}
]
}
}
}
POST /index/_doc/_search
{
"query"{
"bool":{
"should":[
"multi_match":{
"query":"test",
"fields":["desc","nickname"]
},
"term":{
"sex":1
}
]
}
}
}
- 为指定语句加权
POST /index/_doc/_search
{
"query"{
"bool":{
"should":[
"multi_match":{
"query":"test",
"fields":["desc","nickname"],
"boost":18
},
"term":{
"sex":1,
"boost":2
}
]
}
}
过滤器
- 对搜索出来的结果进行数据过滤。不会到es中去搜,不会计算文档的相关分数,所以过滤的性能比较高
- post_filter是一个顶层元素,只会对搜索结果进行过滤,不会计算数据的匹配相关度性分数,不会根据分数排序
- query会计算分数,也会按照分数去排序
- query根据用户搜索条件检索匹配记录;post_filter用于查询后对结果数据的筛选
- gte:大于等于
- lte:小于等于
- gt:大于
- lt:小于
- 示例
POST /index/_doc/_search
{
"query":{
"match":{
"desc":"test"
}
},
"post_filter":{
"range":{
"money":{
"gt":60.
"lt":1000
}
}
}
}
排序
POST /index/_doc/_search
{
"query":{
"match":{
"desc":"test"
}
},
"sort":[
{
"age":"desc"
},
{
"money":"asc"
}
]
}
# 对文本排序
# 由于文本会被分词,这时排序需要为字段附加一个额外属性
# 创建索引
POST /index/_mapping
{
"properties":{
"id":{
"type":"long"
},
"nickname":{
"type":"text",
"analyzer":"ik_max_word",
"fields":{
"keyword":{
"type":"keyword"
}
}
}
}
}
# 文本排序
{
"sort":[
{
"nickname.keyword":"desc"
}
]
}
高亮
POST /index/_doc/_search
{
"query":{
"match":{
"desc":"test"
}
},
"highlight":{
"pre_tags":["<tag>"],
"post_tags":["</tag>"],
"fields":{
"desc":{}
}
}
}
prefix&fuzzy&wildcard
- prefix(根据前缀查询)
POST /index/_doc/_search
{
"query":{
"prefix":{
"desc":"tes"
}
}
}
- fuzzy(模糊搜索-用户进行搜索时打错字,搜索引擎自动纠正)
POST /index/_doc/_search
{
"query":{
"fuzzy":{
"desc":"tev"
}
}
}
# 多字段
{
"query":{
"multi_match":{
"fields":["desc","nickname"],
"query":"test",
"fuzziness":"AUTO"
}
}
}
{
"query":{
"multi_match":{
"fields":["desc","nickname"],
"query":"test",
"fuzziness":"1"
}
}
}
- wildcard(占位符查询)
- ?:1个字符
- *:1个或多个字符
- 示例
POST /index/_doc/_search
{
"query":{
"wildcard":{
"desc":"*t?"
}
}
}
其它
深度分页
- 深度分页其实就是搜索的深浅度,搜索太深会造成性能问题,消耗内存和占用CPU,es默认不支持超过1万条数据以上的分页查询
- 提升搜索量
# 通过设置index.max_result_window来突破10000数据
GET /index/_settings
PUT /index/_settings
{
"index.max_result_window":"20000"
}
- 滚动搜索
scroll
- 先查询出一些数据,然后接着依次往下查询
- 每次搜索都是基于一个历史的数据快照,查询期间如果数据变更,搜索的还是快照中的内容
- 示例
# scroll=1m,相当于是一个session会话时间,搜索保持的上下文时间为1分钟
POST /index/_search?scroll=1m
{
"query":{
"match_all":{}
},
"sort":["_doc"],
"size":5
}
POST /_search/scroll
{
"scroll":"1m",
"scroll_id":"上一次scroll_id"
}
批量操作bulk
- bulk操作和普通请求格式有区别,不需要格式化json
# 批量操作类型,新增、删除或修改
# \n是每行结尾必须填写的一个规范包括最后一行
{action:{metadata}}\n
# 请求body,增加和修改操作需要,删除则不需要
{request body}\n
{action:{metadata}}\n
{request body}\n
...
- 批量操作类型action
- create(如果文档不存在就创建,存在则报错,不会影响其它操作)
- index(创建一个新文档或替换一个现有的文档)
- update(部分更新一个文档)
- delete(删除一个文档)
- metadata中需要指定要操作的文档的_index,_type和_id;_index,_type也可以在url中指定
- 示例
# create新增文档数据,在metadata中指定index和type
POST /_bulk
{"create":{"_index":"index","_type":"_doc","_id":"2001"}}
{"id":"2001","nickname":"name2001"}
{"create":{"_index":"index","_type":"_doc","_id":"2002"}}
{"id":"2002","nickname":"name2002"}
{"create":{"_index":"index","_type":"_doc","_id":"2003"}}
{"id":"2003","nickname":"name2003"}
# create 在url中指定index和type
POST /index/_doc/_bulk
{"create":{"_id":"2003"}}
{"id":"2003","nickname":"name2003"}
{"create":{"_id":"2004"}}
{"id":"2004","nickname":"name2004"}
{"create":{"_id":"2005"}}
{"id":"2005","nickname":"name2005"}
# index创建,已存在则覆盖,不存在则新增
POST /index/_doc/_bulk
{"index":{"_id":"2004"}}
{"id":"2004","nickname":"index2004"}
{"index":{"_id":"2006"}}
{"id":"2006","nickname":"index2006"}
# update更新部分文档数据
POST /index/_doc/_bulk
{"update":{"_id":"2004"}}
{"doc":{"id":"2021"}}
{"update":{"_id":"2006"}}
{"doc":{"nickname":"test"}}
# delete批量删除
POST /index/_doc/_bulk
{"delete":{"_id":"2004"}}
{"delete":{"_id":"2005"}}
# 综合批量操作
POST /index/_doc/_bulk
{"create":{"_id":"2005"}}
{"id":"2005","nickname":"name2005"}
{"update":{"_id":"2004"}}
{"doc":{"id":"2021"}}
{"delete":{"_id":"2004"}}
- 批量查询
mget
ES集群
- 集群不仅可以实现高可用,也能实现海量数据存储的横向扩展
- 分片机制
- 每个索引可以被分片
- 每个主分片都包含索引的数据
- 副本分片是主分片的备份
- 同一个分片的主与副本是不会放在同一个服务器里的,因为一旦宕机,这个分片就没了
搭建ES集群
- 配置集群
# 修改配置elasticsearch.yml
# 配置集群名称,保证每个节点相同,如此就能处于一个集群之内
cluster.name: es-cluster-test
# 每个节点名称必须不一样
node.name: es-node1
# http端口(默认)
http.port: 9200
# 主节点,用于管理整个集群,负责创建索引或删除索引,管理其它非master节点
node.master: true
# 数据节点,用于对文档数据的增删改查
node.data: true
# 集群列表
discovery.seed_hosts: ["192.168.1.1","192.168.1.2","192.168.1.3"]
# 启动的时候使用一个master节点
cluster.initial_master_nodes: ["192.168.1.1"]
ES脑裂
- 脑裂:如果发生网络中断或者服务器宕机,那么集群会有可能被划分为两个部分,各自有自己的master来管理,即脑裂
- 解决方案
- master节点要经过多个master节点共同选举后才能成为新的主节点
- 解决原理:半数以上节点同意选举,节点才能成为新的master
discovery.zen.minimum_master_nodes=(N/2)+1
(N为集群中的master节点数量)- ES 7.x中minimum_master_nodes这个参数已经被移除,由es自己管理,避免了脑裂现象,选举也会非常快
ES整合spring boot
- 创建工程,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
- 配置yml
spring:
data:
elasticsearch:
cluster-name: es-cluster
cluster-node: 192.168.1.1:9300
- netty issue
@Configuration
public class ESConfig {
/*** 解决netty引起的issue */
@PostConstruct
void init() {
System.setProperty("es.set.netty.runtime.available.processors",
"false");
}
}
logstash
- logstash是一个elastic技术栈中的一个技术。它是一个数据采集引擎,可以从数据库中采集数据到es中
- 配置实例
input {
jdbc {
# 设置数据库url及数据库名称
jdbc_connection_string => "jdbc:mysql://192.168.1.1:3306/test"
# 用户名和密码
jdbc_user => "root"
# jdbc_password => "root"
# 数据库驱动位置
jdbc_driver_libraay => "/usr/local/logstash-6.4.3/sync/mysql-connector-java-5.1.41.jar"
# 驱动类名称
jdbc_driver_class => "com.mysql.jdbc.Driver"
# 开启分页
jdbc_paging_enabled => "true"
# 分页每页数量
jdbc_page_size => "10000"
# 执行的sql路径
statement_filepath => "/usr/local/logstash-6.4.3/sync/test.sql"
# 设置定时任务,每分钟执行一次
schedule => "* * * * *"
# 索引类型
type => "_doc"
# 开启记录上次最终的结果
use_column_value => true
# 记录上一次追踪的结果
last_run_metadata_path => "/url/local/logstash-6.4.2/sync/track_time"
# 追踪column名称
tracking_column => "update_time"
# 追踪column类型
tracking_column_type => "timestamp"
# 不清除追踪的结果
clean_run => false
# 数据库字段名称大写转小写
lowercase_column_names => false
}
output {
elasticsearch {
# es地址
hosts => ["192.168.1.1:9200"]
# 同步索引名称
index => "test"
# 设置_docId和数据id相同
document_id => "%{id}"
}
# 日志输出
stdout {
codec => json_lines
}
}
}
- 自定义logstash模板
# 新增如下配置
# 定义模板名称
template_name => "ik"
# 模板所在位置
template => "/usr/local/logstash-6.4.3/sync/logstash-ik.json"
# 重写模板
template_overwrit => true
# 关闭logstash自动管理模板功能
manage_template => false