当前位置: 首页 > ops >正文

TDDL生成全局ID原理

TDDL 在分布式下的SEQUENCE原理

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

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

总述

在数据库中创建 sequence 表,用于记录,当前已被占用的id最大值。

每台客户端主机取一个id区间(比如 1000~2000)缓存在本地,并更新 sequence 表中的id最大值记录。

客户端主机之间取不同的id区间,用完再取,使用乐观锁机制控制并发。

第一步:创建一张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>
View Code

第三步:配置sequence生成器

<bean id="businessSequence"  class="com.taobao.tddl.client.sequence.impl.DefaultSequence"><property name="sequenceDao" ref="sequenceDao"/><property name="name" value="business_sequence" />
</bean>
View Code

第四步:调用

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();}
}
View Code

从调用配置中,我们可以发现其中涉及到二个重要类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;
View Code

接下来看一下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) {// retrycontinue;}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);}
View Code

通过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;}
View Code

有一个特殊需要说明的,在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;}
}
View Code

 

转载于:https://www.cnblogs.com/fanguangdexiaoyuer/p/10946666.html

http://www.xdnf.cn/news/11532.html

相关文章:

  • 图像处理算法——图像常用颜色空间
  • 分享88个焦点幻灯JS特效,总有一款适合您
  • 如何实现快速被Google收录
  • H5、web端页面的性能测试案例以及页面性能测试分析
  • Dreamweaver2021安装教程与创建第一个网页
  • Oracle日期函数集锦
  • 卷积神经网络(CNN)基础
  • Java计算机毕业设计销售合同管理系统(开题报告+源码+论文)
  • 机器人/多机器人控制常用软件介绍
  • Web Service 的工作原理
  • 霍兰德职业兴趣测试全解析,助你找到理想工作!
  • HTC G7 官方ROM卡刷包(国行、台版、港版、印度、亚太版、欧版)
  • 全自动发卡网搭建01(站点搭建)
  • 抖音测试距离的软件,抖音测量长度的软件如何使用?抖音测距仪使用方法介绍...
  • 推荐三款格式转换软件轻松转换3gp格式
  • iframe属性与用法
  • 全球最厉害的 14 位程序员,你认识几位?
  • 数据结构(C语言第2版) 课后习题答案之第四章 串、数组和广义表
  • Android 开发一定要看的15个实战项目(2)
  • CTF writeup 1_网络安全实验室
  • 金盾加密视频提取,真实机器码在这里
  • 撸呀撸的左手(KMP+DP)
  • 《北京遇上西雅图》[HD-RMVB.720p.国语中字][2013年爱情喜剧]
  • 安装VC,NTVDM CPU 遇到无效指令 --绝对能用的解决方法
  • 贝叶斯理论
  • wifi 暴力破解 (python)
  • 堆球问题,开普勒猜想(格密码相关)
  • 如何做国外SEO推广
  • springboard常用的使用方法和注解
  • 深入分析:香港 windows 和linux VPS 区别和使用需求