This commit is contained in:
agapple
2014-09-23 15:11:35 +08:00
commit a0defe575b
331 changed files with 53031 additions and 0 deletions

69
common/pom.xml Normal file
View File

@@ -0,0 +1,69 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.common</artifactId>
<packaging>jar</packaging>
<name>canal common module for otter ${project.version}</name>
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
<dependencies>
<!-- zk -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<!-- external -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- log -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
package com.alibaba.otter.canal.common;
/**
* 基本实现
*
* @author jianghang 2012-7-12 上午10:11:07
* @version 1.0.0
*/
public abstract class AbstractCanalLifeCycle implements CanalLifeCycle {
protected volatile boolean running = false; // 是否处于运行中
public boolean isStart() {
return running;
}
public void start() {
if (running) {
throw new CanalException(this.getClass().getName() + " has startup , don't repeat start");
}
running = true;
}
public void stop() {
if (!running) {
throw new CanalException(this.getClass().getName() + " isn't start , please check");
}
running = false;
}
}

View File

@@ -0,0 +1,37 @@
package com.alibaba.otter.canal.common;
import org.apache.commons.lang.exception.NestableRuntimeException;
/**
* @author jianghang 2012-7-12 上午10:10:31
* @version 1.0.0
*/
public class CanalException extends NestableRuntimeException {
private static final long serialVersionUID = -654893533794556357L;
public CanalException(String errorCode){
super(errorCode);
}
public CanalException(String errorCode, Throwable cause){
super(errorCode, cause);
}
public CanalException(String errorCode, String errorDesc){
super(errorCode + ":" + errorDesc);
}
public CanalException(String errorCode, String errorDesc, Throwable cause){
super(errorCode + ":" + errorDesc, cause);
}
public CanalException(Throwable cause){
super(cause);
}
public Throwable fillInStackTrace() {
return this;
}
}

View File

@@ -0,0 +1,14 @@
package com.alibaba.otter.canal.common;
/**
* @author jianghang 2012-7-12 上午09:39:33
* @version 1.0.0
*/
public interface CanalLifeCycle {
public void start();
public void stop();
public boolean isStart();
}

View File

@@ -0,0 +1,21 @@
package com.alibaba.otter.canal.common.alarm;
import com.alibaba.otter.canal.common.CanalLifeCycle;
/**
* canal报警处理机制
*
* @author jianghang 2012-8-22 下午10:08:56
* @version 1.0.0
*/
public interface CanalAlarmHandler extends CanalLifeCycle {
/**
* 发送对应destination的报警
*
* @param destination
* @param title
* @param msg
*/
public void sendAlarm(String destination, String msg);
}

View File

@@ -0,0 +1,22 @@
package com.alibaba.otter.canal.common.alarm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
/**
* 基于log的alarm机制实现
*
* @author jianghang 2012-8-22 下午10:12:35
* @version 1.0.0
*/
public class LogAlarmHandler extends AbstractCanalLifeCycle implements CanalAlarmHandler {
private static final Logger logger = LoggerFactory.getLogger(LogAlarmHandler.class);
public void sendAlarm(String destination, String msg) {
logger.error("destination:{}[{}]", new Object[] { destination, msg });
}
}

View File

@@ -0,0 +1,95 @@
package com.alibaba.otter.canal.common.utils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.util.Enumeration;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddressUtils {
private static final Logger logger = LoggerFactory.getLogger(AddressUtils.class);
private static final String LOCALHOST_IP = "127.0.0.1";
private static final String EMPTY_IP = "0.0.0.0";
private static final Pattern IP_PATTERN = Pattern.compile("[0-9]{1,3}(\\.[0-9]{1,3}){3,}");
public static boolean isAvailablePort(int port) {
ServerSocket ss = null;
try {
ss = new ServerSocket(port);
ss.bind(null);
return true;
} catch (IOException e) {
return false;
} finally {
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
}
}
}
}
private static boolean isValidHostAddress(InetAddress address) {
if (address == null || address.isLoopbackAddress()) return false;
String name = address.getHostAddress();
return (name != null && !EMPTY_IP.equals(name) && !LOCALHOST_IP.equals(name) && IP_PATTERN.matcher(name).matches());
}
public static String getHostIp() {
InetAddress address = getHostAddress();
return address == null ? null : address.getHostAddress();
}
public static String getHostName() {
InetAddress address = getHostAddress();
return address == null ? null : address.getHostName();
}
public static InetAddress getHostAddress() {
InetAddress localAddress = null;
try {
localAddress = InetAddress.getLocalHost();
if (isValidHostAddress(localAddress)) {
return localAddress;
}
} catch (Throwable e) {
logger.warn("Failed to retriving local host ip address, try scan network card ip address. cause: "
+ e.getMessage());
}
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
try {
NetworkInterface network = interfaces.nextElement();
Enumeration<InetAddress> addresses = network.getInetAddresses();
if (addresses != null) {
while (addresses.hasMoreElements()) {
try {
InetAddress address = addresses.nextElement();
if (isValidHostAddress(address)) {
return address;
}
} catch (Throwable e) {
logger.warn("Failed to retriving network card ip address. cause:" + e.getMessage());
}
}
}
} catch (Throwable e) {
logger.warn("Failed to retriving network card ip address. cause:" + e.getMessage());
}
}
}
} catch (Throwable e) {
logger.warn("Failed to retriving network card ip address. cause:" + e.getMessage());
}
logger.error("Could not get local host ip address, will use 127.0.0.1 instead.");
return localAddress;
}
}

View File

@@ -0,0 +1,157 @@
package com.alibaba.otter.canal.common.utils;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* 实现一个互斥实现,基于Cocurrent中的{@linkplain AbstractQueuedSynchronizer}实现了自己的sync <br/>
* 应用场景:系统初始化/授权控制,没权限时阻塞等待。有权限时所有线程都可以快速通过
*
* <pre>
* false : 代表需要被阻塞挂起,等待mutex变为true被唤醒
* true : 唤醒被阻塞在false状态下的thread
*
* BooleanMutex mutex = new BooleanMutex(true);
* try {
* mutex.get(); //当前状态为true, 不会被阻塞
* } catch (InterruptedException e) {
* // do something
* }
*
* mutex.set(false);
* try {
* mutex.get(); //当前状态为false, 会被阻塞直到另一个线程调用mutex.set(true);
* } catch (InterruptedException e) {
* // do something
* }
* </pre>
*
* @author jianghang 2011-9-23 上午09:58:03
* @version 1.0.0
*/
public class BooleanMutex {
private Sync sync;
public BooleanMutex(){
sync = new Sync();
set(false);
}
public BooleanMutex(Boolean mutex){
sync = new Sync();
set(mutex);
}
/**
* 阻塞等待Boolean为true
*
* @throws InterruptedException
*/
public void get() throws InterruptedException {
sync.innerGet();
}
/**
* 阻塞等待Boolean为true,允许设置超时时间
*
* @param timeout
* @param unit
* @throws InterruptedException
* @throws TimeoutException
*/
public void get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
sync.innerGet(unit.toNanos(timeout));
}
/**
* 重新设置对应的Boolean mutex
*
* @param mutex
*/
public void set(Boolean mutex) {
if (mutex) {
sync.innerSetTrue();
} else {
sync.innerSetFalse();
}
}
public boolean state() {
return sync.innerState();
}
/**
* Synchronization control for BooleanMutex. Uses AQS sync state to
* represent run status
*/
private final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 2559471934544126329L;
/** State value representing that TRUE */
private static final int TRUE = 1;
/** State value representing that FALSE */
private static final int FALSE = 2;
private boolean isTrue(int state) {
return (state & TRUE) != 0;
}
/**
* 实现AQS的接口,获取共享锁的判断
*/
protected int tryAcquireShared(int state) {
// 如果为true,直接允许获取锁对象
// 如果为false,进入阻塞队列,等待被唤醒
return isTrue(getState()) ? 1 : -1;
}
/**
* 实现AQS的接口,释放共享锁的判断
*/
protected boolean tryReleaseShared(int ignore) {
// 始终返回true,代表可以release
return true;
}
boolean innerState() {
return isTrue(getState());
}
void innerGet() throws InterruptedException {
acquireSharedInterruptibly(0);
}
void innerGet(long nanosTimeout) throws InterruptedException, TimeoutException {
if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException();
}
void innerSetTrue() {
for (;;) {
int s = getState();
if (s == TRUE) {
return; // 直接退出
}
if (compareAndSetState(s, TRUE)) {// cas更新状态,避免并发更新true操作
releaseShared(0);// 释放一下锁对象,唤醒一下阻塞的Thread
return;
}
}
}
void innerSetFalse() {
for (;;) {
int s = getState();
if (s == FALSE) {
return; // 直接退出
}
if (compareAndSetState(s, FALSE)) {// cas更新状态,避免并发更新false操作
return;
}
}
}
}
}

View File

@@ -0,0 +1,80 @@
package com.alibaba.otter.canal.common.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* Otter项目内部使用的ToStringStyle
*
* <pre>
* 默认Style输出格式:
* Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
* </pre>
*
* @author jianghang 2010-6-18 上午11:35:27
*/
public class CanalToStringStyle extends ToStringStyle {
private static final long serialVersionUID = -6568177374288222145L;
private static final String DEFAULT_TIME = "yyyy-MM-dd HH:mm:ss";
private static final String DEFAULT_DAY = "yyyy-MM-dd";
/**
* <pre>
* 输出格式:
* Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
* </pre>
*/
public static final ToStringStyle TIME_STYLE = new OtterDateStyle(DEFAULT_TIME);
/**
* <pre>
* 输出格式:
* Person[name=John Doe,age=33,smoker=false ,day=2010-04-01]
* </pre>
*/
public static final ToStringStyle DAY_STYLE = new OtterDateStyle(DEFAULT_DAY);
/**
* <pre>
* 输出格式:
* Person[name=John Doe,age=33,smoker=false ,time=2010-04-01 00:00:00]
* </pre>
*/
public static final ToStringStyle DEFAULT_STYLE = CanalToStringStyle.TIME_STYLE;
// =========================== 自定义style =============================
/**
* 支持日期格式化的ToStringStyle
*
* @author li.jinl
*/
private static class OtterDateStyle extends ToStringStyle {
private static final long serialVersionUID = 5208917932254652886L;
// 日期format格式
private String pattern;
public OtterDateStyle(String pattern){
super();
this.setUseShortClassName(true);
this.setUseIdentityHashCode(false);
// 设置日期format格式
this.pattern = pattern;
}
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
// 增加自定义的date对象处理
if (value instanceof Date) {
value = new SimpleDateFormat(pattern).format(value);
}
// 后续可以增加其他自定义对象处理
buffer.append(value);
}
}
}

View File

@@ -0,0 +1,73 @@
package com.alibaba.otter.canal.common.utils;
import java.util.Arrays;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
/**
* 字节处理相关工具类
*
* @author jianghang
*/
public class JsonUtils {
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
}
public static <T> T unmarshalFromByte(byte[] bytes, TypeReference<T> type) {
return (T) JSON.parseObject(bytes, type.getType());
}
public static byte[] marshalToByte(Object obj) {
return JSON.toJSONBytes(obj); // 默认为UTF-8
}
public static byte[] marshalToByte(Object obj, SerializerFeature... features) {
return JSON.toJSONBytes(obj, features); // 默认为UTF-8
}
public static <T> T unmarshalFromString(String json, Class<T> targetClass) {
return (T) JSON.parseObject(json, targetClass);// 默认为UTF-8
}
public static <T> T unmarshalFromString(String json, TypeReference<T> type) {
return (T) JSON.parseObject(json, type);// 默认为UTF-8
}
public static String marshalToString(Object obj) {
return JSON.toJSONString(obj); // 默认为UTF-8
}
public static String marshalToString(Object obj, SerializerFeature... features) {
return JSON.toJSONString(obj, features); // 默认为UTF-8
}
/**
* 可以允许指定一些过滤字段进行生成json对象
*/
public static String marshalToString(Object obj, String... fliterFields) {
final List<String> propertyFliters = Arrays.asList(fliterFields);
SerializeWriter out = new SerializeWriter();
try {
JSONSerializer serializer = new JSONSerializer(out);
serializer.getPropertyFilters().add(new PropertyFilter() {
public boolean apply(Object source, String name, Object value) {
return !propertyFliters.contains(name);
}
});
serializer.write(obj);
return out.toString();
} finally {
out.close();
}
}
}

View File

@@ -0,0 +1,56 @@
package com.alibaba.otter.canal.common.utils;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author zebin.xuzb 2012-9-20 下午3:47:47
* @version 1.0.0
*/
public class NamedThreadFactory implements ThreadFactory {
private static final Logger logger = LoggerFactory.getLogger(NamedThreadFactory.class);
final private static String DEFAULT_NAME = "canal-worker";
final private String name;
final private boolean daemon;
final private ThreadGroup group;
final private AtomicInteger threadNumber = new AtomicInteger(0);
final static UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() {
public void uncaughtException(Thread t,
Throwable e) {
logger.error("from " + t.getName(), e);
}
};
public NamedThreadFactory(){
this(DEFAULT_NAME, true);
}
public NamedThreadFactory(String name){
this(name, true);
}
public NamedThreadFactory(String name, boolean daemon){
this.name = name;
this.daemon = daemon;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, name + "-" + threadNumber.getAndIncrement(), 0);
t.setDaemon(daemon);
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return t;
}
}

View File

@@ -0,0 +1,79 @@
package com.alibaba.otter.canal.common.utils;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.apache.commons.lang.StringUtils;
/**
* @author zebin.xuzb 2012-11-15 下午3:53:09
* @since 1.0.0
*/
public final class UriUtils {
private final static String SPLIT = "&";
private final static String EQUAL = "=";
private final static String DEFAULT_ENCODING = "ISO_8859_1";
private UriUtils(){
}
public static Map<String, String> parseQuery(final String uriString) {
URI uri = null;
try {
uri = new URI(uriString);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
return parseQuery(uri);
}
public static Map<String, String> parseQuery(final String uriString, final String encoding) {
URI uri = null;
try {
uri = new URI(uriString);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
return parseQuery(uri, encoding);
}
public static Map<String, String> parseQuery(final URI uri) {
return parseQuery(uri, DEFAULT_ENCODING);
}
public static Map<String, String> parseQuery(final URI uri, final String encoding) {
if (uri == null || StringUtils.isBlank(uri.getQuery())) {
return Collections.EMPTY_MAP;
}
String query = uri.getRawQuery();
HashMap<String, String> params = new HashMap<String, String>();
Scanner scan = new Scanner(query);
scan.useDelimiter(SPLIT);
while (scan.hasNext()) {
String token = scan.next().trim();
String[] pair = token.split(EQUAL);
String key = decode(pair[0], encoding);
String value = null;
if (pair.length == 2) {
value = decode(pair[1], encoding);
}
params.put(key, value);
}
return params;
}
private static String decode(final String content, final String encoding) {
try {
return URLDecoder.decode(content, encoding != null ? encoding : DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.otter.canal.common.zookeeper;
import java.io.UnsupportedEncodingException;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
/**
* 基于string的序列化方式
*
* @author jianghang 2012-7-11 下午02:57:09
* @version 1.0.0
*/
public class ByteSerializer implements ZkSerializer {
public Object deserialize(final byte[] bytes) throws ZkMarshallingError {
return bytes;
}
public byte[] serialize(final Object data) throws ZkMarshallingError {
try {
if (data instanceof byte[]) {
return (byte[]) data;
} else {
return ((String) data).getBytes("utf-8");
}
} catch (final UnsupportedEncodingException e) {
throw new ZkMarshallingError(e);
}
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.otter.canal.common.zookeeper;
import java.io.UnsupportedEncodingException;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
/**
* 基于string的序列化方式
*
* @author jianghang 2012-7-11 下午02:57:09
* @version 1.0.0
*/
public class StringSerializer implements ZkSerializer {
public Object deserialize(final byte[] bytes) throws ZkMarshallingError {
try {
return new String(bytes, "utf-8");
} catch (final UnsupportedEncodingException e) {
throw new ZkMarshallingError(e);
}
}
public byte[] serialize(final Object data) throws ZkMarshallingError {
try {
return ((String) data).getBytes("utf-8");
} catch (final UnsupportedEncodingException e) {
throw new ZkMarshallingError(e);
}
}
}

View File

@@ -0,0 +1,146 @@
package com.alibaba.otter.canal.common.zookeeper;
import java.util.Map;
import org.I0Itec.zkclient.IZkConnection;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkException;
import org.I0Itec.zkclient.exception.ZkInterruptedException;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.zookeeper.CreateMode;
import com.google.common.base.Function;
import com.google.common.collect.MapMaker;
/**
* 使用自定义的ZooKeeperx for zk connection
*
* @author jianghang 2012-7-10 下午02:31:15
* @version 1.0.0
*/
public class ZkClientx extends ZkClient {
// 对于zkclient进行一次缓存,避免一个jvm内部使用多个zk connection
private static Map<String, ZkClientx> clients = new MapMaker().makeComputingMap(new Function<String, ZkClientx>() {
public ZkClientx apply(String servers) {
return new ZkClientx(servers);
}
});
public static ZkClientx getZkClient(String servers) {
return clients.get(servers);
}
public ZkClientx(String serverstring){
this(serverstring, Integer.MAX_VALUE);
}
public ZkClientx(String zkServers, int connectionTimeout){
this(new ZooKeeperx(zkServers), connectionTimeout);
}
public ZkClientx(String zkServers, int sessionTimeout, int connectionTimeout){
this(new ZooKeeperx(zkServers, sessionTimeout), connectionTimeout);
}
public ZkClientx(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer){
this(new ZooKeeperx(zkServers, sessionTimeout), connectionTimeout, zkSerializer);
}
private ZkClientx(IZkConnection connection, int connectionTimeout){
this(connection, connectionTimeout, new ByteSerializer());
}
private ZkClientx(IZkConnection zkConnection, int connectionTimeout, ZkSerializer zkSerializer){
super(zkConnection, connectionTimeout, zkSerializer);
}
/**
* Create a persistent Sequential node.
*
* @param path
* @param createParents if true all parent dirs are created as well and no {@link ZkNodeExistsException} is thrown
* in case the path already exists
* @throws ZkInterruptedException if operation was interrupted, or a required reconnection got interrupted
* @throws IllegalArgumentException if called from anything except the ZooKeeper event thread
* @throws ZkException if any ZooKeeper exception occurred
* @throws RuntimeException if any other exception occurs
*/
public String createPersistentSequential(String path, boolean createParents) throws ZkInterruptedException,
IllegalArgumentException, ZkException,
RuntimeException {
try {
return create(path, null, CreateMode.PERSISTENT_SEQUENTIAL);
} catch (ZkNoNodeException e) {
if (!createParents) {
throw e;
}
String parentDir = path.substring(0, path.lastIndexOf('/'));
createPersistent(parentDir, createParents);
return createPersistentSequential(path, createParents);
}
}
/**
* Create a persistent Sequential node.
*
* @param path
* @param data
* @param createParents if true all parent dirs are created as well and no {@link ZkNodeExistsException} is thrown
* in case the path already exists
* @throws ZkInterruptedException if operation was interrupted, or a required reconnection got interrupted
* @throws IllegalArgumentException if called from anything except the ZooKeeper event thread
* @throws ZkException if any ZooKeeper exception occurred
* @throws RuntimeException if any other exception occurs
*/
public String createPersistentSequential(String path, Object data, boolean createParents)
throws ZkInterruptedException,
IllegalArgumentException,
ZkException,
RuntimeException {
try {
return create(path, data, CreateMode.PERSISTENT_SEQUENTIAL);
} catch (ZkNoNodeException e) {
if (!createParents) {
throw e;
}
String parentDir = path.substring(0, path.lastIndexOf('/'));
createPersistent(parentDir, createParents);
return createPersistentSequential(path, data, createParents);
}
}
/**
* Create a persistent Sequential node.
*
* @param path
* @param data
* @param createParents if true all parent dirs are created as well and no {@link ZkNodeExistsException} is thrown
* in case the path already exists
* @throws ZkInterruptedException if operation was interrupted, or a required reconnection got interrupted
* @throws IllegalArgumentException if called from anything except the ZooKeeper event thread
* @throws ZkException if any ZooKeeper exception occurred
* @throws RuntimeException if any other exception occurs
*/
public void createPersistent(String path, Object data, boolean createParents) throws ZkInterruptedException,
IllegalArgumentException, ZkException,
RuntimeException {
try {
create(path, data, CreateMode.PERSISTENT);
} catch (ZkNodeExistsException e) {
if (!createParents) {
throw e;
}
} catch (ZkNoNodeException e) {
if (!createParents) {
throw e;
}
String parentDir = path.substring(0, path.lastIndexOf('/'));
createPersistent(parentDir, createParents);
createPersistent(path, data, createParents);
}
}
}

View File

@@ -0,0 +1,181 @@
package com.alibaba.otter.canal.common.zookeeper;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.I0Itec.zkclient.IZkConnection;
import org.I0Itec.zkclient.exception.ZkException;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.ClientCnxn;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.client.ConnectStringParser;
import org.apache.zookeeper.client.HostProvider;
import org.apache.zookeeper.client.StaticHostProvider;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
/**
* 封装了ZooKeeper,使其支持节点的优先顺序,比如美国机房的节点会优先加载美国对应的zk集群列表,都失败后才会选择加载杭州的zk集群列表 *
*
* @author jianghang 2012-7-10 下午02:31:42
* @version 1.0.0
*/
public class ZooKeeperx implements IZkConnection {
private static final String SERVER_COMMA = ";";
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
private static final Field clientCnxnField = ReflectionUtils.findField(ZooKeeper.class, "cnxn");
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
"serverAddresses");
private static final int DEFAULT_SESSION_TIMEOUT = 90000;
private ZooKeeper _zk = null;
private Lock _zookeeperLock = new ReentrantLock();
private final List<String> _servers;
private final int _sessionTimeOut;
public ZooKeeperx(String zkServers){
this(zkServers, DEFAULT_SESSION_TIMEOUT);
}
public ZooKeeperx(String zkServers, int sessionTimeOut){
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA));
_sessionTimeOut = sessionTimeOut;
}
@Override
public void connect(Watcher watcher) {
_zookeeperLock.lock();
try {
if (_zk != null) {
throw new IllegalStateException("zk client has already been started");
}
try {
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
configMutliCluster(_zk);
} catch (IOException e) {
throw new ZkException("Unable to connect to " + _servers, e);
}
} finally {
_zookeeperLock.unlock();
}
}
public void close() throws InterruptedException {
_zookeeperLock.lock();
try {
if (_zk != null) {
logger.debug("Closing ZooKeeper connected to " + _servers);
_zk.close();
_zk = null;
}
} finally {
_zookeeperLock.unlock();
}
}
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
}
public void delete(String path) throws InterruptedException, KeeperException {
_zk.delete(path, -1);
}
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
return _zk.exists(path, watch) != null;
}
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
InterruptedException {
return _zk.getChildren(path, watch);
}
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
return _zk.getData(path, watch, stat);
}
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
writeData(path, data, -1);
}
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
_zk.setData(path, data, version);
}
public States getZookeeperState() {
return _zk != null ? _zk.getState() : null;
}
public ZooKeeper getZookeeper() {
return _zk;
}
public long getCreateTime(String path) throws KeeperException, InterruptedException {
Stat stat = _zk.exists(path, false);
if (stat != null) {
return stat.getCtime();
}
return -1;
}
public String getServers() {
return StringUtils.join(_servers, SERVER_COMMA);
}
// ===============================
public void configMutliCluster(ZooKeeper zk) {
if (_servers.size() == 1) {
return;
}
String cluster1 = _servers.get(0);
try {
if (_servers.size() > 1) {
// 强制的声明accessible
ReflectionUtils.makeAccessible(clientCnxnField);
ReflectionUtils.makeAccessible(hostProviderField);
ReflectionUtils.makeAccessible(serverAddressesField);
// 添加第二组集群列表
for (int i = 1; i < _servers.size(); i++) {
String cluster = _servers.get(i);
// 强制获取zk中的地址信息
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
List<InetSocketAddress> serverAddrs = (List<InetSocketAddress>) ReflectionUtils.getField(
serverAddressesField,
hostProvider);
// 添加第二组集群列表
serverAddrs.addAll(new ConnectStringParser(cluster).getServerAddresses());
}
}
} catch (Exception e) {
try {
if (zk != null) {
zk.close();
}
} catch (InterruptedException ie) {
// ignore interrupt
}
throw new ZkException("zookeeper_create_error, serveraddrs=" + cluster1, e);
}
}
}

View File

@@ -0,0 +1,181 @@
package com.alibaba.otter.canal.common.zookeeper;
import java.text.MessageFormat;
import org.apache.commons.lang.StringUtils;
/**
* 存储结构:
*
* <pre>
* /otter
* canal
* cluster
* destinations
* dest1
* running (EPHEMERAL)
* cluster
* client1
* running (EPHEMERAL)
* cluster
* filter
* cursor
* mark
* 1
* 2
* 3
* </pre>
*
* @author zebin.xuzb @ 2012-6-21
* @version 1.0.0
*/
public class ZookeeperPathUtils {
public static final String ZOOKEEPER_SEPARATOR = "/";
public static final String OTTER_ROOT_NODE = ZOOKEEPER_SEPARATOR + "otter";
public static final String CANAL_ROOT_NODE = OTTER_ROOT_NODE + ZOOKEEPER_SEPARATOR
+ "canal";
public static final String DESTINATION_ROOT_NODE = CANAL_ROOT_NODE + ZOOKEEPER_SEPARATOR
+ "destinations";
public static final String FILTER_NODE = "filter";
public static final String BATCH_MARK_NODE = "mark";
public static final String PARSE_NODE = "parse";
public static final String CURSOR_NODE = "cursor";
public static final String RUNNING_NODE = "running";
public static final String CLUSTER_NODE = "cluster";
public static final String DESTINATION_NODE = DESTINATION_ROOT_NODE
+ ZOOKEEPER_SEPARATOR + "{0}";
public static final String DESTINATION_PARSE_NODE = DESTINATION_NODE + ZOOKEEPER_SEPARATOR
+ PARSE_NODE;
public static final String DESTINATION_CLIENTID_NODE = DESTINATION_NODE + ZOOKEEPER_SEPARATOR
+ "{1}";
public static final String DESTINATION_CURSOR_NODE = DESTINATION_CLIENTID_NODE
+ ZOOKEEPER_SEPARATOR + CURSOR_NODE;
public static final String DESTINATION_CLIENTID_FILTER_NODE = DESTINATION_CLIENTID_NODE
+ ZOOKEEPER_SEPARATOR + FILTER_NODE;
public static final String DESTINATION_CLIENTID_BATCH_MARK_NODE = DESTINATION_CLIENTID_NODE
+ ZOOKEEPER_SEPARATOR + BATCH_MARK_NODE;
public static final String DESTINATION_CLIENTID_BATCH_MARK_WITH_ID_PATH = DESTINATION_CLIENTID_BATCH_MARK_NODE
+ ZOOKEEPER_SEPARATOR + "{2}";
/**
* 服务端当前正在提供服务的running节点
*/
public static final String DESTINATION_RUNNING_NODE = DESTINATION_NODE + ZOOKEEPER_SEPARATOR
+ RUNNING_NODE;
/**
* 客户端当前正在工作的running节点
*/
public static final String DESTINATION_CLIENTID_RUNNING_NODE = DESTINATION_CLIENTID_NODE
+ ZOOKEEPER_SEPARATOR + RUNNING_NODE;
/**
* 整个canal server的集群列表
*/
public static final String CANAL_CLUSTER_ROOT_NODE = CANAL_ROOT_NODE + ZOOKEEPER_SEPARATOR
+ CLUSTER_NODE;
public static final String CANAL_CLUSTER_NODE = CANAL_CLUSTER_ROOT_NODE
+ ZOOKEEPER_SEPARATOR + "{0}";
/**
* 针对某个destination的工作的集群列表
*/
public static final String DESTINATION_CLUSTER_ROOT = DESTINATION_NODE + ZOOKEEPER_SEPARATOR
+ CLUSTER_NODE;
public static final String DESTINATION_CLUSTER_NODE = DESTINATION_CLUSTER_ROOT
+ ZOOKEEPER_SEPARATOR + "{1}";
public static String getDestinationPath(String destinationName) {
return MessageFormat.format(DESTINATION_NODE, destinationName);
}
public static String getClientIdNodePath(String destinationName, short clientId) {
return MessageFormat.format(DESTINATION_CLIENTID_NODE, destinationName, String.valueOf(clientId));
}
public static String getFilterPath(String destinationName, short clientId) {
return MessageFormat.format(DESTINATION_CLIENTID_FILTER_NODE, destinationName, String.valueOf(clientId));
}
public static String getBatchMarkPath(String destinationName, short clientId) {
return MessageFormat.format(DESTINATION_CLIENTID_BATCH_MARK_NODE, destinationName, String.valueOf(clientId));
}
public static String getBatchMarkWithIdPath(String destinationName, short clientId, Long batchId) {
return MessageFormat.format(DESTINATION_CLIENTID_BATCH_MARK_WITH_ID_PATH, destinationName,
String.valueOf(clientId), getBatchMarkNode(batchId));
}
public static String getCursorPath(String destination, short clientId) {
return MessageFormat.format(DESTINATION_CURSOR_NODE, destination, String.valueOf(clientId));
}
public static String getCanalClusterNode(String node) {
return MessageFormat.format(CANAL_CLUSTER_NODE, node);
}
/**
* 服务端当前正在提供服务的running节点
*/
public static String getDestinationServerRunning(String destination) {
return MessageFormat.format(DESTINATION_RUNNING_NODE, destination);
}
/**
* 客户端当前正在工作的running节点
*/
public static String getDestinationClientRunning(String destination, short clientId) {
return MessageFormat.format(DESTINATION_CLIENTID_RUNNING_NODE, destination, String.valueOf(clientId));
}
public static String getDestinationClusterNode(String destination, String node) {
return MessageFormat.format(DESTINATION_CLUSTER_NODE, destination, node);
}
public static String getDestinationClusterRoot(String destination) {
return MessageFormat.format(DESTINATION_CLUSTER_ROOT, destination);
}
public static String getParsePath(String destination) {
return MessageFormat.format(DESTINATION_PARSE_NODE, destination);
}
/**
* 将batchNode转换为Long
*/
public static short getClientId(String clientNode) {
return Short.valueOf(clientNode);
}
/**
* 将batchNode转换为Long
*/
public static long getBatchMarkId(String batchMarkNode) {
return Long.valueOf(batchMarkNode);
}
/**
* 将batchId转化为zookeeper中的node名称
*/
public static String getBatchMarkNode(Long batchId) {
return StringUtils.leftPad(String.valueOf(batchId.intValue()), 10, '0');
}
}

View File

@@ -0,0 +1,59 @@
package com.alibaba.otter.canal.common.zookeeper.running;
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
import com.alibaba.otter.canal.common.utils.CanalToStringStyle;
/**
* 服务端running状态信息
*
* @author jianghang 2012-11-22 下午03:11:30
* @version 1.0.0
*/
public class ServerRunningData implements Serializable {
private static final long serialVersionUID = 92260481691855281L;
private Long cid;
private String address;
private boolean active = true;
public ServerRunningData(){
}
public ServerRunningData(Long cid, String address){
this.cid = cid;
this.address = address;
}
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public String toString() {
return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE);
}
}

View File

@@ -0,0 +1,31 @@
package com.alibaba.otter.canal.common.zookeeper.running;
/**
* 触发一下mainstem发生切换
*
* @author jianghang 2012-9-11 下午02:26:03
* @version 1.0.0
*/
public interface ServerRunningListener {
/**
* 启动时回调做点事情
*/
public void processStart();
/**
* 关闭时回调做点事情
*/
public void processStop();
/**
* 触发现在轮到自己做为active,需要载入上一个active的上下文数据
*/
public void processActiveEnter();
/**
* 触发一下当前active模式失败
*/
public void processActiveExit();
}

View File

@@ -0,0 +1,273 @@
package com.alibaba.otter.canal.common.zookeeper.running;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.exception.ZkException;
import org.I0Itec.zkclient.exception.ZkInterruptedException;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
import com.alibaba.otter.canal.common.utils.BooleanMutex;
import com.alibaba.otter.canal.common.utils.JsonUtils;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
/**
* 针对server的running节点控制
*
* @author jianghang 2012-11-22 下午02:59:42
* @version 1.0.0
*/
public class ServerRunningMonitor extends AbstractCanalLifeCycle {
private static final Logger logger = LoggerFactory.getLogger(ServerRunningMonitor.class);
private ZkClientx zkClient;
private String destination;
private IZkDataListener dataListener;
private BooleanMutex mutex = new BooleanMutex(false);
private volatile boolean release = false;
// 当前服务节点状态信息
private ServerRunningData serverData;
// 当前实际运行的节点状态信息
private volatile ServerRunningData activeData;
private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
private int delayTime = 5;
private ServerRunningListener listener;
public ServerRunningMonitor(ServerRunningData serverData){
this();
this.serverData = serverData;
}
public ServerRunningMonitor(){
// 创建父节点
dataListener = new IZkDataListener() {
public void handleDataChange(String dataPath, Object data) throws Exception {
MDC.put("destination", destination);
ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class);
if (!isMine(runningData.getAddress())) {
mutex.set(false);
}
if (!runningData.isActive() && isMine(runningData.getAddress())) { // 说明出现了主动释放的操作,并且本机之前是active
release = true;
releaseRunning();// 彻底释放mainstem
}
activeData = (ServerRunningData) runningData;
}
public void handleDataDeleted(String dataPath) throws Exception {
MDC.put("destination", destination);
mutex.set(false);
if (!release && activeData != null && isMine(activeData.getAddress())) {
// 如果上一次active的状态就是本机,则即时触发一下active抢占
initRunning();
} else {
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
delayExector.schedule(new Runnable() {
public void run() {
initRunning();
}
}, delayTime, TimeUnit.SECONDS);
}
}
};
}
public void start() {
super.start();
processStart();
if (zkClient != null) {
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
zkClient.subscribeDataChanges(path, dataListener);
initRunning();
} else {
processActiveEnter();// 没有zk,直接启动
}
}
public void release() {
if (zkClient != null) {
releaseRunning(); // 尝试一下release
} else {
processActiveExit(); // 没有zk,直接启动
}
}
public void stop() {
super.stop();
if (zkClient != null) {
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
zkClient.unsubscribeDataChanges(path, dataListener);
releaseRunning(); // 尝试一下release
} else {
processActiveExit(); // 没有zk,直接启动
}
processStop();
}
private void initRunning() {
if (!isStart()) {
return;
}
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
// 序列化
byte[] bytes = JsonUtils.marshalToByte(serverData);
try {
mutex.set(false);
zkClient.create(path, bytes, CreateMode.EPHEMERAL);
activeData = serverData;
processActiveEnter();// 触发一下事件
mutex.set(true);
} catch (ZkNodeExistsException e) {
bytes = zkClient.readData(path, true);
if (bytes == null) {// 如果不存在节点,立即尝试一次
initRunning();
} else {
activeData = JsonUtils.unmarshalFromByte(bytes, ServerRunningData.class);
}
} catch (ZkNoNodeException e) {
zkClient.createPersistent(ZookeeperPathUtils.getDestinationPath(destination), true); // 尝试创建父节点
initRunning();
}
}
/**
* 阻塞等待自己成为active,如果自己成为active,立马返回
*
* @throws InterruptedException
*/
public void waitForActive() throws InterruptedException {
initRunning();
mutex.get();
}
/**
* 检查当前的状态
*/
public boolean check() {
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
try {
byte[] bytes = zkClient.readData(path);
ServerRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ServerRunningData.class);
activeData = eventData;// 更新下为最新值
// 检查下nid是否为自己
boolean result = isMine(activeData.getAddress());
if (!result) {
logger.warn("canal is running in node[{}] , but not in node[{}]",
activeData.getCid(),
serverData.getCid());
}
return result;
} catch (ZkNoNodeException e) {
logger.warn("canal is not run any in node");
return false;
} catch (ZkInterruptedException e) {
logger.warn("canal check is interrupt");
Thread.interrupted();// 清除interrupt标记
return check();
} catch (ZkException e) {
logger.warn("canal check is failed");
return false;
}
}
private boolean releaseRunning() {
if (check()) {
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
zkClient.delete(path);
mutex.set(false);
processActiveExit();
return true;
}
return false;
}
// ====================== helper method ======================
private boolean isMine(String address) {
return address.equals(serverData.getAddress());
}
private void processStart() {
if (listener != null) {
try {
listener.processStart();
} catch (Exception e) {
logger.error("processStart failed", e);
}
}
}
private void processStop() {
if (listener != null) {
try {
listener.processStop();
} catch (Exception e) {
logger.error("processStop failed", e);
}
}
}
private void processActiveEnter() {
if (listener != null) {
try {
listener.processActiveEnter();
} catch (Exception e) {
logger.error("processActiveEnter failed", e);
}
}
}
private void processActiveExit() {
if (listener != null) {
try {
listener.processActiveExit();
} catch (Exception e) {
logger.error("processActiveExit failed", e);
}
}
}
public void setListener(ServerRunningListener listener) {
this.listener = listener;
}
// ===================== setter / getter =======================
public void setDelayTime(int delayTime) {
this.delayTime = delayTime;
}
public void setServerData(ServerRunningData serverData) {
this.serverData = serverData;
}
public void setDestination(String destination) {
this.destination = destination;
}
public void setZkClient(ZkClientx zkClient) {
this.zkClient = zkClient;
}
}

View File

@@ -0,0 +1,36 @@
package com.alibaba.otter.canal.common.zookeeper.running;
import java.util.Map;
/**
* {@linkplain ServerRunningMonitor}管理容器,使用static进行数据全局共享
*
* @author jianghang 2012-12-3 下午09:32:06
* @version 1.0.0
*/
public class ServerRunningMonitors {
private static ServerRunningData serverData;
private static Map runningMonitors; // <String, ServerRunningMonitor>
public static ServerRunningData getServerData() {
return serverData;
}
public static Map<String, ServerRunningMonitor> getRunningMonitors() {
return runningMonitors;
}
public static ServerRunningMonitor getRunningMonitor(String destination) {
return (ServerRunningMonitor) runningMonitors.get(destination);
}
public static void setServerData(ServerRunningData serverData) {
ServerRunningMonitors.serverData = serverData;
}
public static void setRunningMonitors(Map runningMonitors) {
ServerRunningMonitors.runningMonitors = runningMonitors;
}
}

View File

@@ -0,0 +1,18 @@
package com.alibaba.otter.canal.common;
import org.junit.Assert;
public class AbstractZkTest {
protected String destination = "ljhtest1";
protected String cluster1 = "127.0.0.1:2188";
protected String cluster2 = "127.0.0.1:2188,127.0.0.1:2188";
public void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Assert.fail(e.getMessage());
}
}
}

View File

@@ -0,0 +1,143 @@
package com.alibaba.otter.canal.common;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang.math.RandomUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
import com.alibaba.otter.canal.common.zookeeper.running.ServerRunningData;
import com.alibaba.otter.canal.common.zookeeper.running.ServerRunningListener;
import com.alibaba.otter.canal.common.zookeeper.running.ServerRunningMonitor;
public class ServerRunningTest extends AbstractZkTest {
private ZkClientx zkclientx = new ZkClientx(cluster1 + ";" + cluster2);
@Before
public void setUp() {
String path = ZookeeperPathUtils.getDestinationPath(destination);
zkclientx.deleteRecursive(path);
zkclientx.createPersistent(ZookeeperPathUtils.getDestinationPath(destination), true);
}
@After
public void tearDown() {
String path = ZookeeperPathUtils.getDestinationPath(destination);
zkclientx.deleteRecursive(path);
}
@Test
public void testOneServer() {
final CountDownLatch countLatch = new CountDownLatch(2);
ServerRunningMonitor runningMonitor = buildServerRunning(countLatch, 1L, "127.0.0.1", 2088);
runningMonitor.start();
sleep(2000L);
runningMonitor.stop();
sleep(2000L);
if (countLatch.getCount() != 0) {
Assert.fail();
}
}
@Test
public void testMultiServer() {
final CountDownLatch countLatch = new CountDownLatch(30);
final ServerRunningMonitor runningMonitor1 = buildServerRunning(countLatch, 1L, "127.0.0.1", 2088);
final ServerRunningMonitor runningMonitor2 = buildServerRunning(countLatch, 2L, "127.0.0.1", 2089);
final ServerRunningMonitor runningMonitor3 = buildServerRunning(countLatch, 3L, "127.0.0.1", 2090);
final ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
if (!runningMonitor1.isStart()) {
runningMonitor1.start();
}
sleep(2000L + RandomUtils.nextInt(500));
if (runningMonitor1.check()) {
runningMonitor1.stop();
}
sleep(2000L + RandomUtils.nextInt(500));
}
}
});
executor.submit(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
if (!runningMonitor2.isStart()) {
runningMonitor2.start();
}
sleep(2000L + RandomUtils.nextInt(500));
if (runningMonitor2.check()) {
runningMonitor2.stop();
}
sleep(2000L + RandomUtils.nextInt(500));
}
}
});
executor.submit(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
if (!runningMonitor3.isStart()) {
runningMonitor3.start();
}
sleep(2000L + RandomUtils.nextInt(500));
if (runningMonitor3.check()) {
runningMonitor3.stop();
}
sleep(2000L + RandomUtils.nextInt(500));
}
}
});
sleep(30000L);
}
private ServerRunningMonitor buildServerRunning(final CountDownLatch countLatch, final Long cid, final String ip,
final int port) {
ServerRunningData serverData = new ServerRunningData(cid, ip + ":" + port);
ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData);
runningMonitor.setDestination(destination);
runningMonitor.setListener(new ServerRunningListener() {
public void processActiveEnter() {
System.out.println(String.format("cid:%s ip:%s:%s has start", cid, ip, port));
countLatch.countDown();
}
public void processActiveExit() {
System.out.println(String.format("cid:%s ip:%s:%s has stop", cid, ip, port));
countLatch.countDown();
}
public void processStart() {
System.out.println(String.format("cid:%s ip:%s:%s processStart", cid, ip, port));
}
public void processStop() {
System.out.println(String.format("cid:%s ip:%s:%s processStop", cid, ip, port));
}
});
runningMonitor.setZkClient(zkclientx);
runningMonitor.setDelayTime(1);
return runningMonitor;
}
}

View File

@@ -0,0 +1,14 @@
<configuration scan="true" scanPeriod=" 5 seconds">
<jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
</configuration>