package io.mycat.server.sequence;
import io.mycat.MycatServer;
import io.mycat.backend.BackendConnection;
import io.mycat.backend.PhysicalDBNode;
import io.mycat.route.RouteResultsetNode;
import io.mycat.server.config.ConfigException;
import io.mycat.server.config.node.MycatConfig;
import io.mycat.server.config.node.SequenceConfig;
import io.mycat.server.executors.ResponseHandler;
import io.mycat.server.packet.ErrorPacket;
import io.mycat.server.packet.RowDataPacket;
import io.mycat.server.parser.ServerParse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
public class IncrSequenceMySQLHandler extends SequenceHandler {
public static final Logger LOGGER = LoggerFactory
.getLogger(IncrSequenceMySQLHandler.class);
protected static final String errSeqResult = "-999999999,null";
protected static Map<String, String> latestErrors = new ConcurrentHashMap<String, String>();
private final FetchMySQLSequnceHandler mysqlSeqFetcher = new FetchMySQLSequnceHandler();
/**
* save sequnce -> curval
*/
private ConcurrentHashMap<String, SequenceVal> seqValueMap = new ConcurrentHashMap<String, SequenceVal>();
private static class IncrSequenceMySQLHandlerHolder {
private static final IncrSequenceMySQLHandler instance = new IncrSequenceMySQLHandler();
}
public static SequenceHandler getInstance() {
return IncrSequenceMySQLHandlerHolder.instance;
}
public IncrSequenceMySQLHandler() {
load();
}
public void load() {
Properties props = new Properties();
// load sequnce properties
SequenceConfig sequenceConfig = SequenceHandler.getConfig();
Map<String, Object> data = sequenceConfig.getProps();
Set<String> keySet = data.keySet();
for(String key : keySet){
props.put(key, data.get(key));
}
removeDesertedSequenceVals(props);
putNewSequenceVals(props);
}
private void removeDesertedSequenceVals(Properties props) {
Iterator<Map.Entry<String, SequenceVal>> i = seqValueMap.entrySet()
.iterator();
while (i.hasNext()) {
Map.Entry<String, SequenceVal> entry = i.next();
if (!props.containsKey(entry.getKey())) {
i.remove();
}
}
}
private void putNewSequenceVals(Properties props) {
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String seqName = (String) entry.getKey();
String dataNode = (String) entry.getValue();
if (!seqValueMap.containsKey(seqName)) {
seqValueMap.put(seqName, new SequenceVal(seqName, dataNode));
} else {
seqValueMap.get(seqName).dataNode = dataNode;
}
}
}
@Override
public long nextId(String seqName) {
SequenceVal seqVal = seqValueMap.get(seqName);
if (seqVal == null) {
throw new ConfigException("can't find definition for sequence :"
+ seqName);
}
if (!seqVal.isSuccessFetched()) {
return getSeqValueFromDB(seqVal);
} else {
return getNextValidSeqVal(seqVal);
}
}
private Long getNextValidSeqVal(SequenceVal seqVal) {
Long nexVal = seqVal.nextValue();
if (seqVal.isNexValValid(nexVal)) {
return nexVal;
} else {
seqVal.fetching.compareAndSet(true, false);
return getSeqValueFromDB(seqVal);
}
}
private long getSeqValueFromDB(SequenceVal 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 + " detail:"
+ mysqlSeqFetcher.getLastestError(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 = LoggerFactory
.getLogger(FetchMySQLSequnceHandler.class);
public void execute(SequenceVal seqVal) {
MycatConfig conf = MycatServer.getInstance().getConfig();
PhysicalDBNode mysqlDN = conf.getDataNodes().get(seqVal.dataNode);
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("execute in datanode " + seqVal.dataNode
+ " for fetch sequnce sql " + seqVal.sql);
}
// 修正获取seq的逻辑,在读写分离的情况下只能走写节点。修改Select模式为Update模式。
mysqlDN.getConnection(mysqlDN.getDatabase(), true,
new RouteResultsetNode(seqVal.dataNode, ServerParse.UPDATE,
seqVal.sql), this, seqVal);
} catch (Exception e) {
LOGGER.warn("get connection err " + e);
}
}
public String getLastestError(String seqName) {
return IncrSequenceMySQLHandler.latestErrors.get(seqName);
}
@Override
public void connectionAcquired(BackendConnection conn) {
conn.setResponseHandler(this);
try {
conn.query(((SequenceVal) conn.getAttachment()).sql);
} catch (Exception e) {
executeException(conn, e);
}
}
@Override
public void connectionError(Throwable e, BackendConnection conn) {
((SequenceVal) conn.getAttachment()).dbfinished = true;
LOGGER.warn("connectionError " + e);
}
@Override
public void errorResponse(byte[] data, BackendConnection conn) {
SequenceVal seqVal = ((SequenceVal) conn.getAttachment());
seqVal.dbfinished = true;
ErrorPacket err = new ErrorPacket();
err.read(data);
String errMsg = new String(err.message);
LOGGER.warn("errorResponse " + err.errno + " " + errMsg);
IncrSequenceMySQLHandler.latestErrors.put(seqVal.seqName, errMsg);
conn.release();
}
@Override
public void okResponse(byte[] ok, BackendConnection conn) {
boolean executeResponse = conn.syncAndExcute();
if (executeResponse) {
((SequenceVal) 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);
SequenceVal seqVal = (SequenceVal) conn.getAttachment();
if (IncrSequenceMySQLHandler.errSeqResult.equals(columnVal)) {
seqVal.dbretVal = IncrSequenceMySQLHandler.errSeqResult;
LOGGER.warn(" sequnce sql returned err value ,sequence:"
+ seqVal.seqName + " " + columnVal + " sql:" + seqVal.sql);
} else {
seqVal.dbretVal = columnVal;
}
}
@Override
public void rowEofResponse(byte[] eof, BackendConnection conn) {
((SequenceVal) conn.getAttachment()).dbfinished = true;
conn.release();
}
private void executeException(BackendConnection c, Throwable e) {
SequenceVal seqVal = ((SequenceVal) c.getAttachment());
seqVal.dbfinished = true;
String errMgs = e.toString();
IncrSequenceMySQLHandler.latestErrors.put(seqVal.seqName, errMgs);
LOGGER.warn("executeException " + errMgs);
c.close("exception:" + errMgs);
}
@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 SequenceVal {
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 volatile String dataNode;
public final String seqName;
public final String sql;
public SequenceVal(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) {
throw new java.lang.RuntimeException(
"sequnce fetched failed from db ");
}
return curVal.incrementAndGet();
}
}