package com.meidusa.amoeba.seq.provider; import java.util.Properties; import org.apache.log4j.Logger; import org.apache.zookeeper.data.Stat; import com.meidusa.amoeba.exception.AmoebaRuntimeException; import com.meidusa.amoeba.seq.fetcher.SeqOperationResult; import com.meidusa.amoeba.seq.fetcher.SeqProvider; import com.meidusa.amoeba.seq.provider.utils.Utils; import com.netflix.curator.RetryPolicy; import com.netflix.curator.framework.CuratorFramework; import com.netflix.curator.framework.CuratorFrameworkFactory; import com.netflix.curator.framework.recipes.atomic.AtomicValue; import com.netflix.curator.framework.recipes.atomic.DistributedAtomicLong; import com.netflix.curator.framework.recipes.atomic.PromotedToLock; import com.netflix.curator.retry.ExponentialBackoffRetry; /** * zookeeper 不是一个高并发系统,所以把所有办法弄成同步的以提高响应时间 * 但是降低吞吐量 * * 这种生成方式暂不实现offset,如需支持,可以增加一个offset节点来实现 * * @author WangFei * */ public class SeqProviderBasedOnZK extends SeqProvider{ private static final String ROOT_PATH = "/seq"; private static enum FETCH_OPERATION { CURR, NEXT, BATCH}; private static final int RETRY_TIMES = Integer.MAX_VALUE; private static final int BASE_SLEEP_TIME = 1000; private CuratorFramework client; private static final Logger log = Logger.getLogger(SeqProviderBasedOnZK.class); /** * 创建某个序列 */ @Override public synchronized SeqOperationResult createSeq(String schema, String seqName, long start, long offset) { SeqOperationResult result = null; // 节点路径 String idPath = String.format("%s/%s/%s", ROOT_PATH, schema, seqName); try { if (checkNodeExisted(idPath, client)) { result = new SeqOperationResult(false, String.format("sequence %s is existed", seqName)); } else { DistributedAtomicLong dal = buildDAL(idPath, false); dal.forceSet(start); result = new SeqOperationResult(true, ""); } } catch (Exception e) { throw new AmoebaRuntimeException(e.getMessage()); } return result; } // 当前序列值 @Override public synchronized long getSeqCurrVal(String schema, String seqName) { return commonGetSeqVal(schema, seqName, null, FETCH_OPERATION.CURR); } // 下一个序列值 @Override public synchronized long getSeqNextVal(String schema, String seqName) { return commonGetSeqVal(schema, seqName, null, FETCH_OPERATION.NEXT); } // 批量获取序列值 @Override public synchronized long batchGetSeqVal(String schema, String seqName, long count) { return commonGetSeqVal(schema, seqName, count, FETCH_OPERATION.BATCH); } /** * 删除某个全局序列 */ @Override public synchronized SeqOperationResult deleteSeq(String schema, String seqName) { SeqOperationResult result = null; // 节点路径 String idPath = String.format("%s/%s/%s", ROOT_PATH, schema, seqName); try { if (checkNodeExisted(idPath, client)) { client.delete().forPath(idPath); result = new SeqOperationResult(true, ""); } else { result = new SeqOperationResult(false, String.format("sequence %s is not existed", seqName)); } } catch (Exception e) { throw new AmoebaRuntimeException(e.getMessage()); } return result; } //检查节点是否已经存在 private synchronized boolean checkNodeExisted(String path, CuratorFramework client) throws Exception { Stat stat = client.checkExists().forPath(path); if (stat != null) { return true; } return false; } /** * 提取获取全局序列操作通用流程 * * @param schema * @param seqName * @param count * @param operation * @return */ private long commonGetSeqVal(String schema, String seqName, Long count, FETCH_OPERATION operation) { long id = -1; // 节点路径 String idPath = String.format("%s/%s/%s", ROOT_PATH, schema, seqName); try { if (checkNodeExisted(idPath, client)) { client.sync(idPath, null); // 获取前,先从leader那里同步 !! 异步操作,但是有序 !! DistributedAtomicLong dal = buildDAL(idPath, true); switch (operation) { // 获取当前全局序列值 case CURR: AtomicValue<Long> currValue = dal.get(); if(currValue.succeeded()) { id = currValue.postValue(); } else { throw new AmoebaRuntimeException("fetch from id server error"); } break; // 获取下一个全局序列 case NEXT: AtomicValue<Long> nextValue = dal.increment(); if (nextValue.succeeded()) { id = nextValue.postValue(); } else { throw new AmoebaRuntimeException("fetch from id server error"); } break; // 批量获取全局序列 case BATCH: AtomicValue<Long> startValue = dal.get(); id = startValue.postValue()+count; if (startValue.succeeded()) { dal.forceSet(id); } break; default: throw new AmoebaRuntimeException("not support this fetch method"); } } else { throw new AmoebaRuntimeException(String.format("sequence %s is not existed", seqName)); } } catch (Exception e) { throw new AmoebaRuntimeException(e.getMessage()); } return id; } /** * 全局序列的 创建/获取/删除都通过DistributedAtomicLong接口去操作 * 而且操作前,使用互斥的方式访问某个节点,避免并发的时候,节点并发读写出错 * * @param path * @param needLock * @return */ private DistributedAtomicLong buildDAL(String path, boolean needLock) { RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, RETRY_TIMES); PromotedToLock lock = PromotedToLock.builder().lockPath(path).build(); DistributedAtomicLong dal = new DistributedAtomicLong(client, path, retryPolicy); if (needLock) { dal = new DistributedAtomicLong(client, path, retryPolicy, lock); } return dal; } /** * Amoeba启动的时候,初始化zookeeper客户端,另外,给zookeeper预热 */ public void init() throws Exception { Properties props = Utils.readGlobalSeqConfigProps(); String zkHosts = props.getProperty("zkHosts", "127.0.0.1:2181"); Integer connTimeOut = Integer.valueOf(props.getProperty("zkConnTimeOut", "60")); log.info(String.format("zookeeper config: connect string= %s", zkHosts)); log.info(String.format("zookeeper config: connect timeout= %d seconds", connTimeOut)); client = CuratorFrameworkFactory.builder() .connectString(zkHosts).retryPolicy(new ExponentialBackoffRetry(BASE_SLEEP_TIME, RETRY_TIMES)) .connectionTimeoutMs(connTimeOut*1000).build(); // 时间要由秒转成毫秒 client.start(); // 只是为client热身而调用 client.checkExists().forPath(ROOT_PATH); } /** * Amoeba所在jvm停止时,也要把zookeeper客户端停掉 */ public void stop() throws Exception { if (client != null) { client.close(); } } }