package org.opencloudb.sequence.handler;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
import org.opencloudb.MycatConfig;
import org.opencloudb.MycatSystem;
import org.opencloudb.backend.BackendConnection;
import org.opencloudb.backend.ConnectionMeta;
import org.opencloudb.backend.PhysicalDBNode;
import org.opencloudb.mysql.nio.handler.ResponseHandler;
import org.opencloudb.net.mysql.ErrorPacket;
import org.opencloudb.net.mysql.RowDataPacket;
import org.opencloudb.route.RouteResultsetNode;
import org.opencloudb.server.parser.ServerParse;
public class IncrSequenceMySQLHandler implements SequenceHandler {
protected static final Logger LOGGER = Logger
.getLogger(IncrSequenceMySQLHandler.class);
protected static final String errSeqResult = "-999999999,null";
private final FetchMySQLSequnceHandler mysqlSeqFetcher = new FetchMySQLSequnceHandler();
private static class IncrSequenceMySQLHandlerHolder {
private static final IncrSequenceMySQLHandler instance = new IncrSequenceMySQLHandler();
}
public static IncrSequenceMySQLHandler getInstance() {
return IncrSequenceMySQLHandlerHolder.instance;
}
public IncrSequenceMySQLHandler() {
// load sequnce properties
String file = "sequence_db_conf.properties";
Properties props = new Properties();
InputStream inp = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(file);
if (inp == null) {
throw new java.lang.RuntimeException(
"db sequnce properties not found " + file);
}
try {
props.load(inp);
} catch (IOException e) {
throw new java.lang.RuntimeException(e);
}
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String seqName = (String) entry.getKey();
String dataNode = (String) entry.getValue();
SequnceVal seqVal = new SequnceVal(seqName, dataNode);
seqValueMap.put(seqName, seqVal);
}
}
/**
* save sequnce -> curval
*/
private ConcurrentHashMap<String, SequnceVal> seqValueMap = new ConcurrentHashMap<String, SequnceVal>();
@Override
public long nextId(String seqName) {
SequnceVal seqVal = seqValueMap.get(seqName);
if (!seqVal.isSuccessFetched()) {
return getSeqValueFromDB(seqVal);
} else {
return getNextValidSeqVal(seqVal);
}
}
private Long getNextValidSeqVal(SequnceVal seqVal) {
Long nexVal = seqVal.nextValue();
if (seqVal.isNexValValid(nexVal)) {
return nexVal;
} else {
seqVal.fetching.compareAndSet(true, false);
return getSeqValueFromDB(seqVal);
}
}
private long getSeqValueFromDB(SequnceVal seqVal) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("get next segement of sequence from db for sequnce:"
+ seqVal.seqName + " curVal " + seqVal.curVal);
}
if (seqVal.fetching.compareAndSet(false, true)) {
seqVal.dbretVal = null;
seqVal.dbfinished = false;
seqVal.newValueSetted.set(false);
mysqlSeqFetcher.execute(seqVal);
}
Long[] values = seqVal.waitFinish();
if (values == null) {
throw new RuntimeException("can't fetch sequnce in db,sequnce :"
+ seqVal.seqName);
} else {
if (seqVal.newValueSetted.compareAndSet(false, true)) {
seqVal.setCurValue(values[0]);
seqVal.maxSegValue = values[1];
return values[0];
} else {
return seqVal.nextValue();
}
}
}
}
class FetchMySQLSequnceHandler implements ResponseHandler {
private static final Logger LOGGER = Logger
.getLogger(FetchMySQLSequnceHandler.class);
public void execute(SequnceVal seqVal) {
MycatConfig conf = MycatSystem.getInstance().getConfig();
PhysicalDBNode mysqlDN = conf.getDataNodes().get(seqVal.dataNode);
ConnectionMeta conMeta = new ConnectionMeta(mysqlDN.getDatabase(),
null, -1, true);
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("execute in datanode " + seqVal.dataNode
+ " for fetch sequnce sql " + seqVal.sql);
}
// 修正获取seq的逻辑,在读写分离的情况下只能走写节点。修改Select模式为Update模式。
mysqlDN.getConnection(conMeta, new RouteResultsetNode(
seqVal.dataNode, ServerParse.UPDATE, seqVal.sql), this,
seqVal);
} catch (Exception e) {
LOGGER.warn("get connection err " + e);
}
}
@Override
public void connectionAcquired(BackendConnection conn) {
conn.setResponseHandler(this);
try {
conn.query(((SequnceVal) conn.getAttachment()).sql);
} catch (Exception e) {
executeException(conn, e);
}
}
@Override
public void connectionError(Throwable e, BackendConnection conn) {
((SequnceVal) conn.getAttachment()).dbfinished = true;
LOGGER.warn("connectionError " + e);
}
@Override
public void errorResponse(byte[] data, BackendConnection conn) {
((SequnceVal) conn.getAttachment()).dbfinished = true;
ErrorPacket err = new ErrorPacket();
err.read(data);
LOGGER.warn("errorResponse " + err.errno + " "
+ new String(err.message));
conn.release();
}
@Override
public void okResponse(byte[] ok, BackendConnection conn) {
boolean executeResponse = conn.syncAndExcute();
if (executeResponse) {
((SequnceVal) conn.getAttachment()).dbfinished = true;
conn.release();
}
}
@Override
public void rowResponse(byte[] row, BackendConnection conn) {
RowDataPacket rowDataPkg = new RowDataPacket(1);
rowDataPkg.read(row);
byte[] columnData = rowDataPkg.fieldValues.get(0);
String columnVal = new String(columnData);
SequnceVal seqVal = (SequnceVal) conn.getAttachment();
seqVal.dbretVal = seqVal.dbretVal = columnVal;
if (IncrSequenceMySQLHandler.errSeqResult.equals(columnVal)) {
LOGGER.warn(" sequnce sql returned err value ,sequence:"
+ seqVal.seqName + " " + columnVal + " sql:" + seqVal.sql);
}
}
@Override
public void rowEofResponse(byte[] eof, BackendConnection conn) {
((SequnceVal) conn.getAttachment()).dbfinished = true;
conn.release();
}
private void executeException(BackendConnection c, Throwable e) {
((SequnceVal) c.getAttachment()).dbfinished = true;
LOGGER.warn("executeException " + e);
c.close("exception:" + e);
}
@Override
public void writeQueueAvailable() {
}
@Override
public void connectionClose(BackendConnection conn, String reason) {
LOGGER.warn("connection closed " + conn + " reason:" + reason);
}
@Override
public void fieldEofResponse(byte[] header, List<byte[]> fields,
byte[] eof, BackendConnection conn) {
}
}
class SequnceVal {
public AtomicBoolean newValueSetted = new AtomicBoolean(false);
public AtomicLong curVal = new AtomicLong(0);
public volatile String dbretVal = null;
public volatile boolean dbfinished;
public AtomicBoolean fetching = new AtomicBoolean(false);
public volatile long maxSegValue;
public volatile boolean successFetched;
public final String dataNode;
public final String seqName;
public final String sql;
public SequnceVal(String seqName, String dataNode) {
this.seqName = seqName;
this.dataNode = dataNode;
sql = "SELECT mycat_seq_nextval('" + seqName + "')";
}
public boolean isNexValValid(Long nexVal) {
if (nexVal < this.maxSegValue) {
return true;
} else {
return false;
}
}
FetchMySQLSequnceHandler seqHandler;
public void setCurValue(long newValue) {
curVal.set(newValue);
successFetched = true;
}
public Long[] waitFinish() {
long start = System.currentTimeMillis();
long end = start + 10 * 1000;
while (System.currentTimeMillis() < end) {
if (dbretVal == IncrSequenceMySQLHandler.errSeqResult) {
throw new java.lang.RuntimeException(
"sequnce not found in db table ");
} else if (dbretVal != null) {
String[] items = dbretVal.split(",");
Long curVal = Long.valueOf(items[0]);
int span = Integer.valueOf(items[1]);
return new Long[] { curVal, curVal + span };
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
IncrSequenceMySQLHandler.LOGGER
.warn("wait db fetch sequnce err " + e);
}
}
}
return null;
}
public boolean isSuccessFetched() {
return successFetched;
}
public long nextValue() {
if (successFetched == false) {
throw new java.lang.RuntimeException(
"sequnce fetched failed from db ");
}
return curVal.incrementAndGet();
}
}