Elasticsearch集群迁移

前言

  最近一大段时间,跟ES杠上了,先是从5.x版本迁移到7.3.2,又从7.3.2迁移到7.3.2,真是把我折磨的够呛。在此记录一下相关工作,对其他需要迁移ES的人提供一些经验吧。

一、版本之灾

  说起ES的版本,真的是深恶痛绝!恨不得打一顿设计ES代码的人。之前只是简单的学习了一下ES 7.x查询相关的API,并没有深入研究ES,所以对于迁移还是有点懵的。ES集群维护方浪潮提供了跨大版本迁移的示例代码,但是需要迁移两次,即从5.x迁移到6.x,再从6.x迁移到7.x。这简直就是浪费时间,浪费生命啊。百度了很久,终于在码云发现了bboss-elasticsearch工具:bboss-elasticsearch,支持跨大版本迁移,所以就开始了我的入坑之旅。

二、迁移概览

  因为是第一次迁移ES集群,公司内部也没人干过这事,所以只能靠自己。
根据我的经验,迁移ES集群整体的步骤就是先把索引迁移过去,包括索引的settings、mapping、alias,再把索引的数据迁移过去,切记还有ES的template也要迁移。
  我当初是参考bboss的这个项目elasticsearch-elasticsearch,这个项目是基于graddle构建的,我就给改成springboot+maven构建的了。

  1. 拿到源5.xES集群的索引信息,用代码修改索引的settings和mapping,以适配7.x版本的索引,然后直接往7.3.2ES集群里写索引
  2. 使用bboss工具迁移索引数据

三、bboss-elasticsearch跨版本迁移方案

  更加详细的使用方法参考bboss-elasticsearch官方文档
  使用过程无非就是引入依赖、配置、具体使用,注意这是在springboot中的使用方法,哪个版本无所谓了,我用的是2.1.x版本,其他的使用方法参考官当文档。

3.1、引入依赖

  截止目前,最新版本为6.2.2,你也可以引用最新版。之前跨版本迁移时我使用的是6.1.0版本,遇到了不少BUG,顺手就在github提了几个issure,开发人员回复也是相当快!!强烈推荐使用最新版。

<dependency>
            <groupId>com.bbossgroups.plugins</groupId>
            <artifactId>bboss-elasticsearch-rest-jdbc</artifactId>
            <version>6.2.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.bbossgroups.plugins</groupId>
            <artifactId>bboss-elasticsearch-spring-boot-starter</artifactId>
            <version>6.2.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

3.2、配置ES地址

  application.properties配置文件添加,重要的也就是前四行的配置,其他的我都是直接复制官方文档,我看着没啥用的也给删了。
  还有,如果你的ES是https访问的,可以使用nginx做一下反向代理,就可以使用http访问了,我就是这么使用的。
  配置信息具体含义请参考配置多ES集群

##原ES集群配置
spring.elasticsearch.bboss.default.name=default
spring.elasticsearch.bboss.default.elasticUser=username
spring.elasticsearch.bboss.default.elasticPassword=password
spring.elasticsearch.bboss.default.elasticsearch.rest.hostNames=localhost:9200
spring.elasticsearch.bboss.default.elasticsearch.dateFormat=yyyy.MM.dd
spring.elasticsearch.bboss.default.elasticsearch.timeZone=Asia/Shanghai
spring.elasticsearch.bboss.default.elasticsearch.ttl=2d
spring.elasticsearch.bboss.default.elasticsearch.showTemplate=false
spring.elasticsearch.bboss.default.elasticsearch.discoverHost=false
spring.elasticsearch.bboss.default.http.timeoutConnection=50000
spring.elasticsearch.bboss.default.http.timeoutSocket=50000
spring.elasticsearch.bboss.default.http.connectionRequestTimeout=50000
spring.elasticsearch.bboss.default.http.retryTime=1
spring.elasticsearch.bboss.default.http.maxLineLength=-1
spring.elasticsearch.bboss.default.http.maxHeaderCount=200
spring.elasticsearch.bboss.default.http.maxTotal=400
spring.elasticsearch.bboss.default.http.defaultMaxPerRoute=200
spring.elasticsearch.bboss.default.http.keystore=
spring.elasticsearch.bboss.default.http.keyPassword=

##目标集群配置
spring.elasticsearch.bboss.target.name=target
spring.elasticsearch.bboss.target.elasticUser=username
spring.elasticsearch.bboss.target.elasticPassword=password
spring.elasticsearch.bboss.target.elasticsearch.rest.hostNames=192.169.0.128:9200
spring.elasticsearch.bboss.target.elasticsearch.dateFormat=yyyy.MM.dd
spring.elasticsearch.bboss.target.elasticsearch.timeZone=Asia/Shanghai
spring.elasticsearch.bboss.target.elasticsearch.ttl=2d
spring.elasticsearch.bboss.target.elasticsearch.showTemplate=false
spring.elasticsearch.bboss.target.elasticsearch.discoverHost=false
spring.elasticsearch.bboss.target.http.timeoutConnection=50000
spring.elasticsearch.bboss.target.http.timeoutSocket=50000
spring.elasticsearch.bboss.target.http.connectionRequestTimeout=50000
spring.elasticsearch.bboss.target.http.retryTime=1
spring.elasticsearch.bboss.target.http.maxLineLength=-1
spring.elasticsearch.bboss.target.http.maxHeaderCount=200
spring.elasticsearch.bboss.target.http.maxTotal=400
spring.elasticsearch.bboss.target.http.defaultMaxPerRoute=200
spring.elasticsearch.bboss.target.http.keystore=
spring.elasticsearch.bboss.target.http.keyPassword=

3.3、添加配置类

  添加一个配置类,配置一下操作es的工具类,使用的时候直接注入即可

@Configuration
public class BbossESStarterConfig {
    
    

    @Primary
    @Bean(initMethod = "start")
    @ConfigurationProperties("spring.elasticsearch.bboss.default")
    public BBossESStarter bbossESStarterDefault(){
    
    
        return new BBossESStarter();

    }

    @Bean(initMethod = "start")
    @ConfigurationProperties("spring.elasticsearch.bboss.target")
    public BBossESStarter bbossESStarterTarget(){
    
    
        return new BBossESStarter();

    }

}

3.4、使用方法

    @Autowired
    private BBossESStarter bbossESStarterDefault;
    @Autowired
    private BBossESStarter bbossESStarterTarget;
	
	ClientInterface defaultRestClient = bbossESStarterDefault.getRestClient();
    ClientInterface targetRestClient = bbossESStarterTarget.getRestClient();

  ClientInterface接口含了操作ES的大部分API,实在没有的,可以直接使用executeHttp方法
例如:

restClient.executeHttp("/_settings", ClientInterface.HTTP_GET);
  1. 迁移索引
    把源ES 5.x集群的索引查询出来,遍历
   public void transferAllIndex(){
    
    

        ClientInterface sourceElasticsearch = bbossESStarterDefault.getRestClient("default");
        ClientInterface targetElasticsearch = bbossESStarterTarget.getRestClient("target");

        List<ESIndice> indexes = sourceElasticsearch.getIndexes();
        //过滤索引,排序
        List<ESIndice> indexList = indexes.stream()
                .filter((indices) -> {
    
    
                    String status = indices.getStatus();
                    String indexName = indices.getIndex();
                    //过滤已关闭的索引
                    if (StringUtils.equals(status, "close")){
    
    
                        return false;
                    }
                    return true;
                })
                .sorted(Comparator.comparing(ESIndice::getIndex))
                .collect(Collectors.toList());

        for (ESIndice index : indexList) {
    
    

            String indexName = index.getIndex();
            log.info("索引名称:【{}】", indexName);
            boolean sourceIndex = sourceElasticsearch.existIndice(indexName);
            log.info("{}索引是否存在:{}", indexName, sourceIndex);

            if (sourceIndex){
    
    
                this.createIndexToTarget(sourceElasticsearch, targetElasticsearch, indexName);
            }

        }
    }

  1. 目标集群创建索引
   private void createIndexToTarget(ClientInterface sourceElasticsearch,
                                     ClientInterface targetElasticsearch,
                                     String indexName){
    
    

        String indexMapping = sourceElasticsearch.getIndexMapping(indexName);
        String indexSetting = sourceElasticsearch.getIndiceSetting(indexName);
        String indexAlias = sourceElasticsearch.executeHttp(indexName + "/_alias", ClientUtil.HTTP_GET);

        //处理setting和mapping
        String mappings = resolveSettingsAndMappings(indexName, indexSetting, indexMapping);
        log.info("目标ES集群【{}】索引的setting和mapping:{}", indexName , mappings);
        String alias = resolveAlias(indexName, indexAlias);
        log.info("目标ES集群【{}】索引的别名:{}", indexName , alias);

        boolean existIndice = targetElasticsearch.existIndice(indexName);
        //如果目标索引不存在才新建
        if (!existIndice){
    
    
            String createResult = targetElasticsearch.createIndiceMapping(indexName, mappings);
            log.info("创建【{}】索引的结果:{}", indexName, createResult);
            if (StringUtils.isNotBlank(alias)){
    
    
                String aliasResult = targetElasticsearch.addAlias(indexName, alias);
                log.info("【{}】索引添加别名:{}的结果:{}", indexName, alias, aliasResult);
            }
        } else {
    
    
            log.warn("目标索引【{}】已存在", indexName);
        }
    }
  1. 处理settings、mapping、alias
    private String resolveSettingsAndMappings(String indexName, String indexSetting, String indexMapping){
    
    
        JSONObject jsonObject = JSONObject.parseObject(indexMapping);
        JSONObject jsonObject1 = jsonObject.getJSONObject(indexName).getJSONObject("mappings");
        JSONObject mappings = new JSONObject();
        if (jsonObject1.size()!= 0){
    
    
            String type = "";
            Set<String> mappingSet = jsonObject1.keySet();
            for (String mapping : mappingSet) {
    
    
                type = mapping;
            }
            JSONObject properties = jsonObject1.getJSONObject(type).getJSONObject("properties");

            JSONObject pro = new JSONObject();
            pro.put("properties", properties);

            mappings.put("mappings", pro);

        }
        JSONObject settingObject = JSONObject.parseObject(indexSetting);
        JSONObject settingObject1 = settingObject.getJSONObject(indexName).getJSONObject("settings");

        if (settingObject1.size()!= 0){
    
    
            String type = "";
            Set<String> settingSet = settingObject1.keySet();
            for (String setting : settingSet) {
    
    
                type = setting;
            }
            JSONObject settings = settingObject1.getJSONObject(type);
            settings.remove("creation_date");
            settings.remove("provided_name");
            settings.remove("uuid");
            settings.remove("version");

            if (Objects.nonNull(settings.getJSONObject("analysis"))){
    
    
                settings.put("index.max_ngram_diff", 29);
            }

            mappings.put("settings", settings);

        }

        return mappings.toJSONString();
    }

    private String resolveAlias(String indexName, String indexAlias){
    
    
        JSONObject jsonObject = JSONObject.parseObject(indexAlias);
        JSONObject aliasObject = jsonObject.getJSONObject(indexName).getJSONObject("aliases");

        String alias = "";
        Set<String> keys = aliasObject.keySet();
        for (String key : keys) {
    
    
            alias = key;
        }
        return alias;
    }
  1. 迁移数据
    迁移ES数据的方法参考:ES2ESScrollAllTimestampDemo.java这个类。我是直接拷贝,稍微改了改。

3.5、bboss使用小结

  使用bboss迁移数据的速度还是相当快的,150万条数据,1分钟左右就可以迁移完成。我们集群当时有600多个索引,40T的数据,大改花了一周的时间迁移完毕。

四、elasticsearch-dump同版本迁移方案

  经历上一次跨版本迁移之后,我以为我不会再接触ES迁移了,没想到最近几天又接到ES集群迁移的任务,这一次是同版本的迁移。为了少写代码,百度加问其他大佬,得到了几种其他的迁移方案。主要有一下三种方案:reindex、logstash、elasticsearch-dump。reindex需要修改集群配置,添加白名单,排除该方案,我没找到使用logstash迁移索引的settings和mapping的方法,所以也排除,最终选择elasticsearch-dump来迁移ES集群。
  elasticsearch-dump包含两个命令:elasticdump和multielasticdump,elasticdump是迁移单个索引,multielasticdump可以使用正则表达式匹配索引名称迁移多个索引。具体参数还是参考github的README,那里有详细说明,我也就不再赘述了。
  我刚开始是打算使用multielasticdump来迁移所有的索引,然后使用elasticdump迁移单个索引的数据。经过测试elasticdump迁移数据速度比bboss-elasticsearch慢太多,所以最终选择multielasticdump和bboss-elasticsearch结合的方案来迁移ES集群。

4.1、离线安装nodejs

1、下载官方安装包并拷贝到离线机器上。

官方下载地址:https://nodejs.org/en/download/

2、解压文件:

tar-xJf node-v8.9.4-linux-x64.tar.xz

2、放到相应目录例如/opt/

sudo mv node-v8.9.4-linux-x64 /opt/

3、建立文件链接使npm和node命令到系统命令

sudo ln -s /opt/node-v8.9.4-linux-x64/bin/node /usr/local/bin/node
sudo ln -s /opt/node-v8.9.4-linux-x64/bin/npm /usr/local/bin/npm

4、检查是否安装成功

node -v
npm -v

4.2、安装elasticsearch-dump

4.2.1、在线安装

直接运行安装命令即可

npm install elasticdump -g
4.2.2、 离线安装

找一台能联网的机器执行以下操作
1、安装npm-pack-all工具
npm-pack-all:用于打包npm库为.tgz文件

npm install npm-pack-all -g

2、安装elasticdump

npm install elasticdump -g

3、打包elasticdump
查看npm位置

npm config get cache

进入elasticdump安装目录:

cd %appdata%\npm\node_modules\elasticdump

执行:

npm-pack-all

即可生成对应的.tgz文件,例如:elasticdump-6.33.2.tgz

4、在不能联网的机器上离线安装elasticdump
在离线机器,进入npm目录

cd /opt/node-v8.9.4-linux-x64/bin/npm

把elasticdump-6.33.2.tgz复制到目录下,执行:

npm install elasticdump-6.33.2.tgz

5、 建立文件软连接到系统命令

ln -s /opt/node-v8.9.4-linux-x64/lib/node_modules/elasticdump/bin/elasticdump /usr/bin/elasticdump

6、验证elasticdump
输入命令:

elasticdump --help

4.3、使用elasticsearch-dump

  以下为我用的elasticsearch-dump命令

##备份所有的索引信息到本地文件
nohup multielasticdump --direction=dump --match='^.*$' --input=http://username:password@localhost:9200 --includeType='mapping,alias,settings' --output=./backup  > ./stdout.log 2>&1 &

##恢复索引信息到目标ES集群
nohup multielasticdump --direction=load --match='^.*$' --input=./backup --output=http://username:password@192.168.0.128:9200 --includeType='mapping,alias,settings' > ./stdout.log 2>&1 &

写在最后的话

从深入了解到工具选择,再到真正的迁移,我遇到了好多莫名其妙的问题,比如现在,目标ES集群无法访问了,所以我才有时间整理这一篇博客。如果你遇到问题了,可以留言咨询我。

猜你喜欢

转载自blog.csdn.net/WayneLee0809/article/details/109484214