作者使用了很巧妙的方法,让分布式的系统产生唯一的ID。项目依赖于MySQL的事务性以及行级锁。
首先需要几个数据库,每个DB中创建几张表。用来存储ID,框架对表中的数据进行selec和update操作。我的理解是数据库和表的数量因分布式系统数量和事务数量多少自行设计。每个数据库对应一个DBConfig。表中存的数据大致为name value,name即应用系统名,value是ID。
项目的核心如下:
1.更新ID
首先取消自动提交事务
connection.setAutoCommit(false);
更新事务的第一步是查出当前的ID值
String querySql = "select value from sequence" + tableIndex + " where name='" + sequenceName + "' for update;";
statement = connection.createStatement();
queryResult = statement.executeQuery(querySql);
此处select for update必须配合InnoDB引擎以及数据库事务使用,作用是锁住表一行数据,防止其他事务进行update操作。接着计算新的值:
queryValue = queryValue + step * (dbSize * tableSize);
step:步长,dbSize:数据库数量,tableSize:表数量。意味着每个表中存放的都是没有交集的ID。例如dbSize=2,tableSize=2,step=100,当value==1时,代表1-400这些ID都属于该系统。引入step的目的是减少系统与数据库之间的IO操作,形成一个cache,类似内存调度中的迁移整个块到内存。后面也会看到。接着更新数据:
String updateSql = "update sequence" + tableIndex + " set value= " + queryValue + " where name='" + sequenceName + "';";
int result = statement.executeUpdate(updateSql);
最后提交事务,完成ID值更新
connection.commit();
2. “cache”
前面提到了这个思想,其实现很简单,
if ((nextValue - startValue) % step == 0) {
nextValue = updateValue();
}
nextValue = nextValue + 1;
return nextValue - 1;
uodateValue()就是上面的过程
3.队列
已经有了多个数据库、多张表,那么应用系统应该如何使用呢?
BlockingQueue<SequenceTable> sequenceTableQueue = new ArrayBlockingQueue<>(databaseConfigs.size() * tableSize);
在init过程中创建一个阻塞队列,大小就是表的总数量,将每个sequence表对象放入队列,接下来的操作就显而易见了,当需要获取新的ID是,poll队列,拿到下一个sequenceTable中的值,如果达到step这一临界值,做update操作,如上。拿到了唯一ID后,将table put回队列。实现如下:
SequenceTable sequenceTable = pollFreeConnectionQueue();
if (null == sequenceTable) {
throw new SequenceNoFreeConnectionException("there is no free connection! idName=" + idName);
}
//获取值
long nextValue = sequenceTable.nextValue();
//释放连接
putFreeConnectionQueue(sequenceTable);
return nextValue;
以上就是框架的核心思想和实现。其思想和算法比较清楚。通过创建多数据库、多表;以及step步长减轻数据库的压力,降低了锁的触发频率,进而提高tps。如果要上线的话,需要根据公司内部事务数量测试选择几个数据库几张表,step的大小,以达到最高的效率。很好的框架,点个赞!