同步分布式开关

This commit is contained in:
mcy
2018-10-25 16:43:33 +08:00
parent 42a4abe4c7
commit 6ee6b3c398
10 changed files with 178 additions and 27 deletions

View File

@@ -18,11 +18,6 @@
<artifactId>canal.protocol</artifactId> <artifactId>canal.protocol</artifactId>
<version>${canal_version}</version> <version>${canal_version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency> <dependency>
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>

View File

@@ -8,6 +8,7 @@ import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
@@ -52,7 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
hbaseMapping = MappingConfigLoader.load(); hbaseMapping = MappingConfigLoader.load();
mappingConfigCache = new HashMap<>(); mappingConfigCache = new HashMap<>();
for (MappingConfig mappingConfig : hbaseMapping.values()) { for (MappingConfig mappingConfig : hbaseMapping.values()) {
mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-" mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseOrm().getDestination())
+ "." + mappingConfig.getHbaseOrm().getDatabase() + "."
+ mappingConfig.getHbaseOrm().getTable(), + mappingConfig.getHbaseOrm().getTable(),
mappingConfig); mappingConfig);
} }
@@ -77,9 +79,10 @@ public class HbaseAdapter implements OuterAdapter {
if (dml == null) { if (dml == null) {
return; return;
} }
String destination = StringUtils.trimToEmpty(dml.getDestination());
String database = dml.getDatabase(); String database = dml.getDatabase();
String table = dml.getTable(); String table = dml.getTable();
MappingConfig config = mappingConfigCache.get(database + "-" + table); MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table);
hbaseSyncService.sync(config, dml); hbaseSyncService.sync(config, dml);
} }

View File

@@ -50,6 +50,11 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.10.0</version>
</dependency>
<!-- 单独引入rocketmq依赖 --> <!-- 单独引入rocketmq依赖 -->
<dependency> <dependency>
<groupId>org.apache.rocketmq</groupId> <groupId>org.apache.rocketmq</groupId>

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.adapter.launcher.common; package com.alibaba.otter.canal.adapter.launcher.common;
import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -8,42 +9,114 @@ import java.util.concurrent.TimeoutException;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.apache.commons.lang.StringUtils; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.adapter.launcher.config.CuratorClient;
import com.alibaba.otter.canal.common.utils.BooleanMutex; import com.alibaba.otter.canal.common.utils.BooleanMutex;
@Component @Component
public class SyncSwitch { public class SyncSwitch {
private static final String SYN_SWITCH_ZKNODE = "/sync-switch/";
private static final Map<String, BooleanMutex> LOCAL_LOCK = new ConcurrentHashMap<>(); private static final Map<String, BooleanMutex> LOCAL_LOCK = new ConcurrentHashMap<>();
private Mode mode = Mode.LOCAL; private static final Map<String, BooleanMutex> DISTRIBUTED_LOCK = new ConcurrentHashMap<>();
private static Mode mode = Mode.LOCAL;
@Resource @Resource
private AdapterCanalConfig adapterCanalConfig; private AdapterCanalConfig adapterCanalConfig;
@Resource
private CuratorClient curatorClient;
@PostConstruct @PostConstruct
public void init() { public void init() {
if (StringUtils.isEmpty(adapterCanalConfig.getZookeeperHosts())) { CuratorFramework curator = curatorClient.getCurator();
if (curator != null) {
mode = Mode.DISTRIBUTED;
DISTRIBUTED_LOCK.clear();
for (String destination : adapterCanalConfig.DESTINATIONS) {
// 对应每个destination注册锁
BooleanMutex mutex = new BooleanMutex(true);
initMutex(curator, destination, mutex);
DISTRIBUTED_LOCK.put(destination, mutex);
startListen(destination, mutex);
}
} else {
mode = Mode.LOCAL; mode = Mode.LOCAL;
LOCAL_LOCK.clear(); LOCAL_LOCK.clear();
for (String destination : AdapterCanalConfig.DESTINATIONS) { for (String destination : adapterCanalConfig.DESTINATIONS) {
// 对应每个destination注册锁 // 对应每个destination注册锁
LOCAL_LOCK.put(destination, new BooleanMutex(true)); LOCAL_LOCK.put(destination, new BooleanMutex(true));
} }
}
}
private synchronized void startListen(String destination, BooleanMutex mutex) {
try {
String path = SYN_SWITCH_ZKNODE + destination;
CuratorFramework curator = curatorClient.getCurator();
final NodeCache nodeCache = new NodeCache(curator, path);
nodeCache.start();
nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
private synchronized void initMutex(CuratorFramework curator, String destination, BooleanMutex mutex) {
try {
String path = SYN_SWITCH_ZKNODE + destination;
Stat stat = curator.checkExists().forPath(path);
if (stat == null) {
if (!mutex.state()) {
mutex.set(true);
}
} else { } else {
mode = Mode.DISTRIBUTED; String data = new String(curator.getData().forPath(path), StandardCharsets.UTF_8);
if ("on".equals(data)) {
if (!mutex.state()) {
mutex.set(true);
}
} else {
if (mutex.state()) {
mutex.set(false);
}
}
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} }
} }
public synchronized void off(String destination) { public synchronized void off(String destination) {
if (mode == Mode.LOCAL) { if (mode == Mode.LOCAL) {
BooleanMutex mutex = LOCAL_LOCK.get(destination); BooleanMutex mutex = LOCAL_LOCK.get(destination);
if (mutex != null && mutex.state()) { if (mutex != null && mutex.state()) {
mutex.set(false); mutex.set(false);
} }
} else {
try {
String path = SYN_SWITCH_ZKNODE + destination;
try {
curatorClient.getCurator()
.create()
.creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(path, "off".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
curatorClient.getCurator().setData().forPath(path, "off".getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
} }
@@ -53,14 +126,35 @@ public class SyncSwitch {
if (mutex != null && !mutex.state()) { if (mutex != null && !mutex.state()) {
mutex.set(true); mutex.set(true);
} }
} else {
try {
String path = SYN_SWITCH_ZKNODE + destination;
try {
curatorClient.getCurator()
.create()
.creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(path, "on".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
curatorClient.getCurator().setData().forPath(path, "on".getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
} }
public synchronized void release(String destination) { public synchronized void release(String destination) {
if (mode == Mode.LOCAL) { if (mode == Mode.LOCAL) {
BooleanMutex mutex = LOCAL_LOCK.get(destination); BooleanMutex mutex = LOCAL_LOCK.get(destination);
if (mutex != null && mutex.state()) { if (mutex != null && !mutex.state()) {
mutex.set(false); mutex.set(true);
}
}
if (mode == Mode.DISTRIBUTED) {
BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination);
if (mutex != null && !mutex.state()) {
mutex.set(true);
} }
} }
} }
@@ -73,9 +167,15 @@ public class SyncSwitch {
} else { } else {
return null; return null;
} }
} } else {
BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination);
if (mutex != null) {
return mutex.state();
} else {
return null; return null;
} }
}
}
public void get(String destination) throws InterruptedException { public void get(String destination) throws InterruptedException {
if (mode == Mode.LOCAL) { if (mode == Mode.LOCAL) {
@@ -83,6 +183,11 @@ public class SyncSwitch {
if (mutex != null) { if (mutex != null) {
mutex.get(); mutex.get();
} }
} else {
BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination);
if (mutex != null) {
mutex.get();
}
} }
} }
@@ -92,6 +197,11 @@ public class SyncSwitch {
if (mutex != null) { if (mutex != null) {
mutex.get(timeout, unit); mutex.get(timeout, unit);
} }
} else {
BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination);
if (mutex != null) {
mutex.get(timeout, unit);
}
} }
} }

View File

@@ -13,7 +13,7 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
@ConfigurationProperties(prefix = "canal.conf") @ConfigurationProperties(prefix = "canal.conf")
public class AdapterCanalConfig extends CanalClientConfig { public class AdapterCanalConfig extends CanalClientConfig {
public static final Set<String> DESTINATIONS = new LinkedHashSet<>(); public final Set<String> DESTINATIONS = new LinkedHashSet<>();
@Override @Override
public void setCanalInstances(List<CanalInstance> canalInstances) { public void setCanalInstances(List<CanalInstance> canalInstances) {

View File

@@ -0,0 +1,36 @@
package com.alibaba.otter.canal.adapter.launcher.config;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.stereotype.Component;
@Component
public class CuratorClient {
@Resource
private AdapterCanalConfig adapterCanalConfig;
private CuratorFramework curator = null;
@PostConstruct
public void init() {
if (adapterCanalConfig.getZookeeperHosts() != null) {
curator = CuratorFrameworkFactory.builder()
.connectString(adapterCanalConfig.getZookeeperHosts())
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.sessionTimeoutMs(6000)
.connectionTimeoutMs(3000)
.namespace("canal-adapter")
.build();
curator.start();
}
}
public CuratorFramework getCurator() {
return curator;
}
}

View File

@@ -15,8 +15,8 @@ import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
/** /**
* 外部适配器的加载器 * 外部适配器的加载器
@@ -66,10 +66,12 @@ public class CanalAdapterLoader {
canalOuterAdapterGroups.add(canalOutConnectors); canalOuterAdapterGroups.add(canalOutConnectors);
} }
CanalAdapterWorker worker; CanalAdapterWorker worker;
if (zkHosts != null) { if (sa != null) {
worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups);
} else if (zkHosts != null) {
worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups);
} else { } else {
worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); throw new RuntimeException("No canal server connector found");
} }
canalWorkers.put(instance.getInstance(), worker); canalWorkers.put(instance.getInstance(), worker);
worker.start(); worker.start();

View File

@@ -54,8 +54,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", "");
((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT);
// super.initSwitcher(canalDestination);
} }
@Override @Override

View File

@@ -26,6 +26,9 @@ public class CommonRest {
@Resource @Resource
private SyncSwitch syncSwitch; private SyncSwitch syncSwitch;
@Resource
private AdapterCanalConfig adapterCanalConfig;
@PostConstruct @PostConstruct
public void init() { public void init() {
loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class); loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class);
@@ -82,7 +85,7 @@ public class CommonRest {
@GetMapping("/destinations") @GetMapping("/destinations")
public List<Map<String, String>> destinations() { public List<Map<String, String>> destinations() {
List<Map<String, String>> result = new ArrayList<>(); List<Map<String, String>> result = new ArrayList<>();
Set<String> destinations = AdapterCanalConfig.DESTINATIONS; Set<String> destinations = adapterCanalConfig.DESTINATIONS;
for (String destination : destinations) { for (String destination : destinations) {
Map<String, String> resMap = new LinkedHashMap<>(); Map<String, String> resMap = new LinkedHashMap<>();
Boolean status = syncSwitch.status(destination); Boolean status = syncSwitch.status(destination);

View File

@@ -16,14 +16,14 @@ hbase.zookeeper.znode.parent: /hbase-unsecure
canal.conf: canal.conf:
canalServerHost: 127.0.0.1:11111 canalServerHost: 127.0.0.1:11111
# zookeeperHosts: slave1:2181 zookeeperHosts: slave1:2181
# bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876 # bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876
flatMessage: true flatMessage: true
canalInstances: canalInstances:
- instance: example - instance: example
adapterGroups: adapterGroups:
- outAdapters: - outAdapters:
- name: logger # - name: logger
- name: hbase - name: hbase
properties: properties:
hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} hbase.zookeeper.quorum: ${hbase.zookeeper.quorum}
@@ -45,4 +45,3 @@ adapter.conf:
password: 121212 password: 121212
adapterConfigs: adapterConfigs:
- hbase/mytest_person2.yml - hbase/mytest_person2.yml
# - hbase/mytest.person2@defaultDS