最近一大段时间,跟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构建的了。
更加详细的使用方法参考bboss-elasticsearch官方文档
使用过程无非就是引入依赖、配置、具体使用,注意这是在springboot中的使用方法,哪个版本无所谓了,我用的是2.1.x版本,其他的使用方法参考官当文档。
截止目前,最新版本为6.2.2,你也可以引用最新版。之前跨版本迁移时我使用的是6.1.0版本,遇到了不少BUG,顺手就在github提了几个issure,开发人员回复也是相当快!!强烈推荐使用最新版。
<dependency>
<groupId>com.bbossgroups.pluginsgroupId>
<artifactId>bboss-elasticsearch-rest-jdbcartifactId>
<version>6.2.2version>
<exclusions>
<exclusion>
<artifactId>servlet-apiartifactId>
<groupId>javax.servletgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.bbossgroups.pluginsgroupId>
<artifactId>bboss-elasticsearch-spring-boot-starterartifactId>
<version>6.2.2version>
<exclusions>
<exclusion>
<artifactId>servlet-apiartifactId>
<groupId>javax.servletgroupId>
exclusion>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.68version>
dependency>
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=
添加一个配置类,配置一下操作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();
}
}
@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);
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);
}
}
}
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);
}
}
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;
}
使用bboss迁移数据的速度还是相当快的,150万条数据,1分钟左右就可以迁移完成。我们集群当时有600多个索引,40T的数据,大改花了一周的时间迁移完毕。
经历上一次跨版本迁移之后,我以为我不会再接触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集群。
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
直接运行安装命令即可
npm install elasticdump -g
找一台能联网的机器执行以下操作
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
以下为我用的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集群无法访问了,所以我才有时间整理这一篇博客。如果你遇到问题了,可以留言咨询我。