ElasticSearch集群与分片管理

目录

1、集群的相关概念

1.1 集群 cluster

1.2 节点 node

1.3 分片和复制 shards&replicas

1.4 健康状态

1.5 存储空间

2、集群的搭建

2.1 准备三台服务器

2.2 修改服务器配置

2.3 启动测试

2.4 增加索引

2.5 插入文档

2.6 集群关停测试

3、ES其他

3.1 数据迁移和别名定义

3.2 自定义文档路由规则

3.3 Elasticsearch的路由算法



1、集群的相关概念

ES集群是一个 P2P类型(使用 gossip 协议)的点对点分布式系统,除了集群状态管理以外,其他所有的请求都可以发送到集群内任意一台节点上,这个节点可以自己找到需要转发给哪些节点,并且直接跟这些节点通信。所以,从网络架构及服务配置上来说,构建集群所需要的配置极其简单。在 Elasticsearch 2.0 之前,无阻碍的网络下,所有配置了相同 cluster.name 的节点都自动归属到一个集群中。2.0 版本之后,基于安全的考虑避免开发环境过于随便造成的麻烦,从 2.0 版本开始,默认的自动发现方式改为了单播(unicast)方式。配置里提供几台节点的地址,ES 将其视作 gossip router 角色,借以完成集群的发现。由于这只是 ES 内一个很小的功能,所以 gossip router 角色并不需要单独配置,每个 ES 节点都可以担任。所以,采用单播方式的集群,各节点都配置相同的几个节点列表作为 router 即可。

集群中节点数量没有限制,一般大于等于2个节点就可以看做是集群了。一般处于高性能及高可用方面来考虑一般集群中的节点数量都是3个及3个以上。

1.1 集群 cluster

一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群,这个名字默认是“elasticsearch”

1.2 节点 node

一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。

一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。

在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。

1.3 分片和复制 shards&replicas

一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因: 1)允许你水平分割/扩展你的内容容量。 2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。

至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。

在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。

复制之所以重要,有两个主要原因:

  • 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。

  • 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。

PUT /blog1/_settings
{
  "number_of_replicas": 1
}
PUT /blog2/_settings
{
  "number_of_shards": 2
}
​
-------------------------------
报错如下:
{
  "error" : {
    "root_cause" : [
      {
        "type" : "illegal_argument_exception",
        "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[blog2/_J9flP8pTkue1N6pdZ6Lxw]]"
      }
    ],
    "type" : "illegal_argument_exception",
    "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[blog2/_J9flP8pTkue1N6pdZ6Lxw]]"
  },
  "status" : 400
}
​

默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。(不好意思,es7.8版本默认就一个分片)

1.4 健康状态

针对一个索引,Elasticsearch 中其实有专门的衡量索引健康状况的标志,分为三个等级:

  • green,绿色。这代表所有的主分片和副本分片都已分配。你的集群是 100% 可用的。

  • yellow,黄色。所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果更多的分片消失,你就会丢数据了。所以可把 yellow 想象成一个需要及时调查的警告。

  • red,红色。至少一个主分片以及它的全部副本都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。

1.5 存储空间

既然是群集,那么存储空间肯定也是联合起来的,假如一台主机的存储空间是固定的,那么集群它相对于单个主机也有更多的存储空间,可存储的数据量也更大。

2、集群的搭建

2.1 准备三台服务器

192.168.223.128

192.168.223.129

192.168.223.130

分别安装好es,参照之前的课件;当然,你高兴可以配更多

2.2 修改服务器配置

分别编辑两台服务器,vim /etc/elasticsearch/elasticsearch.yml

#192.168.223.128 节点1的配置信息:
#集群名称
cluster.name: ES-Cluster
##节点名称
node.name: ES-node1
##是否是master节点
#node.master: true
##是否允许该节点存储索引数据
#node.data: true
##日志目录
##绑定地址
network.host: 192.168.223.128
##http端口
http.port: 9200
##集群主机列表
discovery.seed_hosts: ["192.168.223.128","192.168.223.129","192.168.223.130"]
##启动全新的集群时需要此参数,再次重新启动时此参数可免
cluster.initial_master_nodes: ["ES-node1","ES-node2","ES-node3"]
##集群内同时启动的数据任务个数,默认是2个
#cluster.routing.allocation.cluster_concurrent_rebalance: 32
##添加或删除节点及负载均衡时并发恢复的线程个数,默认4个
#cluster.routing.allocation.node_concurrent_recoveries: 32
##初始化数据恢复时,并发恢复线程的个数,默认4个
#cluster.routing.allocation.node_initial_primaries_recoveries: 32
##是否开启跨域访问
http.cors.enabled: true
##开启跨域访问后的地址限制,*表示无限制
http.cors.allow-origin: "*"
​
#192.168.223.129 节点1的配置信息:
#集群名称
cluster.name: ES-Cluster
###节点名称
node.name: ES-node2
###是否是master节点
#node.master: true
###是否允许该节点存储索引数据
#node.data: true
###绑定地址
network.host: 192.168.223.129
###http端口
http.port: 9200
###集群主机列表
discovery.seed_hosts: ["192.168.223.128","192.168.223.129","192.168.223.130"]
##启动全新的集群时需要此参数,再次重新启动时此参数可免
cluster.initial_master_nodes: ["ES-node1","ES-node2","ES-node3"]
###集群内同时启动的数据任务个数,默认是2个
#cluster.routing.allocation.cluster_concurrent_rebalance: 32
###添加或删除节点及负载均衡时并发恢复的线程个数,默认4个
#cluster.routing.allocation.node_concurrent_recoveries: 32
###初始化数据恢复时,并发恢复线程的个数,默认4个
#cluster.routing.allocation.node_initial_primaries_recoveries: 32
###是否开启跨域访问
http.cors.enabled: true
###开启跨域访问后的地址限制,*表示无限制
http.cors.allow-origin: "*"
​
#192.168.223.130 节点1的配置信息:
#集群名称
cluster.name: ES-Cluster
###节点名称
node.name: ES-node3
###是否是master节点
#node.master: true
###是否允许该节点存储索引数据
#node.data: true
###绑定地址
network.host: 192.168.223.130
###http端口
http.port: 9200
###集群主机列表
discovery.seed_hosts: ["192.168.223.128","192.168.223.129","192.168.223.130"]
##启动全新的集群时需要此参数,再次重新启动时此参数可免
cluster.initial_master_nodes: ["ES-node1","ES-node2","ES-node3"]
###集群内同时启动的数据任务个数,默认是2个
#cluster.routing.allocation.cluster_concurrent_rebalance: 32
###添加或删除节点及负载均衡时并发恢复的线程个数,默认4个
#cluster.routing.allocation.node_concurrent_recoveries: 32
###初始化数据恢复时,并发恢复线程的个数,默认4个
#cluster.routing.allocation.node_initial_primaries_recoveries: 32
###是否开启跨域访问
http.cors.enabled: true
###开启跨域访问后的地址限制,*表示无限制
http.cors.allow-origin: "*"
​

2.3 启动测试

#多机集群因为新增服务器导致数据节点不匹配,会导致相互不能发现,所以只能保留一台最完整的数据服务,其他data全部清空 并且新建一个空目录
rm -rf /var/lib/elasticsearch
mkdir /var/lib/elasticsearch
​
#PS 如果启动报没有文件夹访问权限,需要给用户设置权限0
chown root /var/lib/elasticsearch -R
chmod 777 /var/lib/elasticsearch
​
#启动所有集群上服务器
systemctl restart elasticsearch

启动完成,通过es-head可以看到集群中三个节点:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

 

2.4 增加索引

我们增加一个blog1索引(包含mapping映射),他有五个分片和一个备份

PUT /blog1
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "long"
      },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

结果:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

 

可以看出来,任何一个分片都有一个备份,所以任何一个节点挂掉,都能立马找到对应的备胎!

2.5 插入文档

@Test
    public void test3() throws IOException {
        //增加文档(如果类注释推荐使用org.elasticsearch.client.Requests来创建类,最好用他推荐的方式)
        IndexRequest request = Requests.indexRequest("blog1");
        for (int i = 0; i < 30; i++) {
            request.id(i+"");// 指定ID
            request.source(i+"title", "ElasticSearch是一个基于Lucene的搜索服务器",
                    "content", i+"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java 开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时 搜索,稳定,可靠,快速,安装使用方便。");// 支持多种方式
            IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
            System.out.println(indexResponse);
        }
    }

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

 

2.6 集群关停测试

现在我们关掉其中一台服务器192.168.223.129,再来查询:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

可以看到,因为任意两台服务器都可以找齐五个分片,所以不会数据丢失!但是因为分片并没有备份了,所以健康值为黄色!

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

再关掉一台节点: 192.168.223.130

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

集群终结,因为只剩下一台服务器了,已经无法构成集群,再次启动192.168.223.130:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

再次启动192.168.223.129,集群还原:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1eGlhbmcxOTg1MTExNA==,size_16,color_FFFFFF,t_70

3、ES其他

3.1 数据迁移和别名定义

我们使用Elasticsearch索引文档时,最理想的情况是文档JSON结构是确定的,数据源源不断地灌进来即可,但实际情况中,没人能够阻拦需求的变更,在项目的某个版本,可能会对原有的文档结构造成冲击,增加新的字段还好,如果要修改原有的字段,只能重建索引了。

POST _reindex
{
  "source": {
    "index": "blog1"
  },
  "dest": {
    "index": "blog2",
    "version_type": "internal"
    #"op_type": "create" 如果op_type设置为create,那么迁移时只在目标索引中创建ID不存在的文档,已存在的文档,会提示错误
  }
}
​
POST /_aliases
{
    "actions": [
        { "remove": { "index": "blog1", "alias": "blog" }},
        { "add":    { "index": "blog2", "alias": "blog" }}
    ]
}
​
#version_type属性含义如下:
​
    # internal:直接拷贝文档到目标索引,对相同的type、文档ID直接进行覆盖,默认值
    # external:迁移文档到目标索引时,保留version信息,对目标索引中不存在的文档进行创建,已存在的文档按version进行更新,遵循乐观锁机制。
//数据迁移
ReindexRequest reindexRequest = new ReindexRequest();
reindexRequest.setSourceIndices("blog1");
reindexRequest.setDestIndex("blog3");
reindexRequest.setDestVersionType(VersionType.INTERNAL);
reindexRequest.setDestOpType(String.valueOf(DocWriteRequest.OpType.DELETE));
client.reindex(reindexRequest,RequestOptions.DEFAULT);

//别名处理
IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();
IndicesAliasesRequest.AliasActions aliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.REMOVE);
aliasActions.index("blog2");
aliasActions.alias("blog");
indicesAliasesRequest.addAliasAction(aliasActions);
client.indices().updateAliases(indicesAliasesRequest,RequestOptions.DEFAULT);

3.2 自定义文档路由规则

正常的一次查询(search,不是findById),请求会被发给所有shard(不考虑副本),然后等所有shard返回,再将结果聚合,返回给调用方。如果我们事先已经知道数据可能分布在哪些shard上,那么就可以减少不必要的请求。

DELETE /blog1
​
PUT /blog1
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "long"
      },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}
​
​
#根据指定routing规则入库
PUT /blog1/_doc/3?routing=key3
{
    "id":3,
    "title": "3ElasticSearch是一个基于Lucene的搜索服务器",
    "content": "3它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java 开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时 搜索,稳定,可靠,快速,安装使用方便。"
}
​
#查看shard及文档数
GET _cat/shards/blog1?v
​
#根据routing查看所在分片
GET /_search_shards?routing=key3
​
#根据routing直接获取数据
GET /blog1/_search?routing=key3
{
  "query":{
    "match":{
      "content":"开放源码"
    }
  },
  "highlight": {
    "fields": {
      "content":{}
    }
  }
}

PS:不自定义routing的情况下,根据_id查询也可以直接命中单个shard,不过根据id查询的情况毕竟不多!

我们自定义routing后会导致的一个问题:id不再全局唯一,如果自定义了routing字段的话,一般doc的增删改查接口都要加上routing参数以保证一致性。注意这里的【一般】指的是查询,并不是所有查询接口都要加上routing。当然最好的办法还是需要入库的时候保证id的唯一性

	//按照路由key新增文档
        IndexRequest request = Requests.indexRequest("blog3");
        Random random = new Random();
        for (int i = 0; i < 40; i++) {
            request.id(i + "");//指定唯一标志ID
            request.source(
                    "id", i,
                    "title", i + "ElasticSearch是一个基于Lucene的搜索服务器555",
                    "content", i + "它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。" +
                            "Elasticsearch是用Java 开发的,并作为Apache许可条款下的开放源码发布," +
                            "是当前流行的企业级搜索引擎。设计用于云计算中," +
                            "能够达到实时 搜索,稳定,可靠,快速,安装使用方便。",
                    "salary",random.nextInt(100)
            );// 支持多种方式
            request.routing("key3");
            IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
            System.out.println(indexResponse);
        }

    //按照路由Key查询文档
		SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices("blog3");
        searchRequest.routing("key2");
        SearchHits hits = client.search(searchRequest, RequestOptions.DEFAULT).getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }

3.3 Elasticsearch的路由算法

路由算法:shard = hash(routing) % number_of_primary_shards

举个例子,一个index有3个primary shard,P0,P1,P2

每次增删改查一个document的时候,都会带过来一个routing number,默认就是这个document的id(可能是手动指定,也可能是自动生成) routing = _id,假设id=1

会将这个routing值,传入一个hash函数中,产出一个routing值的hash值,hash(routing) = 21 然后将hash函数产出的值对这个index的primary shard的数量求余数,21 % 3 = 0 就决定了,这个document就放在P0上。

决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的

无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

 

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值