ElasticSearch的gateway分析

来源:互联网 发布:淘宝能寄到印尼吗 编辑:程序博客网 时间:2024/05/20 03:45

        ElasticSearch的gateway功能,官方上的解释为时间机器。当集群整体down掉的时候,就好比时间机器一样进行数据的恢复。因此ElasticSearch的gateway模块就是为了集群的整体数据恢复服务的。

        在上篇博客ElasticSearchshard迁移中简单讲到了ElasticSearch的数据迁移,其实它算是当集群中有部分节点down掉后的局部恢复功能。本篇博客简要分析整体数据恢复gateway功能模块,主要针对的是Local方式。

        仔细分析ElasticSearch生成的数据,会发现包括三方面的数据:state、index、translog。其中state存储的是集群中的状态信息,index是lucene的索引文件,而translog是ElasticSearch为lucene添加的日志文件。在上篇博客中,数据迁移只用到了index和translog。这里面讲到的gateway功能这三种数据全部用到了。

        Lucene的索引恢复是比较简单的,只是将IndexWriter的打开模式设置为append即可。然而ElasticSearch是分布式的系统,它将数据打碎了分配到不同的shard上,这种数据恢复就很有难度了。

这里我认为的难点:

1)  Elasticsearch 如何恢复路由信息,将旧的shard数据一一对应上。(否则数据是不可用的)

2)  Elasticsearch如何保证的主副本数据的一致性。(在数据迁移的时候也有这方面的问题,上篇博客没有讲清楚,有遗漏点,后期补充。)

3)  Elasticsearch是如何利用translog里面的数据的,它的读取时机是什么时候。

 

       首先简要提下state文件是如何写入的。这里可以参考LocalGateway、LocalGatewayMetaState和LocalGatewayShardsState类,注意到它们都是ClusterStateListener的实现类。因此是当集群状态有变化时会自动存储的。

       调试代码可以发现:global文件中的信息是对应的MetaData对象。Index的state文件信息是存储的IndexMetaData对象。而shard的state文件存储的是ShardStateInfo对象。经过分析发现这些对象中都是没有存储集群的路由表信息的。那么Elasticsearch是如何恢复路由信息的?下面引出Elasticsearch恢复路由信息的具体过程。

Elasticsearch节点启动的时候,会执行InternalNodestart方法。代码如下:

……

……

DiscoveryService discoService = injector.getInstance(DiscoveryService.class).start();

 

        // gateway should start afterdisco, so it can try and recovery from gateway on "start"

        injector.getInstance(GatewayService.class).start();

……

……

         我们只关注上面的两行代码。上面的两行代码关系到Elasticsearchgateway功能。首先,是DiscoveryService服务,该服务的作用是发现节点、发现集群;并将自己加入集群;管理并维护各个节点间的心跳等信息。该服务的一系列行为会触发集群变更路由表状态,即调用clusterService.submitStateUpdateTask方法。此时集群路由的状态可能是这样的:

{

  "master_node" : "CYyxDyGxQjWtrQXKWPHwXQ",

  "blocks" : {

    "global" : {

      "1" : {

        "description" : "state not recovered / initialized",

        "retryable" : true,

        "disable_state_persistence" : true,

        "levels" : [ "read", "write", "metadata" ]

      }

    }

  },

  "nodes" : {

    "-gQwFu1NRUSoJp9dV_Ok7Q" : {

      "name" : "Dreadnought",

      "transport_address" : "inet[/192.168.1.1:9302]",

      "attributes" : { }

    },

    "gr3Lt9RPSTSygKKoGGqRMw" : {

      "name" : "Zuras",

      "transport_address" : "inet[/192.168.1.1:9301]",

      "attributes" : { }

    },

    "CYyxDyGxQjWtrQXKWPHwXQ" : {

      "name" : "Gertrude Yorkes",

      "transport_address" : "inet[/192.168.1.1:9300]",

      "attributes" : { }

    }

  },

  "metadata" : {

    "templates" : { },

    "indices" : { }

  },

  "routing_table" : {

    "indices" : { }

  },

  "routing_nodes" : {

    "unassigned" : [ ],

    "nodes" : {

      "-gQwFu1NRUSoJp9dV_Ok7Q" : [ ],

      "gr3Lt9RPSTSygKKoGGqRMw" : [ ],

      "CYyxDyGxQjWtrQXKWPHwXQ" : [ ]

    }

  },

  "allocations" : [ ]

}

        注意到,这个时候路由表已经有一部分信息了。在这张不完整的路由表里存储了node的地址等信息。然而没有索引分片等信息。这是执行DiscoveryService服务所得到的路由结果,算是为gatewayService铺路。

         接下来,gatewayService会判断一些条件,包括最少启动节点数、时间等信息,当满足条件后,gatewayService会根据上面生成的路由表,去各个点请求state信息。

         可以参考LocalGateway的performStateRecovery方法,在该方法中master会获得各个节点汇聚过来的state信息,找到各个节点中version最高的index state信息。可以恢复出:系统中有多少index,每个index的具体配置是什么情况(包括分配多少shard、每个shard多少副本等)然后计算路由,此时可能会生成如下的路由表:

{

  "master_node" : null,

  "blocks" : { },

  "nodes" : { },

  "metadata" : {

    "templates" : { },

    "indices" : {

      "codeingappleTestIndex" : {

        "state" : "open",

        "settings" : {

          "index.version.created" : "1000001",

          "index.number_of_replicas" : "1",

          "index.number_of_shards" : "2"

        },

        "mappings" : {

          "tweet" : {

            "properties" : {

              "message" : {

                "type" : "string"

              },

              "postDate" : {

                "format" : "dateOptionalTime",

                "type" : "date"

              },

              "user" : {

                "type" : "string"

              }

            }

          }

        },

        "aliases" : [ ]

      }

    }

  },

  "routing_table" : {

    "indices" : { }

  },

  "routing_nodes" : {

    "unassigned" : [ ],

    "nodes" : { }

  },

  "allocations" : [ ]

}

        注意了,上面的路由表已经具备了索引的详细配置信息。根据上面的两张表,GatewayService的GatewayRecoveryListener会再次更新集群路由状态信息,此时会进行shard的routing算法。注意routing部分,会执行GatewayAllocator.allocateUnassigned()方法,这是一个接口方法,在LocalGatewayAllocator实现类里面,我们可以发现该方法优先分配P shard到具体的node节点上。分配时是向各个node发起请求,查询到集群中node上指定shardId的ShardStateInfo信息,然后将当前P shard分配到shard version最大的node上去。由此完成了对P shard的分配。后面就调用其他路由算法将所有未分配的shard通通分配一遍。

         经过上面的处理,一个完整的路由表就会建立起来。此时仅仅用到了state类型的文件(注:还有文件夹结构),这只是完成了gateway的第一步操作。我们发现,在这步操作中,我们保证了P shard分配到原来down掉的集群中版本最高的shard所在node节点上(这种node一定也是集群中最后死掉的node,也就是数据最新的node)。

算出路由表后,开始gateway的第2步操作,根据路由表的信息对数据进行恢复。这时候就会用到index文件和translog文件。

         在上篇博客中重点介绍了一个IndicesClusterStateService类,gateway的数据恢复部分还在该类中。和数据迁移一样,也是INITIALIZING状态的shard作为数据恢复的发起者。注意到IndicesClusterStateService类的applyInitializingShard方法,在该方法中是大致如下的逻辑:

……

……

if (!shardRouting.primary()) {

            // recovery from primary

            ……

            // only recover from started primary, if we can't find one, we will do it next round

            ……

} else {

      if (shardRouting.relocatingNodeId() ==null) {

           // we are the first primary, recover from the gateway

           // if its post api allocation, the index should exists

……

调用shardGatewayService.recover方法进行数据的恢复

      } else {

                //进行数据迁移

      }

}

       在集群启动阶段,Elasticsearch的路由表是不断变化的,是一个迭代过程,节点可能会反复收到路由表信息。从上面的逻辑可以看出,当节点中有INITIALIZING的R shard时,它不先启动,而是找到started状态的P shard,然后从P shard上拷贝数据。这一点其实在启动时保证了数据的一致性。当started状态的Pshard找不到时,就什么也不做了,等待下一次接受到路由表信息再进行激发操作。当节点中有INITIALIZING的P shard时,这个时候就要进行判断了,此时的P shard状态是处于数据迁移过程,还是处于启动阶段。Elasticsearch巧妙的运用了shardRouting.relocatingNodeId是否为空进行判断。在启动阶段路由表中的relocatingNodeId一定是空值的。因此这样的P shard会调用shardGatewayService.recover方法将数据从本地gateway中恢复过来。具体的恢复方法的实现可以参考LocalIndexShardGateway. Recover方法。在该方法中用到了index文件和translog文件进行数据的恢复。

        这里有一点思考,为什么Elasticsearch要采用重新计算的方式,而不是整个将路由表存储起来,集群启动的时候直接恢复呢?我认为有2点原因,其一:Elasticsearchnodeid每次启动都是新算的,是没法和旧状态一致的;其二:这种方式较通过整体保存路由表再读出恢复的方式更加灵活。(当集群全部down后,只启动其中的部分节点也是可以的,当然了也是有数据丢失的风险的)

        以上就是我对Elasticsearchgateway功能中local部分代码的一些分析总结,是基于其0.93版本。对于某些问题点进行试验也是比较困难,有错误的地方,欢迎大家留言或发邮件给我,我将及时改正。我的邮箱是:codingapple@126.com。同时如有人需要转载此文,请注明出处,最好放上本人的邮箱地址,方便大家提出问题与我沟通。

0 0