一 遇到的问题
数据平台中元数据的管理一般有离线数据元数据 实时数据元数据,本次主要解决了离线的元数据。也就是hive的metastore信息。
但是metastore数据存储在mysql中,而且数据之间关联性比较强比如存储数据库的dbs,存储表的tbls,存储字段信息的colums_v2等等。想要获取到一个字段需要关联大量的表。
造成了查询速度过慢,而且对于hive的元数据库也会有比较大的压力。所以准备将元数据信息迁移到ES上进行检索。
二 为什么是ES
ES的优点很多,网上一搜一堆。主要是考虑到需要进行对于字段的comment,对于表的comment进行全文检索。不仅限于用户通过db.table 的搜索模式。可以直接搜索字段,搜索字段的中文注释。
目前支持的如下:
- 数据库名
- 表中文名称
- 表英文名
- 字段名称
- 字段中文名
- owner
三 Mysql2ES
Mysql数据实时同步至ES有比较好的解决方案
比如 go-mysql-elasticsearch 或者 logstash-input-jdbc 上面两个插件各有各的优缺点。但是我们需要多表JOIN,需要多个hive的metasotre表join后的数据,没有必要将数据全部同步过去。而且各个表之间有以来关系。
到最后我也没弄明白ES表的以来关系是怎么创建的,所以上面的表无法满足情况只能自己写同步的代码。
因为元数据这块对实时性要求不是特别高,所以我准备每5分钟同步一次增量的。每天凌晨同步一次全量的(也可以手动),基本满足要求。
但是同步的时候不能影响用户的使用 所以需要用到ES的别名。
四 具体的实现
具体的步骤 从mysql获取到数据 -> 创建新的索引 -> 导入数据 -> 添加别名 -> 删除别名下其他的索引只保留今天刚创建的索引
1. 每天凌晨同步全量数据
a.获取所有同步的表的数据
List<SyncWideTable> syncWideTableList = syncDao.getSyncInfo();
b.判断索引是否存在
public boolean isExists(String index) {
Client client = elasticsearchTemplate.getClient();
IndicesExistsResponse response = client.admin().indices().prepareExists(index).get();
boolean exists = response.isExists();
return exists;
}
c.索引不存在则创建新的索引
public boolean createIndex(String index, String type, String mapping) {
Client client = elasticsearchTemplate.getClient();
//创建索引
CreateIndexResponse indexResponse = client.admin().indices().prepareCreate(index.toLowerCase()).get();
//如果创建成功则创建mapping
boolean exists = indexResponse.isAcknowledged();
if (exists) {
AcknowledgedResponse acknowledgedResponse = client.admin().indices().preparePutMapping(index.toLowerCase())
.setType(type)
.setSource(mapping, XContentType.JSON)
.get();
//返回mapping是否创建成功
boolean acknowledged = acknowledgedResponse.isAcknowledged();
return acknowledged;
}
//否则返回失败
return false;
}
d.导入数据
public void bulkIndex(String index, String type, List<SyncWideTable> syncWideTables) throws Exception {
Client client = elasticsearchTemplate.getClient();
//批处理
BulkRequestBuilder bulkRequest = client.prepareBulk();
for (SyncWideTable syncWideTable : syncWideTables) {
String id = syncWideTable.getDbName() + ":" + syncWideTable.getTbName();
UpdateRequestBuilder requestBuilder = client.prepareUpdate(index, type, id);
requestBuilder.setDoc(XContentFactory.jsonBuilder()
.startObject()
.field("TABLE_COMMENT", syncWideTable.getTableComment() == null ? "" : syncWideTable.getTableComment())
...
.endObject());
//有则更新无则插入
requestBuilder.setDocAsUpsert(true);
bulkRequest.add(requestBuilder);
}
bulkRequest.execute().actionGet();
// client.close();
}
e.添加别名
public boolean addAlias(String index, String alias) {
Client client = elasticsearchTemplate.getClient();
IndicesAliasesRequestBuilder aliasesRequestBuilder = client.admin().indices().prepareAliases().addAlias(index, alias);
boolean acknowledged = aliasesRequestBuilder.execute().actionGet().isAcknowledged();
return acknowledged;
}
f.删除别名下其他的索引
List<String> indexes = syncDao.getIndexFromAlias(ALIAS);
for (String indexTmp : indexes) {
if (!indexTmp.equals(newIndex)) {
try {
syncDao.removeAlias(indexTmp, ALIAS);
} catch (Exception e) {
logger.info(ALIAS + " 中不存在索引 " + indexTmp);
continue;
}
}
}
2. 每五分钟同步一次
直接用的springboot的调度器
private void getTbls() throws Exception {
List<SyncWideTable> syncWideTableList = syncDao.getSyncInfo();
logger.info("need to sync size is :" + syncWideTableList.size());
List<String> indexes = syncDao.getIndexFromAlias(ALIAS);
for (String index : indexes) {
syncDao.bulkIndex(index, TYPE, syncWideTableList);
}
logger.info("save syncWideTableList success !!!");
}
五 注意事项
1.需要定时清理过期索引。
2.实时性要求高的无法满足。
3.如果修改表结构了 只能删除索引重新call第一个每天同步一次的接口 或者等第二天自动创建
4.10000+表同步时间基本就10几秒 所以可以不用设置为5分钟