Hi guys,
I have written something that I think is a generic clusterable id generator. It doesn't require your db to support locking and its all written in standard java (no j2ee dependancies).
I want to know
1) if you can spot a flaw in the ideas and principles of the mechanism
2) if you can spot a flaw in the implementation.
3) if I'm reinventing the wheel or not. maybe someone has already done it or maybe its already in the hibernate distro somewhere.
strategy : every node uses the sequence block pattern. for each node there is
a row in the database that keeps track of the next free sequence. the trick
is that each node only consumes a portion of the ids from the block it acquires.
each node consumes a different portion of the block.
every node is configered with its id and the total number of nodes in the
cluster.
e.g. 3 nodes, blocksize 100. Nodes are numbered 1, 2 and 3. The 3 rows in
the database look like this
Code:
NODE_ID | NEXT_FREE_ID
----------------------
1 | 1
2 | 1
3 | 1
Whenever a node needs a block of ids, it will request a block of 300 ids. for
the first blocks this results in node one using ids 1-100, node 2 will use ids
101-200 and node 3 will use 201-300.
Within a node I use static synchronization as a row-locking mechanism.
In the next code-fragment consider following equalities :
* PersistenceSessionFactory == hibernate SessionFactory
* PersistenceSession == hibernate Session
Code:
public class SequenceBlockIdGenerator implements IdGenerator {
private static long nextId = 0;
private static long lastId = -1;
private PersistenceSessionFactory persistenceSessionFactory = null;
private long nodeId = -1;
private long totalNodes = -1;
private long blockSize = -1;
public SequenceBlockIdGenerator( JbpmConfiguration jbpmConfiguration ) {
persistenceSessionFactory = jbpmConfiguration.getPersistenceSessionFactory();
nodeId = jbpmConfiguration.getLong( "jbpm.id.generator.node.id" );
totalNodes = jbpmConfiguration.getLong( "jbpm.id.generator.total.nodes" );
blockSize = jbpmConfiguration.getLong( "jbpm.id.generator.block.size" );
}
public long getNextId() {
return getNextId( persistenceSessionFactory, nodeId, totalNodes, blockSize );
}
public static synchronized long getNextId( PersistenceSessionFactory persistenceSessionFactory, long nodeId, long totalNodes, long blockSize ) {
long generatedId = 0;
// if there are no more ids available in the current block
if ( lastId < nextId ) {
// get a new block of ids
log.debug("getting a new block of ids: " + nextId + ", " + lastId );
PersistenceSession persistenceSession = persistenceSessionFactory.createPersistenceSession();
persistenceSession.beginTransaction();
// a SequenceBlock-object corresponds to one row in the sequence-table
SequenceBlock sequenceBlock = null;
try {
sequenceBlock = (SequenceBlock) persistenceSession.load( SequenceBlock.class, new Long( nodeId ) );
} catch (InvalidIdException e) {
sequenceBlock = new SequenceBlock( new Long( nodeId ) );
}
// a block is reserved for all nodes in the cluster
long firstIdOfCompleteBlock = sequenceBlock.getNextBlock(blockSize * totalNodes);
// this node only consumes a part of the block
nextId = firstIdOfCompleteBlock + (blockSize * nodeId);
lastId = nextId + blockSize - 1;
persistenceSession.save( sequenceBlock );
persistenceSession.commitTransaction();
persistenceSession.close();
log.debug("got a new block of ids: " + nextId + ", " + lastId );
}
// take the first available id
generatedId = nextId++;
return generatedId;
}
private static Log log = LogFactory.getLog(SequenceBlockIdGenerator.class);
}
Regards, Tom.