package com.taobao.tddl.sequence.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import com.taobao.tddl.common.GroupDataSourceRouteHelper;
import com.taobao.tddl.group.jdbc.TGroupDataSource;
import com.taobao.tddl.monitor.eagleeye.EagleeyeHelper;
import com.taobao.tddl.sequence.SequenceDao;
import com.taobao.tddl.sequence.SequenceRange;
import com.taobao.tddl.sequence.exception.SequenceException;
import com.taobao.tddl.sequence.util.RandomSequence;
import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;
/**
* @author JIECHEN 2013-10-31 下午5:48:48
* @since 5.0.0
*/
public class GroupSequenceDao implements SequenceDao {
private static final Logger logger = LoggerFactory.getLogger(GroupSequenceDao.class);
// private static final int MIN_STEP = 1;
// private static final int MAX_STEP = 100000;
private static final int DEFAULT_INNER_STEP = 1000;
private static final int DEFAULT_RETRY_TIMES = 2;
private static final String DEFAULT_TABLE_NAME = "sequence";
private static final String DEFAULT_TEMP_TABLE_NAME = "sequence_temp";
private static final String DEFAULT_NAME_COLUMN_NAME = "name";
private static final String DEFAULT_VALUE_COLUMN_NAME = "value";
private static final String DEFAULT_GMT_MODIFIED_COLUMN_NAME = "gmt_modified";
private static final int DEFAULT_DSCOUNT = 2; // 默认
private static final Boolean DEFAULT_ADJUST = false;
protected static final long DELTA = 100000000L;
/**
* 应用名
*/
protected String appName;
/**
* group阵列
*/
protected List<String> dbGroupKeys;
protected List<String> oriDbGroupKeys;
/**
* 数据源
*/
protected Map<String, DataSource> dataSourceMap;
/**
* 自适应开关
*/
protected boolean adjust = DEFAULT_ADJUST;
/**
* 重试次数
*/
protected int retryTimes = DEFAULT_RETRY_TIMES;
/**
* 数据源个数
*/
protected int dscount = DEFAULT_DSCOUNT;
/**
* 内步长
*/
protected int innerStep = DEFAULT_INNER_STEP;
/**
* 外步长
*/
protected int outStep = DEFAULT_INNER_STEP;
/**
* 序列所在的表名
*/
protected String tableName = DEFAULT_TABLE_NAME;
protected String switchTempTable = DEFAULT_TEMP_TABLE_NAME;
private String TEST_TABLE_PREFIX = "__test_";
// 全链路压测对应sequence表的影子表
protected String testTableName = TEST_TABLE_PREFIX + tableName;
// 全链路压测对应sequence_temp表的影子表
protected String testSwitchTempTable = TEST_TABLE_PREFIX + switchTempTable;
/**
* 存储序列名称的列名
*/
protected String nameColumnName = DEFAULT_NAME_COLUMN_NAME;
/**
* 存储序列值的列名
*/
protected String valueColumnName = DEFAULT_VALUE_COLUMN_NAME;
/**
* 存储序列最后更新时间的列名
*/
protected String gmtModifiedColumnName = DEFAULT_GMT_MODIFIED_COLUMN_NAME;
/**
* 初试化
*
* @throws SequenceException
*/
public void init() throws SequenceException {
// 如果应用名为空,直接抛出
if (StringUtils.isEmpty(appName)) {
SequenceException sequenceException = new SequenceException("appName is Null ");
logger.error("没有配置appName", sequenceException);
throw sequenceException;
}
if (dbGroupKeys == null || dbGroupKeys.size() == 0) {
logger.error("没有配置dbgroupKeys");
throw new SequenceException("dbgroupKeys为空!");
}
dataSourceMap = new HashMap<String, DataSource>();
for (String dbGroupKey : dbGroupKeys) {
if (dbGroupKey.toUpperCase().endsWith("-OFF")) {
continue;
}
// TGroupDataSource tGroupDataSource = new TGroupDataSource(
// dbGroupKey, appName, dataSourceType);
TGroupDataSource tGroupDataSource = new TGroupDataSource(dbGroupKey, appName);
tGroupDataSource.init();
dataSourceMap.put(dbGroupKey, tGroupDataSource);
}
if (dbGroupKeys.size() >= dscount) {
dscount = dbGroupKeys.size();
} else {
for (int ii = dbGroupKeys.size(); ii < dscount; ii++) {
dbGroupKeys.add(dscount + "-OFF");
}
}
outStep = innerStep * dscount;// 计算外步长
outputInitResult();
}
/**
* 初始化完打印配置信息
*/
private void outputInitResult() {
StringBuilder sb = new StringBuilder();
sb.append("GroupSequenceDao初始化完成:\r\n ");
sb.append("appName:").append(appName).append("\r\n");
sb.append("innerStep:").append(this.innerStep).append("\r\n");
sb.append("dataSource:").append(dscount).append("个:");
for (String str : dbGroupKeys) {
sb.append("[").append(str).append("]、");
}
sb.append("\r\n");
sb.append("adjust:").append(adjust).append("\r\n");
sb.append("retryTimes:").append(retryTimes).append("\r\n");
sb.append("tableName:").append(tableName).append("\r\n");
sb.append("nameColumnName:").append(nameColumnName).append("\r\n");
sb.append("valueColumnName:").append(valueColumnName).append("\r\n");
sb.append("gmtModifiedColumnName:").append(gmtModifiedColumnName).append("\r\n");
logger.info(sb.toString());
}
/**
* @param index gourp内的序号,从0开始
* @param value 当前取的值
* @return
*/
private boolean check(int index, long value) {
return (value % outStep) == (index * innerStep);
}
/**
* <pre>
* 检查并初试某个sequence。
*
* 1、如果sequece不处在,插入值,并初始化值。
* 2、如果已经存在,但有重叠,重新生成。
* 3、如果已经存在,且无重叠。
*
* @throws SequenceException
* </pre>
*/
public void adjust(String name) throws SequenceException, SQLException {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
for (int i = 0; i < dbGroupKeys.size(); i++) {
if (dbGroupKeys.get(i).toUpperCase().endsWith("-OFF"))// 已经关掉,不处理
{
continue;
}
TGroupDataSource tGroupDataSource = (TGroupDataSource) dataSourceMap.get(dbGroupKeys.get(i));
try {
conn = tGroupDataSource.getConnection();
stmt = conn.prepareStatement(getSelectSql());
stmt.setString(1, name);
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0);
rs = stmt.executeQuery();
int item = 0;
while (rs.next()) {
item++;
long val = rs.getLong(this.getValueColumnName());
if (!check(i, val)) // 检验初值
{
if (this.isAdjust()) {
this.adjustUpdate(i, val, name);
} else {
logger.error("数据库中配置的初值出错!请调整你的数据库,或者启动adjust开关");
throw new SequenceException("数据库中配置的初值出错!请调整你的数据库,或者启动adjust开关");
}
}
}
if (item == 0)// 不存在,插入这条记录
{
if (this.isAdjust()) {
this.adjustInsert(i, name);
} else {
logger.error("数据库中未配置该sequence!请往数据库中插入sequence记录,或者启动adjust开关");
throw new SequenceException("数据库中未配置该sequence!请往数据库中插入sequence记录,或者启动adjust开关");
}
}
} catch (SQLException e) {// 吞掉SQL异常,我们允许不可用的库存在
logger.error("初值校验和自适应过程中出错.", e);
throw e;
} finally {
closeDbResource(rs, stmt, conn);
}
}
}
/**
* 更新
*
* @param index
* @param value
* @param name
* @throws SequenceException
* @throws SQLException
*/
private void adjustUpdate(int index, long value, String name) throws SequenceException, SQLException {
long newValue = (value - value % outStep) + outStep + index * innerStep;// 设置成新的调整值
TGroupDataSource tGroupDataSource = (TGroupDataSource) dataSourceMap.get(dbGroupKeys.get(index));
Connection conn = null;
PreparedStatement stmt = null;
// ResultSet rs = null;
try {
conn = tGroupDataSource.getConnection();
stmt = conn.prepareStatement(getUpdateSql());
stmt.setLong(1, newValue);
stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
stmt.setString(3, name);
stmt.setLong(4, value);
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0);
int affectedRows = stmt.executeUpdate();
if (affectedRows == 0) {
throw new SequenceException("faild to auto adjust init value at " + name + " update affectedRow =0");
}
logger.info(dbGroupKeys.get(index) + "更新初值成功!" + "sequence Name:" + name + "更新过程:" + value + "-->"
+ newValue);
} catch (SQLException e) { // 吃掉SQL异常,抛Sequence异常
logger.error("由于SQLException,更新初值自适应失败!dbGroupIndex:" + dbGroupKeys.get(index) + ",sequence Name:" + name
+ "更新过程:" + value + "-->" + newValue, e);
throw new SequenceException("由于SQLException,更新初值自适应失败!dbGroupIndex:" + dbGroupKeys.get(index)
+ ",sequence Name:" + name + "更新过程:" + value + "-->" + newValue, e);
} finally {
closeDbResource(null, stmt, conn);
}
}
/**
* 插入新值
*
* @param index
* @param name
* @return
* @throws SequenceException
* @throws SQLException
*/
private void adjustInsert(int index, String name) throws SequenceException, SQLException {
TGroupDataSource tGroupDataSource = (TGroupDataSource) dataSourceMap.get(dbGroupKeys.get(index));
long newValue = index * innerStep;
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = tGroupDataSource.getConnection();
stmt = conn.prepareStatement(getInsertSql());
stmt.setString(1, name);
stmt.setLong(2, newValue);
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0);
int affectedRows = stmt.executeUpdate();
if (affectedRows == 0) {
throw new SequenceException("faild to auto adjust init value at " + name + " update affectedRow =0");
}
logger.info(dbGroupKeys.get(index) + " name:" + name + "插入初值:" + name + "value:" + newValue);
} catch (SQLException e) {
logger.error("由于SQLException,插入初值自适应失败!dbGroupIndex:" + dbGroupKeys.get(index) + ",sequence Name:" + name
+ " value:" + newValue, e);
throw new SequenceException("由于SQLException,插入初值自适应失败!dbGroupIndex:" + dbGroupKeys.get(index)
+ ",sequence Name:" + name + " value:" + newValue, e);
} finally {
closeDbResource(rs, stmt, conn);
}
}
private ConcurrentHashMap<Integer/* ds index */, AtomicInteger/* 掠过次数 */> excludedKeyCount = new ConcurrentHashMap<Integer, AtomicInteger>(dscount);
// 最大略过次数后恢复
private int maxSkipCount = 10;
// 使用慢速数据库保护
private boolean useSlowProtect = false;
// 保护的时间
private int protectMilliseconds = 50;
private ExecutorService exec = Executors.newFixedThreadPool(1);
protected Lock configLock = new ReentrantLock();
/**
* 检查groupKey对象是否已经关闭
*
* @param groupKey
* @return
*/
protected boolean isOffState(String groupKey) {
return groupKey.toUpperCase().endsWith("-OFF");
}
/**
* 检查是否被exclude,如果有尝试恢复
*
* @param index
* @return
*/
protected boolean recoverFromExcludes(int index) {
boolean result = true;
if (excludedKeyCount.get(index) != null) {
if (excludedKeyCount.get(index).incrementAndGet() > maxSkipCount) {
excludedKeyCount.remove(index);
logger.error(maxSkipCount + "次数已过,index为" + index + "的数据源后续重新尝试取序列");
} else {
result = false;
}
}
return result;
}
protected long queryOldValue(DataSource dataSource, String keyName) throws SQLException, SequenceException {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(getSelectSql());
stmt.setString(1, keyName);
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0);
rs = stmt.executeQuery();
if (rs.next()) {
return rs.getLong(1);
} else {
throw new SequenceException("找不到对应的sequence记录,请检查sequence : " + keyName);
}
} finally {
closeDbResource(rs, stmt, conn);
}
}
/**
* CAS更新sequence值
*
* @param dataSource
* @param keyName
* @param oldValue
* @param newValue
* @return
* @throws SQLException
*/
protected int updateNewValue(DataSource dataSource, String keyName, long oldValue, long newValue)
throws SQLException {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(getUpdateSql());
stmt.setLong(1, newValue);
stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
stmt.setString(3, keyName);
stmt.setLong(4, oldValue);
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0);
return stmt.executeUpdate();
} finally {
closeDbResource(rs, stmt, conn);
}
}
/**
* 从指定的数据库中获取sequence值
*
* @param dataSource
* @param keyName
* @return
* @throws SQLException
* @throws SequenceException
*/
protected long getOldValue(final DataSource dataSource, final String keyName) throws SQLException,
SequenceException {
long result = 0;
// 如果未使用超时保护或者已经只剩下了1个数据源,无论怎么样去拿
if (!useSlowProtect || excludedKeyCount.size() >= (dscount - 1)) {
result = queryOldValue(dataSource, keyName);
} else {
FutureTask<Long> future = new FutureTask<Long>(new Callable<Long>() {
@Override
public Long call() throws Exception {
return queryOldValue(dataSource, keyName);
}
});
try {
exec.submit(future);
result = future.get(protectMilliseconds, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new SQLException("[SEQUENCE SLOW-PROTECTED MODE]:InterruptedException", e);
} catch (ExecutionException e) {
throw new SQLException("[SEQUENCE SLOW-PROTECTED MODE]:ExecutionException", e);
} catch (TimeoutException e) {
throw new SQLException("[SEQUENCE SLOW-PROTECTED MODE]:TimeoutException,当前设置超时时间为"
+ protectMilliseconds, e);
}
}
return result;
}
/**
* 生成oldValue生成newValue
*
* @param index
* @param oldValue
* @param keyName
* @return
* @throws SequenceException
*/
protected long generateNewValue(int index, long oldValue, String keyName) throws SequenceException {
long newValue = oldValue + outStep;
if (!check(index, newValue)) // 新算出来的值有问题
{
if (this.isAdjust()) {
newValue = adjustNewValue(index, newValue);
} else {
throwErrorRangeException(index, keyName);
}
}
return newValue;
}
protected long adjustNewValue(int index, long newValue) {
return (newValue - newValue % outStep) + outStep + index * innerStep;// 设置成新的调整值
}
protected void throwErrorRangeException(int index, String keyName) throws SequenceException {
String errorMsg = dbGroupKeys.get(index) + ":" + keyName + "的值得错误,覆盖到其他范围段了!请修改数据库,或者开启adjust开关!";
throw new SequenceException(errorMsg);
}
protected TGroupDataSource getGroupDsByIndex(int index) {
return (TGroupDataSource) dataSourceMap.get(dbGroupKeys.get(index));
}
/**
* 检查该sequence值是否在正常范围内
*
* @return
*/
protected boolean isOldValueFixed(long oldValue) {
boolean result = true;
StringBuilder message = new StringBuilder();
if (oldValue < 0) {
message.append("Sequence value cannot be less than zero.");
result = false;
} else if (oldValue > Long.MAX_VALUE - DELTA) {
message.append("Sequence value overflow.");
result = false;
}
if (!result) {
message.append(" Sequence value = ").append(oldValue);
message.append(", please check table ").append(getTableName());
logger.info(message.toString());
}
return result;
}
/**
* 将该数据源排除到sequence可选数据源以外
*
* @param index
*/
protected void excludeDataSource(int index) {
// 如果数据源只剩下了最后一个,就不要排除了
if (excludedKeyCount.size() < (dscount - 1)) {
excludedKeyCount.put(index, new AtomicInteger(0));
logger.error("暂时踢除index为" + index + "的数据源," + maxSkipCount + "次后重新尝试");
}
}
public SequenceRange nextRange(final String name) throws SequenceException {
if (name == null) {
logger.error("序列名为空!");
throw new IllegalArgumentException("序列名称不能为空");
}
configLock.lock();
try {
int[] randomIntSequence = RandomSequence.randomIntSequence(dscount);
for (int i = 0; i < retryTimes; i++) {
for (int j = 0; j < dscount; j++) {
int index = randomIntSequence[j];
if (isOffState(dbGroupKeys.get(index)) || !recoverFromExcludes(index)) {
continue;
}
final TGroupDataSource tGroupDataSource = getGroupDsByIndex(index);
long oldValue;
// 查询,只在这里做数据库挂掉保护和慢速数据库保护
try {
oldValue = getOldValue(tGroupDataSource, name);
if (!isOldValueFixed(oldValue)) {
continue;
}
} catch (SQLException e) {
logger.error("取范围过程中--查询出错!" + dbGroupKeys.get(index) + ":" + name, e);
excludeDataSource(index);
continue;
}
long newValue = generateNewValue(index, oldValue, name);
try {
if (0 == updateNewValue(tGroupDataSource, name, oldValue, newValue)) {
continue;
}
} catch (SQLException e) {
logger.error("取范围过程中--更新出错!" + dbGroupKeys.get(index) + ":" + name, e);
continue;
}
return new SequenceRange(newValue + 1, newValue + innerStep);
}
// 当还有最后一次重试机会时,清空excludedMap,让其有最后一次机会
if (i == (retryTimes - 2)) {
excludedKeyCount.clear();
}
}
logger.error("所有数据源都不可用!且重试" + this.retryTimes + "次后,仍然失败!");
throw new SequenceException("All dataSource faild to get value!");
} finally {
configLock.unlock();
}
}
public void setDscount(int dscount) {
this.dscount = dscount;
}
protected String getInsertSql() {
StringBuilder buffer = new StringBuilder();
buffer.append("insert into ").append(getTableName()).append("(");
buffer.append(getNameColumnName()).append(",");
buffer.append(getValueColumnName()).append(",");
buffer.append(getGmtModifiedColumnName()).append(") values(?,?,?);");
return buffer.toString();
}
protected String getSelectSql() {
StringBuilder buffer = new StringBuilder();
buffer.append("select ").append(getValueColumnName());
buffer.append(" from ").append(getTableName());
buffer.append(" where ").append(getNameColumnName()).append(" = ?");
return buffer.toString();
}
protected String getUpdateSql() {
StringBuilder buffer = new StringBuilder();
buffer.append("update ").append(getTableName());
buffer.append(" set ").append(getValueColumnName()).append(" = ?, ");
buffer.append(getGmtModifiedColumnName()).append(" = ? where ");
buffer.append(getNameColumnName()).append(" = ? and ");
buffer.append(getValueColumnName()).append(" = ?");
return buffer.toString();
}
protected static void closeDbResource(ResultSet rs, Statement stmt, Connection conn) {
closeResultSet(rs);
closeStatement(stmt);
closeConnection(conn);
}
protected static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
logger.debug("Could not close JDBC ResultSet", e);
} catch (Throwable e) {
logger.debug("Unexpected exception on closing JDBC ResultSet", e);
}
}
}
protected static void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
logger.debug("Could not close JDBC Statement", e);
} catch (Throwable e) {
logger.debug("Unexpected exception on closing JDBC Statement", e);
}
}
}
protected static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.debug("Could not close JDBC Connection", e);
} catch (Throwable e) {
logger.debug("Unexpected exception on closing JDBC Connection", e);
}
}
}
public int getRetryTimes() {
return retryTimes;
}
public void setRetryTimes(int retryTimes) {
this.retryTimes = retryTimes;
}
public int getInnerStep() {
return innerStep;
}
public void setInnerStep(int innerStep) {
this.innerStep = innerStep;
}
public String getTableName() {
// 全链路压测需求
String t = EagleeyeHelper.getUserData("t");
if (!StringUtils.isBlank(t) && t.equals("1")) {
return testTableName;
} else {
return tableName;
}
}
public void setTableName(String tableName) {
this.tableName = tableName;
this.testTableName = TEST_TABLE_PREFIX + this.tableName;
}
public String getNameColumnName() {
return nameColumnName;
}
public void setNameColumnName(String nameColumnName) {
this.nameColumnName = nameColumnName;
}
public String getValueColumnName() {
return valueColumnName;
}
public void setValueColumnName(String valueColumnName) {
this.valueColumnName = valueColumnName;
}
public String getGmtModifiedColumnName() {
return gmtModifiedColumnName;
}
public void setGmtModifiedColumnName(String gmtModifiedColumnName) {
this.gmtModifiedColumnName = gmtModifiedColumnName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public void setDbGroupKeys(List<String> dbGroupKeys) {
// 这里ugly,如果动态变更也调用这个方法的话,那就见鬼了
this.oriDbGroupKeys = dbGroupKeys;
this.dbGroupKeys = dbGroupKeys;
}
public boolean isAdjust() {
return adjust;
}
public void setAdjust(boolean adjust) {
this.adjust = adjust;
}
public int getMaxSkipCount() {
return maxSkipCount;
}
public void setMaxSkipCount(int maxSkipCount) {
this.maxSkipCount = maxSkipCount;
}
public boolean isUseSlowProtect() {
return useSlowProtect;
}
public void setUseSlowProtect(boolean useSlowProtect) {
this.useSlowProtect = useSlowProtect;
}
public int getProtectMilliseconds() {
return protectMilliseconds;
}
public void setProtectMilliseconds(int protectMilliseconds) {
this.protectMilliseconds = protectMilliseconds;
}
public String getSwitchTempTable() {
String t = EagleeyeHelper.getUserData("t");
if (!StringUtils.isBlank(t) && t.equals("1")) {
return testSwitchTempTable;
} else {
return switchTempTable;
}
}
public void setSwitchTempTable(String switchTempTable) {
this.switchTempTable = switchTempTable;
this.testSwitchTempTable = TEST_TABLE_PREFIX + this.switchTempTable;
}
}