1,000,000 個の ID を処理するのに 9 桁が必要なので、3 桁で遊ぶことができます (ID の 0 ~ 999999 には残りの 6 桁が必要です)。
マルチサーバーのセットアップがあると仮定します。各サーバーに 3 桁のサーバー ID を割り当てると、サーバー間の重複を気にすることなく、各サーバー内で一意の ID 値を割り当てることができます。JVMの再起動を生き残るためには、最近割り当てられた値をディスクにエコーする必要があります(ローカルディスク、memcacheなど、保存したい場所に)。
各リクエストでファイルなどの I/O のオーバーヘッドが発生しないようにするには、ID をブロックに割り当て、ブロックのエンドポイントをストレージにエコー バックします。
したがって、最終的には次のようになります。
- 各サーバーに ID を付与する
- その日の最後に割り当てられた値を格納するストレージをサーバーに用意します (たとえば、ファイル)。
- ID アロケータをブロック単位で動作させる (一度に 10 個の ID、100 個など)
- ブロックを割り当てるには:
- ファイルを読み取り、ブロックサイズを増やした数値を書き戻します
- ブロックの ID を使用する
- ID は、サーバー #12 によって割り当てられた 28 番目の ID の場合、たとえば 12000000027 になります。
- 日が変わると (真夜中など)、現在のブロックを破棄し、新しい日に新しいブロックを割り当てます
疑似コードで:
class IDAllocator {
Storage storage;
int nextId;
int idCount;
int blockSize;
long lastIdTime;
/**
* Creates an IDAllocator with the given server ID, backing storage,
* and block size.
*
* @param serverId the ID of the server (e.g., 12)
* @param s the backing storage to use
* @param size the block size to use
* @throws SomeException if something goes wrong
*/
IDAllocator(int serverId, Storage s, int size)
throws SomeException {
// Remember our info
this.serverId = serverId * 1000000; // Add a million to make life easy
this.storage = s;
this.nextId = 0;
this.idCount = 0;
this.blockSize = bs;
this.lastIdTime = this.getDayMilliseconds();
// Get the first block. If you like and depending on
// what container this code is running in, you could
// spin this out to a separate thread.
this.getBlock();
}
public synchronized int getNextId()
throws SomeException {
int id;
// If we're out of IDs, or if the day has changed, get a new block
if (idCount == 0 || this.lastIdTime < this.getDayMilliseconds()) {
this.getBlock();
}
// Alloc from the block
id = this.nextId;
--this.idCount;
++this.nextId;
// If you wanted (and depending on what container this
// code is running in), you could proactively retrieve
// the next block here if you were getting low...
// Return the ID
return id + this.serverId;
}
protected long getDayMilliseconds() {
return System.currentTimeMillis() % 86400000;
}
protected void getBlock()
throws SomeException {
int id;
synchronized (this) {
synchronized (this.storage.syncRoot()) {
id = this.storage.readIntFromStorage();
this.storage.writeIntToStroage(id + blocksize);
}
this.nextId = id;
this.idCount = blocksize;
}
}
}
...しかし、繰り返しになりますが、これは疑似コードであり、ID が必要なときに ID を待っている I/O をブロックしないように、プロアクティブなものをそこに投入したい場合があります。
上記は、ある種のアプリケーション全体のシングルトンが既にあり、IDAllocator
インスタンスがその単一インスタンスの単なるデータ メンバーであると仮定して書かれています。getInstance
そうでない場合は、従来の方法を使用して、コンストラクターへの引数として受け取るのではなく、環境から構成を読み取ることにより、代わりに上記を簡単にシングルトンにすることができます。