TDDL 在分布式下的SEQUENCE原理

TDDL大家应该很熟悉了,淘宝分布式数据层。很好的为我们实现了分库分表、Master/Salve、动态数据源配置等功能。

那么分布式之后,数据库自增序列肯定用不了了,如何方便快捷的解决这个问题呢?TDDL也提供了SEQUENCE的解决方案。

下面就来简单剖析一下实现原理。。。。。。

第一步:创建一张sequence对应的表。

CREATE TABLE `imp_sequence` (
  `BIZ_NAME` varchar(45) NOT NULL COMMENT '业务名称',
  `CURRENT_VALUE` int(11) NOT NULL COMMENT '当前最大值',
  `GMT_CREATE` datetime DEFAULT NULL COMMENT '创建时间',
  `GMT_MODIFIED` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`BIZ_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='数据序列表';

表名和字段可以按各自规则定义,定义之后需要与第二步DAO中的定义相对应!


几张逻辑表需要声明几个sequence

第二步:配置sequenceDao

<bean id="sequenceDao" class="com.taobao.tddl.client.sequence.impl.DefaultSequenceDao">
		<!-- 数据源 -->
		<property name="dataSource"  ref="dataSource" />
		<!-- 步长-->
		<property name="step" value="1000" />
		<!-- 重试次数-->
		<property name="retryTimes" value="1" />
		<!-- sequence 表名-->
		<property name="tableName" value="gt_sequence" />
		<!-- sequence 名称-->
		<property name="nameColumnName" value="BIZ_NAME" />
		<!-- sequence 当前值-->
		<property name="valueColumnName" value="CURRENT_VALUE" />
		<!-- sequence 更新时间-->
		<property name="gmtModifiedColumnName" value="gmt_modified" />
	</bean>
第三步:配置sequence生成器
	<bean id="businessSequence"  class="com.taobao.tddl.client.sequence.impl.DefaultSequence">
		<property name="sequenceDao" ref="sequenceDao"/>
		<property name="name" value="business_sequence" />
	</bean>
第四步:调用
public class IbatisSmDAO extends SqlMapClientDaoSupport implements SmDAO {

  /**smSequence*/
  private DefaultSequence   businessSequence;
   
    public int insert(SmDO sm) throws DataAccessException {
    	if (sm == null) {
    		throw new IllegalArgumentException("Can't insert a null data object into db.");
    	}
    	
    	try {
            sm.setId((int)businessSequence.nextValue());
        } catch (SequenceException e) {
            throw new RuntimeException("Can't get primary key.");
        }
        
        getSqlMapClientTemplate().insert("MS-SM-INSERT", sm);

        return sm.getId();
    }
}
从调用配置中,我们可以发现其中涉及到二个重要类DefaultSequenceDao和DefaultSequence,这二个都是TDDL的默认实现。DefaultSequenceDao:序列DAO默认实现,JDBC方式。DefaultSequence:序列默认实现

先来看DefaultSequenceDao,TDDL中提供了默认的表名,列名和步长等,第一步的建表可以参照默认方式。

private static final int MIN_STEP = 1;
	private static final int MAX_STEP = 100000;
	private static final int DEFAULT_STEP = 1000;
	private static final int DEFAULT_RETRY_TIMES = 150;

	private static final String DEFAULT_TABLE_NAME = "sequence";
	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 long DELTA = 100000000L;

	private DataSource dataSource;

	/**
	 * 重试次数
	 */
	private int retryTimes = DEFAULT_RETRY_TIMES;

	/**
	 * 步长
	 */
	private int step = DEFAULT_STEP;

	/**
	 * 序列所在的表名
	 */
	private String tableName = DEFAULT_TABLE_NAME;

	/**
	 * 存储序列名称的列名
	 */
	private String nameColumnName = DEFAULT_NAME_COLUMN_NAME;

	/**
	 * 存储序列值的列名
	 */
	private String valueColumnName = DEFAULT_VALUE_COLUMN_NAME;

	/**
	 * 存储序列最后更新时间的列名
	 */
	private String gmtModifiedColumnName = DEFAULT_GMT_MODIFIED_COLUMN_NAME;
接下来看一下nextRange方法:取得下一个可用的序列区间
public SequenceRange nextRange(String name) throws SequenceException {
		if (name == null) {
			throw new IllegalArgumentException("序列名称不能为空");
		}

		long oldValue;
		long newValue;

		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;

		for (int i = 0; i < retryTimes + 1; ++i) {
			try {
				conn = dataSource.getConnection();
				stmt = conn.prepareStatement(getSelectSql());
				stmt.setString(1, name);
				rs = stmt.executeQuery();
				rs.next();
				oldValue = rs.getLong(1);

				if (oldValue < 0) {
					StringBuilder message = new StringBuilder();
					message.append("Sequence value cannot be less than zero, value = ").append(oldValue);
					message.append(", please check table ").append(getTableName());

					throw new SequenceException(message.toString());
				}

				if (oldValue > Long.MAX_VALUE - DELTA) {
					StringBuilder message = new StringBuilder();
					message.append("Sequence value overflow, value = ").append(oldValue);
					message.append(", please check table ").append(getTableName());

					throw new SequenceException(message.toString());
				}

				newValue = oldValue + getStep();
			} catch (SQLException e) {
				throw new SequenceException(e);
			} finally {
				closeResultSet(rs);
				rs = null;
				closeStatement(stmt);
				stmt = null;
				closeConnection(conn);
				conn = null;
			}

			try {
				conn = dataSource.getConnection();
				stmt = conn.prepareStatement(getUpdateSql());
				stmt.setLong(1, newValue);
				stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
				stmt.setString(3, name);
				stmt.setLong(4, oldValue);
				int affectedRows = stmt.executeUpdate();
				if (affectedRows == 0) {
					// retry
					continue;
				}

				return new SequenceRange(oldValue + 1, newValue);
			} catch (SQLException e) {
				throw new SequenceException(e);
			} finally {
				closeStatement(stmt);
				stmt = null;
				closeConnection(conn);
				conn = null;
			}
		}

		throw new SequenceException("Retried too many times, retryTimes = " + retryTimes);
	}


通过getSelectSql查询最新的value值,然后加上步点,通过getUpdateSql更新到数据库中

private String getSelectSql() {
		if (selectSql == null) {
			synchronized (this) {
				if (selectSql == null) {
					StringBuilder buffer = new StringBuilder();
					buffer.append("select ").append(getValueColumnName());
					buffer.append(" from ").append(getTableName());
					buffer.append(" where ").append(getNameColumnName()).append(" = ?");

					selectSql = buffer.toString();
				}
			}
		}

		return selectSql;
	}

	private String getUpdateSql() {
		if (updateSql == null) {
			synchronized (this) {
				if (updateSql == null) {
					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(" = ?");

					updateSql = buffer.toString();
				}
			}
		}

		return updateSql;
	}
有一个特殊需要说明的,在update语句中,where需要把之前的value当成条件传入。实现了类型version的乐观锁操作。如果同一个时间AB二台机器同时请求获取到相同的value,进行update操作只有可能一条成功。失败的会按retryTimes进行重试。

接下来看DefaultSequence,比较简单,就不说明了

public class DefaultSequence implements Sequence {
	private final Lock lock = new ReentrantLock();

	private SequenceDao sequenceDao;

	/**
	 * 序列名称
	 */
	private String name;

	private volatile SequenceRange currentRange;

	public long nextValue() throws SequenceException {
		if (currentRange == null) {
			lock.lock();
			try {
				if (currentRange == null) {
					currentRange = sequenceDao.nextRange(name);
				}
			} finally {
				lock.unlock();
			}
		}

		long value = currentRange.getAndIncrement();
		if (value == -1) {
			lock.lock();
			try {
				for (;;) {
					if (currentRange.isOver()) {
						currentRange = sequenceDao.nextRange(name);
					}

					value = currentRange.getAndIncrement();
					if (value == -1) {
						continue;
					}

					break;
				}
			} finally {
				lock.unlock();
			}
		}

		if (value < 0) {
			throw new SequenceException("Sequence value overflow, value = " + value);
		}

		return value;
	}

	public SequenceDao getSequenceDao() {
		return sequenceDao;
	}

	public void setSequenceDao(SequenceDao sequenceDao) {
		this.sequenceDao = sequenceDao;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}









没有更多推荐了,返回首页