Java
一意な数値

分散環境&マルチスレッドで簡単に一意な数値を生成するJAVAフレームワーク「numatrix」を作った

概要

numatrixは並列で動作する分散環境で一意な64ビットの数値を生成するフレームワークです。

特徴

分散したJVMごとに一意なIDを持つことで、それぞれが独立しながら全体で一意な数値を生成できます。
それぞれが独立しているため、非常に高速で動作します。
スレッドセーフで、スレッドごとに並列で動作します。

仕様

numatrixは64ビットを3つのフィールドに分解し、それぞれに値を割り当てます。
1. タイムスタンプ
このフィールドのビット数は初期設定で32ビットです。
値は基準となる時間から経過した秒数です。基準となる時間は初期設定で2017年11月3日0時です。
値は数値を発行を開始した時に決定され、次の連続した番号が0に戻った時に更新されます。
2. 連続した番号
このフィールドのビット数は前のタイムスタンプと次の環境で一意なIDが消費するビット数の余りです。
値は0から開始し、数値を発行するたびにインクリメントされます。このフィールドのビット数で表すことのできる数値を越えると0に戻り、前のタイムスタンプの値を更新します。
3. 環境で一意なID
このフィールドのビット数は、使用する環境の最大JVM数と最大スレッド数によって決定されます。
値は環境で必ず一意になるように発行された数値です。

先頭のビットは符合を表すので、マイナス値を出力しない場合は0で固定され、使用されません。

使い方

numatrixからソースをダウンロードして、mavenでビルドしてください。

NumatrixNumberGeneratorFactoryを実装

NumatrixNumberGeneratorFactoryクラスを継承したクラスを作成します。

package numatrix;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class NumatrixNumberGeneratorFactoryImple extends NumatrixNumberGeneratorFactory {

  @Override
  protected int getTypeId() {
    return 0;
  }

  @Override
  protected int getJvmId() {
    Properties properties = new Properties();
    try {
      properties.load(new FileInputStream("プロパティファイルのパス"));
  } catch (IOException e) {
      throw new RuntimeException(e);
  }
    return Integer.parseInt(properties.getProperty("jvmid"));
  }

  @Override
  protected int getMaxJvmCount() {
    return 100;
  }

  @Override
  protected int getMaxGeneratorCount() {
    return 300;
  }

}

getTypeId()の実装

このメソッドの返却値ごとに一意な数値が生成できます。
複数の一意な数値を運用する場合、このメソッドを実装する複数のクラスを作成し、使い分けてください。

getJvmId()の実装

数値を一意としたい環境でこの実装クラスを使うJVMごとに一意な値を返却してください。
返却値は0から順番に使用してください。

getMaxJvmCount()の実装

数値を一意としたい環境でこの実装クラスを使うJVMの上限数を返却してください。

getMaxGeneratorCount()の実装

JVMごとに生成されるNumatrixNumberGeneratorインスタンスの上限数を返却してください。
NumatrixNumberGeneratorインスタンスは、一意な数値を生成するスレッドごとに割り当てられます。

NumatrixNumberGeneratorインスタンスから数値を生成

NumatrixNumberGeneratorFactoryの実装クラスのインスタンスから、NumatrixNumberGeneratorインスタンスを取得し、一意な数値を生成します。

public class Sample {

  public static void main(String[] args) throws NumatrixNumberGenerateException {
    NumatrixNumberGeneratorFactoryImple factory = new NumatrixNumberGeneratorFactoryImple();
    NumatrixNumberGenerator generator = factory.getGenerator();
    long uniqueueNumber = generator.generate();

  }

}

NumatrixNumberGeneratorインスタンスの取得

getGenerator()の返却値のNumatrixNumberGeneratorインスタンスは、それを呼んだスレッドごとに異なります。
getGenerator()を呼んだスレッドの参照がなくなり、ガベージコレクションによって消滅できるようになった場合、そのスレッドのNumatrixNumberGeneratorインスタンスは別のスレッドに再利用されます。

一意な数値の生成

NumatrixNumberGenerator#generate()で一意な数値を生成できます。

注意事項

numatrixは2017年から約136年後に利用できなくなります。
回避方法はカスタマイズで説明します。

カスタマイズ

NumatrixNumberGeneratorクラスを継承したクラスを作成し、各メソッドをオーバーライドすることでカスタマイズできます。
NumatrixNumberGeneratorFactoryの実装クラスでgetGenerator()メソッドをオーバライドして、継承したクラスのインスタンスを返却するように実装します。

public class NumatrixNumberGeneratorFactoryImple extends NumatrixNumberGeneratorFactory {

  class NumatrixNumberGeneratorEx extends NumatrixNumberGenerator {
    // TODO NumatrixNumberGeneratorをカスタマイズ
    public NumatrixNumberGeneratorEx(int generatorId, int generatorIdBitLength) {
      super(generatorId, generatorIdBitLength);
    }

  }

  @Override
  protected NumatrixNumberGenerator makeGenerator(int generatorId, int generatorIdBitLength) {
    return new NumatrixNumberGeneratorEx(generatorId, generatorIdBitLength);
  }

一意な数値の秒間最大出力数の増加(性能向上)

64ビットを3分割したフィールドの1つの連続した番号が最大値を超えると、タイムスタンプフィールドの値を更新しなければなりませんが、タイムスタンプフィールドは1秒単位で更新されるため、
連続した番号の最大値 = スレッド毎の一意な数値の秒間最大出力数
となります。
連続した番号のビット数は、他のフィールドが消費するビット数の余りなので、他のフィールドのビット数を小さくすることで最大値を大きくできます。

環境で一意なIDフィールドは、NumatrixNumberGeneratorFactoryのgetMaxJvmCount()とgetMaxGeneratorCount()の実装でビット数が決定するので、それらの返却値を小さくすることでビット数を少なくできます。

その他のフィールドは次のようにビット数を変更します。

マイナス値の出力

マイナス値の出力を許可することで、64ビットの先頭1ビットが利用できるようになり、連続した番号フィールドのビット数を増やすことができます。

public class NumatrixNumberGeneratorEx extends NumatrixNumberGenerator {

  public NumatrixNumberGeneratorEx(int generatorId, int generatorIdBitLength) {
    super(generatorId, generatorIdBitLength);
  }

  @Override
  public boolean isOutMinus() {
    return true;
  }

}

タイムスタンプフィールドのビット数変更

タイムスタンプフィールドのビット数を減らすことで、連続した番号フィールドのビット数を増やすことができます。
タイムスタンプフィールドのビット数は初期値で32ビットで、最大約136年の秒数を表すことができますが、ビット数を1つ減らすたびに、タイムスタンプの最大値は半減し、利用できる期限が短くなります。

public class NumatrixNumberGeneratorEx extends NumatrixNumberGenerator {

  public NumatrixNumberGeneratorEx(int generatorId, int generatorIdBitLength) {
    super(generatorId, generatorIdBitLength);
  }

  @Override
  public int getTimestampBitLength() {
    // 32ビットから減らす
    return 31;
  }

}

利用できる期限(2017年から136年)の延長

64ビットを3分割したフィールドの1つのタイムスタンプは、フィールドのビット数(初期設定で32ビット)で表すことのできる数値を超えることができません。
タイムスタンプは基準時間(初期設定で2017年11月3日0時)から経過した秒数なので、基準時間を変更することで利用できる期限を延長できます。

基準時間の変更

基準時間を変更します。
基準時間はnumatrixの利用を開始する時間より前の時間を定義してください。

public class NumatrixNumberGeneratorEx extends NumatrixNumberGenerator {

  public NumatrixNumberGeneratorEx(int generatorId, int generatorIdBitLength) {
    super(generatorId, generatorIdBitLength);
  }

  @Override
  public Date getBaseTime() {
    try {
      // 基準時間の変更
      return new SimpleDateFormat("yyyyMMdd").parse("20171111");
    } catch (ParseException e) {
      throw new RuntimeException();
    }
  }

}