ElasticSearch是什么
todo
部署
压缩包部署
略,大概就是copy 相同的es文件 修改elasticsearch.yml内的node_name
docker-compose部署
version: '3'
services:
elasticsearch:
##image:tag image为hub.docker.com上镜像名称 tag为版本号 不写tag时默认拉取最新镜像
image: elasticsearch:7.9.3
container_name: es
##加载的环境变量
environment:
##集群名称
- cluster.name=my-application
- node.name=node
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "cluster.initial_master_nodes=node,node1,node2" ##集群初始化master节点
- "discovery.seed_hosts=elasticsearch,elasticsearch1,elasticsearch2"
ulimits:
memlock:
soft: -1
hard: -1
##宿主机与容器的端口映射,端口需要暴露出来
ports:
- 9200:9200
- 9300:9300
networks:
- esnet
##数据卷映射可以多个映射 左侧es_data为别名 右侧为容器内地址
## es_data具体通过 docker volume inspect ${user}_es_data
## ${user}为创建容器时账户 eg:docker volume inspect root_es_data
volumes:
- es_data:/usr/share/elasticsearch/data
- es_plugin:/usr/share/elasticsearch/plugins
elasticsearch1:
image: elasticsearch:7.9.3
container_name: es1
environment:
- cluster.name=my-application
- node.name=node1
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "cluster.initial_master_nodes=node,node1,node2"
- "discovery.seed_hosts=elasticsearch,elasticsearch1,elasticsearch2"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9201:9200
- 9301:9300
networks:
- esnet
volumes:
- es1_data:/usr/share/elasticsearch/data
- es_plugin:/usr/share/elasticsearch/plugins
elasticsearch2:
image: elasticsearch:7.9.3
container_name: es2
environment:
- cluster.name=my-application
- node.name=node2
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "cluster.initial_master_nodes=node,node1,node2"
- "discovery.seed_hosts=elasticsearch,elasticsearch1,elasticsearch2"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9202:9200
- 9302:9300
networks:
- esnet
volumes:
- es2_data:/usr/share/elasticsearch/data
- es_plugin:/usr/share/elasticsearch/plugins
kibana:
image: kibana:7.9.3
container_name: kibana
environment:
- ELASTICSEARCH_URL=http://elasticsearch:9200
ports:
- "5601:5601"
networks:
- esnet
volumes:
es_data:
driver: local
es1_data:
driver: local
es2_data:
driver: local
es_plugin:
driver: local
networks: ##容器之间通信网络 局域网的概念
esnet:
ElasticSearch架构原理
基本概念
索引
- 将同一类数据放到一起的集合 相当于传统关系型数据库中的库
类型
- 7.0已废弃 默认type为_doc 8.0版本正式剔除 相当于传统关系型数据库中的表
文档/doc
- 即一行数据为一个doc
字段/field
- doc中某个字段
shard
索引被分成若干primary shard 即数据被几等分 分布在不同node
每个primary shard有几个副本即replica shard;
primary shard与其replica shard不会出现在同一个node 为了满足分区容错性
数据会被存入哪一个shard 根据hash(routing) % number_of_primary_shards routing默认是_id
由于一致性hash问题所以shard个数定好之后不可能增加修改
读写流程
写请求:
- 请求任意节点(协调节点) 协调节点根据id路由到指定primary shard-->primary shard写完后分发给自己的replica shard->replica shard写完后回传信息给自己的primary shard->primary shard收到所有写完信号后通知协调节点
读请求:
- 请求任意节点(协调节点) 无id可以路由时分发请求到所有shard,将所有shard返回的数据放到一起(结果集仅仅包含docId和所有排序的字段值) 而后确定需要返回给client的docId 再去这些docId对应的shard上取回真正的数据返回
倒排索引
将一句话利用分词器分成若干字或词,每个字词分布在哪些doc
正排索引是指文档ID为key,表中记录每个关键词出现的次数,查找时扫描表中的每个文档中字的信息,直到找到所有包含查询关键字的文档
docA 内容 智器云大数据,大数据时代的福尔摩斯
docb 内容 大数据中心
此时的索引为
docA_id 智器云 1(出现次数)
大数据 2
...
docB_id 大数据 1
中心 1
检索 关键字 智器云时 查询所有doc找到含有智器云的文档docA
检索 关键字 大数据时 查询所有doc找到含有智器云的文档docA,docB
docA 内容 智器云大数据,大数据时代的福尔摩斯
docb 内容 大数据中心
此时的索引为
智器云 docA_id,docB_id
大数据 docA_id,docB_id
中心 docB_id
...
检索 关键字 智器云时 直接返回docA_id
检索 关键字 大数据时 直接返回docA_id,docB_id
检索 关键字 中心时 直接返回docB_id
分词器
索引时使用 ik_max_word(细粒度),在搜索时用ik_smart(粗粒度)
ik_max_word会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
ik_smart会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
pinyin
keep_first_letter启用此选项时,例如:刘德华> ldh,默认值:true
keep_separate_first_letter启用该选项时,将保留第一个字母分开,例如:刘德华> l,d,h,默认:false
limit_first_letter_length 设置first_letter结果的最大长度,默认值:16
keep_full_pinyin当启用该选项,例如:刘德华> [ liu,de,hua],默认值:true
keep_joined_full_pinyin当启用此选项时,例如:刘德华> [ liudehua],默认值:false
keep_none_chinese 在结果中保留非中文字母或数字,默认值:true
keep_none_chinese_together保持非中国信一起,默认值:true,如:DJ音乐家- > DJ,yin,yue,jia,当设置为false,例如:DJ音乐家- > D,J,yin,yue,jia,注意:keep_none_chinese必须先启动
keep_none_chinese_in_first_letter第一个字母保持非中文字母,例如:刘德华AT2016- > ldhat2016,默认值:true
keep_none_chinese_in_joined_full_pinyin保留非中文字母加入完整拼音,例如:刘德华2016- > liudehua2016,默认:false
none_chinese_pinyin_tokenize打破非中国信成单独的拼音项,如果他们拼音,默认值:true,如:liudehuaalibaba13zhuanghan- > liu,de,hua,a,li,ba,ba,13,zhuang,han,注意:keep_none_chinese和keep_none_chinese_together应首先启用
keep_original 当启用此选项时,也会保留原始输入,默认值:false
lowercase 小写非中文字母,默认值:true
trim_whitespace 默认值:true
remove_duplicated_term当启用此选项时,将删除重复项以保存索引,例如:de的> de,默认值:false,注意:位置相关查询可能受影响
setting
{
"settings":{
"analysis":{
"char_filter": {},//分词前,字符过滤器
"tokenizer": {},//自定义分词器,对已有分词器进行属性参数上的更改
"filter": {},//分词后,分词过滤器
"analyzer":{}//定制化分析器,结合上述3个组装出自己的分词器
}
}
}
{
"settings": {
"index.max_ngram_diff": 20,
"number_of_shards": "5",
"number_of_replicas": "2",
"analysis": {
"analyzer": {
"pinyin_analyzer": {
"tokenizer": "standard_pinyin"
},
"first_py_letter_analyzer": {
"tokenizer": "first_py_letter"
},
"full_pinyin_letter_analyzer": {
"tokenizer": "full_pinyin_letter"
}
},
"tokenizer": {
"standard_pinyin": {
"keep_joined_full_pinyin": "true",
"keep_first_letter": "true",
"keep_separate_first_letter": "false",
"lowercase": "true",
"type": "pinyin",
"limit_first_letter_length": "16",
"keep_original": "true",
"keep_full_pinyin": "true",
"keep_none_chinese_in_joined_full_pinyin": "true"
},
"first_py_letter": {
"type": "pinyin",
"keep_first_letter": true,
"keep_full_pinyin": false,
"keep_original": false,
"limit_first_letter_length": 16,
"lowercase": true,
"trim_whitespace": true,
"keep_none_chinese_in_first_letter": false,
"none_chinese_pinyin_tokenize": false,
"keep_none_chinese": true,
"keep_none_chinese_in_joined_full_pinyin": true
},
"full_pinyin_letter": {
"type": "pinyin",
"keep_separate_first_letter": false,
"keep_full_pinyin": false,
"keep_original": false,
"limit_first_letter_length": 16,
"lowercase": true,
"keep_first_letter": false,
"keep_none_chinese_in_first_letter": false,
"none_chinese_pinyin_tokenize": false,
"keep_none_chinese": true,
"keep_joined_full_pinyin": true,
"keep_none_chinese_in_joined_full_pinyin": true
}
}
}
}
}
索引静态配置
△△△△index.number_of_shards 索引分片的数量。在ES层面可以通过es.index.max_number_of_shards属性设置索引最大的分片数,默认为1024,index.number_of_shards的默认值为Math.min(es.index.max_number_of_shards,5),故通常默认值为5。
index.shard.check_on_startup 分片在打开之前是否应该检查该分片是否损坏。当检测到损坏时,它将阻止分片被打开。可选值:false:不检测;checksum:只检查物理结构;true:检查物理和逻辑损坏,相对比较耗CPU;fix:类同与false,7.0版本后将废弃。默认值:false。
index.codec 数据存储的压缩算法,默认值为LZ4,可选择值best_compression ,比LZ4可以获得更好的压缩比(即占据较小的磁盘空间,但存储性能比LZ4低)。
index.routing_partition_size 路由分区数,如果设置了该参数,其路由算法为:(hash(_routing) + hash(_id) % index.routing_parttion_size ) % number_of_shards。如果该值不设置,则路由算法为 hash(_routing) % number_of_shardings,_routing默认值为_id。
索引动态配置
△△△△index.number_of_replicas 索引复制分片的个数,默认值1,该值必须大于等于0,索引创建后该值可以变更。
index.auto_expand_replicas 副本数是否自动扩展,可设置(e.g0-5)或(0-all)。
index.refresh_interval 执行刷新操作的频率,该操作使对索引的最新更改对搜索可见。默认为1s。可以设置为-1以禁用刷新。
index.max_result_window 控制分页搜索总记录数,from + size的大小不能超过该值,默认为10000。
index.max_inner_result_window 从from+ size的最大值,用于控制top aggregations,默认为100。内部命中和顶部命中聚合占用堆内存,并且时间与 from + size成正比,这限制了内存。
index.max_rescore_window 在rescore的搜索中,rescore请求的window_size的最大值。
index.max_docvalue_fields_search 一次查询最多包含开启doc_values字段的个数,默认为100。
index.max_script_fields 查询中允许的最大script_fields数量。默认为32。
index.max_ngram_diff NGramTokenizer和NGramTokenFilter的min_gram和max_gram之间允许的最大差异。默认为1。
index.max_shingle_diff 对于ShingleTokenFilter, max_shingle_size和min_shingle_size之间允许的最大差异。默认为3。
index.blocks.read_only 索引数据、索引元数据是否只读,如果设置为true,则不能修改索引数据,也不能修改索引元数据。
index.blocks.read_only_allow_delete 与index.blocks.read_only基本类似,唯一的区别是允许删除动作。
index.blocks.read 设置为true以禁用对索引数据的读取操作。
index.blocks.write 设置为true以禁用对索引数据的写操作。(针对索引数据,而不是索引元数据)
index.blocks.metadata 设置为true,表示不允许对索引元数据进行读与写。
index.max_refresh_listeners 索引的每个分片上当刷新索引时最大的可用监听器数量。这些侦听器用于实现refresh=wait_for。
index.highlight.max_analyzed_offset 高亮显示请求分析的最大字符数。此设置仅适用于在没有偏移量或term vectors的文本字段时。默认情况下,该设置在6中未设置。x,默认值为-1。
index.max_terms_count 可以在terms查询中使用的术语的最大数量。默认为65536。
index.routing.allocation.enable Allocation机制,其主要解决的是如何将索引在ES集群中在哪些节点上分配分片(例如在Node1是创建的主分片,在其他节点上创建复制分片)。举个例子,如果集群中新增加了一个节点,集群的节点由原来的3个变成了4 可选值:
all 所有类型的分片都可以重新分配,默认。
primaries 只允许分配主分片。
new_primaries 只允许分配新创建的主分片。
none 所有的分片都不允许分配。
index.routing.rebalance.enable 索引的分片重新平衡机制。可选值如下:
all 默认值,允许对所有分片进行再平衡。
primaries 只允许对主分片进行再平衡。
replicas 只允许对复制分片进行再平衡。
none 不允许对任何分片进行再平衡
index.gc_deletes 文档删除后(删除后版本号)还可以存活的周期,默认为60s。
index.max_regex_length 用于正在表达式查询(regex query)正在表达式长度,默认为1000。
index.default_pipeline 默认的管道聚合器。
mapping
{
"mappings": {
"properties": {
"field1": {
"type": "keyword"
},
"field2": {
"type": "text"
},
"field3": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}
选举
使用zk选举
言简意赅就是有资格成为master的节点去zk上创建指定路径,成功当选master失败则watch这个路径
自带的选举
脑裂:可能出现单个node多次选举多个有效master致使集群存在多个master 以下选举为单个node选择出自己的master
ElasticSearch.class=>Command.class: main方法启动
Command.class=>EnvironmentAwareCommand.class: execute()
EnvironmentAwareCommand.class=>ElasticSearch.class: execute()
ElasticSearch.class=>Bootstrap.class: init()
Bootstrap.class=>Node.class: start()
// Node.class
public Node start() throws NodeValidationException {
...
...
discovery.start();
...
...
discovery.startInitialJoin();
...
...
discovery.start()
protected void doStart() {
DiscoveryNode localNode = transportService.getLocalNode();
assert localNode != null;
synchronized (stateMutex) {
// set initial state
assert committedState.get() == null;
assert localNode != null;
ClusterState.Builder builder = ClusterState.builder(clusterName);
ClusterState initialState = builder
.blocks(ClusterBlocks.builder()
.addGlobalBlock(STATE_NOT_RECOVERED_BLOCK)
.addGlobalBlock(noMasterBlockService.getNoMasterBlock()))
.nodes(DiscoveryNodes.builder().add(localNode).localNodeId(localNode.getId()))
.build();
committedState.set(initialState);
clusterApplier.setInitialState(initialState);
nodesFD.setLocalNode(localNode);
joinThreadControl.start();//**running.set(true)**
}
zenPing.start();
}
discovery.startInitialJoin();
//ZenDiscovery.class
public void startNewThreadIfNotRunning() {
assert Thread.holdsLock(stateMutex);
if (joinThreadActive()) {
return;
}
threadPool.generic().execute(new Runnable() {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
if (!currentJoinThread.compareAndSet(null, currentThread)) {
return;
}
//自旋直到上面设置成true
while (running.get() && joinThreadActive(currentThread)) {
try {
innerJoinCluster();
return;
} catch (Exception e) {
logger.error("unexpected error while joining cluster, trying again", e);
// Because we catch any exception here, we want to know in
// tests if an uncaught exception got to this point and the test infra uncaught exception
// leak detection can catch this. In practise no uncaught exception should leak
assert ExceptionsHelper.reThrowIfNotNull(e);
}
}
// cleaning the current thread from currentJoinThread is done by explicit calls.
}
});
}
innerJoinCluster();
private void innerJoinCluster() {
DiscoveryNode masterNode = null;
final Thread currentThread = Thread.currentThread();
nodeJoinController.startElectionContext();
while (masterNode == null && joinThreadControl.joinThreadActive(currentThread)) {
masterNode = findMaster();
}
if (!joinThreadControl.joinThreadActive(currentThread)) {
logger.trace("thread is no longer in currentJoinThread. Stopping.");
return;
}
//如果选举结果是自己
if (transportService.getLocalNode().equals(masterNode)) {
//半数以上结果时即可成为集群master requiredJoins为还需几个节点join
final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
logger.debug("elected as master, waiting for incoming joins ([{}] needed)", requiredJoins);
//等待其他node返回选举结果
nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
new NodeJoinController.ElectionCallback() {
@Override
public void onElectedAsMaster(ClusterState state) {
synchronized (stateMutex) {
joinThreadControl.markThreadAsDone(currentThread);
}
}
@Override
public void onFailure(Throwable t) {
logger.trace("failed while waiting for nodes to join, rejoining", t);
synchronized (stateMutex) {
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
}
}
}
);
} else {//选举的master不是自己时 向选举的node发送join投靠信息
// process any incoming joins (they will fail because we are not the master)
nodeJoinController.stopElectionContext(masterNode + " elected");
// send join request
final boolean success = joinElectedMaster(masterNode);
synchronized (stateMutex) {
if (success) {
DiscoveryNode currentMasterNode = this.clusterState().getNodes().getMasterNode();
if (currentMasterNode == null) {
// Post 1.3.0, the master should publish a new cluster state before acking our join request. we now should have
// a valid master.
logger.debug("no master node is set, despite of join request completing. retrying pings.");
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
} else if (currentMasterNode.equals(masterNode) == false) {
// update cluster state
joinThreadControl.stopRunningThreadAndRejoin("master_switched_while_finalizing_join");
}
joinThreadControl.markThreadAsDone(currentThread);
} else {
// 如果当前节点所选择的master节点,无法成为集群master时(自己没选举自己,选择其他node当选master)
//重新选举master
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
}
}
}
}
findMaster();
//ZenDiscovery.class
private DiscoveryNode findMaster() {
//通过ping 其他节点来判定本节点能够连接上的节点的个数,yml配置的discovery.seed_hosts获取ping列表
List<ZenPing.PingResponse> fullPingResponses = pingAndWait(pingTimeout).toList();
if (fullPingResponses == null) {
return null;
}
//当前节点
final DiscoveryNode localNode = transportService.getLocalNode();
fullPingResponses.add(new ZenPing.PingResponse(localNode, null, this.clusterState()));
//根据配置是否剔除非master节点过滤 配置文件true|false
final List<ZenPing.PingResponse> pingResponses = filterPingResponses(fullPingResponses, masterElectionIgnoreNonMasters, logger);
//集群中已存在的master节点,activeMasters中去掉自己 因为ping自己无意义
List<DiscoveryNode> activeMasters = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
if (pingResponse.master() != null && !localNode.equals(pingResponse.master())) {
activeMasters.add(pingResponse.master());
}
}
//配置 node.matser=true 即有资格成为master 备胎
List<ElectMasterService.MasterCandidate> masterCandidates = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
if (pingResponse.node().isMasterNode()) {
masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
}
}
//master节点为空 则让备胎竞选
if (activeMasters.isEmpty()) {
//是否需要满足min_master_nodes配置 最少得几个master
if (electMaster.hasEnoughCandidates(masterCandidates)) {
final ElectMasterService.MasterCandidate winner = electMaster.electMaster(masterCandidates);
//此时master还可能是自己 因为是其他node的master
return winner.getNode();
} else {
return null;
}
} else {
//不然则选择activeMasters中nodeID最小的master,此时master不可能有自己
return electMaster.tieBreakActiveMasters(activeMasters);
}
}
与传统关系型数据对比
todo
使用场景
todo
怎么使用
sql
局限性
只能返回数据,像score,heightlight都没有 跟真正的关系型数据返回无异
dsl
- match 将搜索词分词查询
- match_phrase 搜索词顺序一致,间隔为slop
- match_phrase_prefix
- operator 将分词条件用and连接 默认为or
- boost 检索权重 跟分数相关
- minmum_should_match 搜索词相关度
- slop 查询词条能够相隔多远时仍然将文档视为匹配 比如查询ab slop=1时a@b也能满足条件返回 todo
spring-data-elasticsearch
todo
highRestClient
todo