mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
增加adapter相关参数: retry timeout batchSize
This commit is contained in:
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.adapter.es;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.transport.client.PreBuiltTransportClient;
|
||||
@@ -13,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
|
||||
import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService;
|
||||
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
|
||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||
@@ -50,7 +51,8 @@ public class ESAdapter implements OuterAdapter {
|
||||
properties.forEach(settingBuilder::put);
|
||||
Settings settings = settingBuilder.build();
|
||||
transportClient = new PreBuiltTransportClient(settings);
|
||||
esSyncService = new ESSyncService(transportClient);
|
||||
ESTemplate esTemplate = new ESTemplate(transportClient);
|
||||
esSyncService = new ESSyncService(esTemplate);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -72,7 +74,9 @@ public class ESAdapter implements OuterAdapter {
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
if (transportClient != null) {
|
||||
transportClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.alibaba.otter.canal.client.adapter.es.service;
|
||||
|
||||
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ESSyncService {
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private TransportClient transportClient;
|
||||
private static Logger logger = LoggerFactory.getLogger(ESSyncService.class);
|
||||
|
||||
public ESSyncService(TransportClient transportClient) {
|
||||
this.transportClient = transportClient;
|
||||
private ESTemplate esTemplate;
|
||||
|
||||
public ESSyncService(ESTemplate esTemplate){
|
||||
this.esTemplate = esTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.alibaba.otter.canal.client.adapter.es.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryAction;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||
|
||||
public class ESTemplate {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ESTemplate.class);
|
||||
|
||||
private static final int MAX_BATCH_SIZE = 1000;
|
||||
|
||||
private TransportClient transportClient;
|
||||
|
||||
public ESTemplate(TransportClient transportClient){
|
||||
this.transportClient = transportClient;
|
||||
}
|
||||
|
||||
public void setTransportClient(TransportClient transportClient) {
|
||||
this.transportClient = transportClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入数据
|
||||
*
|
||||
* @param esSyncConfig
|
||||
* @param pkVal
|
||||
* @param esFieldData
|
||||
* @return
|
||||
*/
|
||||
public boolean insert(ESSyncConfig esSyncConfig, Object pkVal, Map<String, Object> esFieldData) {
|
||||
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||
ESMapping mapping = esSyncConfig.getEsMapping();
|
||||
if (mapping.get_id() != null) {
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareIndex(mapping.get_index(), mapping.get_type(), pkVal.toString())
|
||||
.setSource(esFieldData));
|
||||
} else {
|
||||
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||
.setTypes(mapping.get_type())
|
||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||
.setSize(MAX_BATCH_SIZE)
|
||||
.get();
|
||||
for (SearchHit hit : response.getHits()) {
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), hit.getId()));
|
||||
}
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareIndex(mapping.get_index(), mapping.get_type()).setSource(esFieldData));
|
||||
}
|
||||
return commitBulkRequest(bulkRequestBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键更新数据
|
||||
*
|
||||
* @param esSyncConfig
|
||||
* @param pkVal
|
||||
* @param esFieldData
|
||||
* @return
|
||||
*/
|
||||
public boolean update(ESSyncConfig esSyncConfig, Object pkVal, Map<String, Object> esFieldData) {
|
||||
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||
append4Update(bulkRequestBuilder, esSyncConfig, pkVal, esFieldData);
|
||||
return commitBulkRequest(bulkRequestBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结合 addBulkRequest4Update 批量更新
|
||||
*
|
||||
* @param consumer
|
||||
* @return
|
||||
*/
|
||||
public boolean updateBatch(Consumer<BulkRequestBuilder> consumer) {
|
||||
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||
consumer.accept(bulkRequestBuilder);
|
||||
return commitBulkRequest(bulkRequestBuilder);
|
||||
}
|
||||
|
||||
public void append4Update(BulkRequestBuilder bulkRequestBuilder, ESSyncConfig esSyncConfig, Object pkVal,
|
||||
Map<String, Object> esFieldData) {
|
||||
ESMapping mapping = esSyncConfig.getEsMapping();
|
||||
if (mapping.get_id() != null) {
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), pkVal.toString())
|
||||
.setDoc(esFieldData));
|
||||
} else {
|
||||
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||
.setTypes(mapping.get_type())
|
||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||
.setSize(MAX_BATCH_SIZE)
|
||||
.get();
|
||||
for (SearchHit hit : response.getHits()) { // 理论上只有一条
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
|
||||
.setDoc(esFieldData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update by query
|
||||
*
|
||||
* @param esSyncConfig
|
||||
* @param queryBuilder
|
||||
* @param esFieldData
|
||||
* @return
|
||||
*/
|
||||
private boolean updateByQuery(ESSyncConfig esSyncConfig, QueryBuilder queryBuilder,
|
||||
Map<String, Object> esFieldData) {
|
||||
return updateByQuery(esSyncConfig, queryBuilder, esFieldData, 0);
|
||||
}
|
||||
|
||||
private boolean updateByQuery(ESSyncConfig esSyncConfig, QueryBuilder queryBuilder, Map<String, Object> esFieldData,
|
||||
int counter) {
|
||||
if (CollectionUtils.isEmpty(esFieldData)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ESMapping mapping = esSyncConfig.getEsMapping();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
esFieldData.forEach((key, value) -> {
|
||||
if (value instanceof Map) {
|
||||
HashMap mapValue = (HashMap) value;
|
||||
if (mapValue.containsKey("lon") && mapValue.containsKey("lat") && mapValue.size() == 2) {
|
||||
sb.append("ctx._source")
|
||||
.append("['")
|
||||
.append(key)
|
||||
.append("']")
|
||||
.append(" = [")
|
||||
.append(mapValue.get("lon"))
|
||||
.append(", ")
|
||||
.append(mapValue.get("lat"))
|
||||
.append("];");
|
||||
} else {
|
||||
logger.warn("Unsupported object type for script_update");
|
||||
}
|
||||
} else if (value instanceof String) {
|
||||
sb.append("ctx._source")
|
||||
.append("['")
|
||||
.append(key)
|
||||
.append("']")
|
||||
.append(" = '")
|
||||
.append(value)
|
||||
.append("';");
|
||||
} else {
|
||||
sb.append("ctx._source").append("['").append(key).append("']").append(" = ").append(value).append(";");
|
||||
}
|
||||
});
|
||||
String scriptLine = sb.toString();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(scriptLine);
|
||||
}
|
||||
|
||||
UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE.newRequestBuilder(transportClient);
|
||||
updateByQuery.source(mapping.get_index())
|
||||
.abortOnVersionConflict(false)
|
||||
.filter(queryBuilder)
|
||||
.script(new Script(ScriptType.INLINE, "painless", scriptLine, Collections.emptyMap()));
|
||||
|
||||
BulkByScrollResponse response = updateByQuery.get();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("updateByQuery response: {}", response.getStatus());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(response.getSearchFailures())) {
|
||||
logger.error("script update_for_search has search error: " + response.getBulkFailures());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(response.getBulkFailures())) {
|
||||
logger.error("script update_for_search has update error: " + response.getBulkFailures());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.getStatus().getVersionConflicts() > 0) {
|
||||
if (counter >= 3) {
|
||||
logger.error("第 {} 次执行updateByQuery, 依旧存在版本冲突,不再继续重试。", counter);
|
||||
return false;
|
||||
}
|
||||
logger.warn("本次updateByQuery存在版本冲突,准备重新执行...");
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
return updateByQuery(esSyncConfig, queryBuilder, esFieldData, ++counter);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
* @param esSyncConfig
|
||||
* @param pkVal
|
||||
* @return
|
||||
*/
|
||||
public boolean delete(ESSyncConfig esSyncConfig, Object pkVal) {
|
||||
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||
ESMapping mapping = esSyncConfig.getEsMapping();
|
||||
if (mapping.get_id() != null) {
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), pkVal.toString()));
|
||||
} else {
|
||||
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||
.setTypes(mapping.get_type())
|
||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||
.setSize(MAX_BATCH_SIZE)
|
||||
.get();
|
||||
for (SearchHit hit : response.getHits()) {
|
||||
bulkRequestBuilder
|
||||
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), hit.getId()));
|
||||
}
|
||||
}
|
||||
return commitBulkRequest(bulkRequestBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量提交
|
||||
*
|
||||
* @param bulkRequestBuilder
|
||||
* @return
|
||||
*/
|
||||
private static boolean commitBulkRequest(BulkRequestBuilder bulkRequestBuilder) {
|
||||
if (bulkRequestBuilder.numberOfActions() > 0) {
|
||||
BulkResponse response = bulkRequestBuilder.execute().actionGet();
|
||||
if (response.hasFailures()) {
|
||||
for (BulkItemResponse itemResponse : response.getItems()) {
|
||||
if (!itemResponse.isFailed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemResponse.getFailure().getStatus() == RestStatus.NOT_FOUND) {
|
||||
logger.warn(itemResponse.getFailureMessage());
|
||||
} else {
|
||||
logger.error("ES 同步数据错误 {}", itemResponse.getFailureMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !response.hasFailures();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,6 @@ public class SqlParseTest {
|
||||
|
||||
@Test
|
||||
public void parseTest() {
|
||||
// String sql = "select a.id,d.user_id2, concat(name,'_', a.nick) as name,
|
||||
// b.name as roleNme, d.name as typeName,concat(d.label,'_') as label,"
|
||||
// + " c.name as refName from user a left join role b on b.user_id=a.id "
|
||||
// + "left join type d on d.user_id=a.id and d.user_id2=a.p_id "
|
||||
// + "left join ( select ref_id,group_concat(name,',') as name from role group
|
||||
// by ref_id ) c on c.ref_id=a.id "
|
||||
// + "where a.id=1";
|
||||
String sql = "select a.id, concat(a.name,'_test') as name, a.role_id, b.name as role_name, c.labels from user a "
|
||||
+ "left join role b on a.role_id=b.id "
|
||||
+ "left join (select user_id, group_concat(label,',') as labels from user_label "
|
||||
|
||||
Reference in New Issue
Block a user