English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
サーバーシステムの開発中に、データの大規模な並行リクエストに対応するために、データのアシンクリナスストレージが必要です。特に分散システムを作成する場合、データベースへの挿入が完了してIDを自動的に取得を待つことはできず、データベースへの挿入前にグローバルユニークなIDを生成する必要があります。ゲームサーバーでは、グローバルユニークなIDは将来の合服を簡単にし、キーの衝突が発生しません。また、ビジネスの成長に応じて分庫分表を実現することもできます。例えば、あるユーザーのアイテムを同じスライス内に配置する必要があり、そのスライスはユーザーIDの範囲値に基づいて決定される場合があります。例えば、ユーザーIDが1000が小さい100000のユーザーが一つのスライス内にいます。現在一般的に使用されているものには以下のようなものがあります:
1Javaの標準のUUID。
UUID.randomUUID().toString()は、サービスプログラムのローカル生成が可能で、IDの生成はデータベースの実装に依存しません。
利点:
ローカルでIDを生成し、リモートコールを行う必要がありません。
全体的にユニークで重複しません。
水平エキスパンション能力が非常に良いです。
欠点:
IDには128 bitsは空間を大きく占め、文字列型に保存する必要があり、索引効率が非常に低いです。
生成されたIDにはTimestampが含まれていないため、トレンドの増分を保証できず、データベースの分庫分表時の依存関係が難しいです。
2Redisのincrメソッドに基づいて
Redis自体は単一スレッド操作ですが、incrは原子的な増分操作を保証しており、増分ステップの設定もサポートしています。
利点:
デプロイメントが簡単で、使いやすいです。RedisのAPIを呼び出すだけで良いです。
複数のサーバーが同じRedisサービスを共有し、共有データの開発時間を短縮できます。
Redisはクラスタリングデプロイメントが可能で、単一ポイントの故障問題を解決できます。
欠点:
システムが非常に大きい場合、多数のサービスが同時にRedisにリクエストを送信すると、パフォーマンスのボトルネックが発生します。
3Flickerからの解決策
この解決策はデータベースの自動増分IDに基づいており、IDの生成に専用のデータベースを使用しています。詳細については、インターネットで検索してみてください。個人的には使い方が面倒くさいと思っており、推奨しません。
4、Twitter Snowflake
SnowflakeはTwitterがオープンソースで提供する分散ID生成アルゴリズムで、その核心的な考え方は:long型のIDを生成し、その中で使用します41ビットをミリ秒として使用します10ビットを機器番号として使用します12ビットをミリ秒内のシーケンス番号として使用します。このアルゴリズムは、単機で一秒間に理論的には最大で生成できます1000*(2^12)個、つまり約400WのIDであれば、ビジネスの要件を完全に満たすことができます。
Snowflakeアルゴリズムの考え方に基づいて、私たちのビジネスシーンに応じて、独自のグローバルユニークなIDを生成できます。なぜならJavaのlong型の長さは64bits、だから私たちが設計したIDは制御する必要があります64bits。
利点:高性能、低遅延;独立したアプリケーション;時間順序で整っています。
欠点:独立な開発とデプロイが必要です。
例えば、私たちが設計したIDには以下の情報が含まれています:
| 41 bits: Timestamp | 3 bits: 区域 | 10 bits: 機器番号 | 10 bits: シーケンス号 |
ユニークなIDを生成するJavaコード:
/** * カスタムID生成器 * ID生成規則: IDは長さ 64 bits * * | 41 bits: Timestamp (ミリ秒) | 3 bits: 区域(機房) | 10 bits: 機器番号 | 10 bits: シーケンス号 | */ public class GameUUID{ // 基準時間 private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT // 地域フラグビット数 private final static long regionIdBits = 3L; // 機器識別ビット数 private final static long workerIdBits = 10L; // シーケンス識別ビット数 private final static long sequenceBits = 10L; // 地域フラグIDの最大値 private final static long maxRegionId = -1L ^ (-1L << regionIdBits); // 機器IDの最大値 private final static long maxWorkerId = -1L ^ (-1L << workerIdBits); // シーケンスIDの最大値 private final static long sequenceMask = -1L ^ (-1L << sequenceBits); // 機器ID左シフト10ビット private final static long workerIdShift = sequenceBits; // ビジネスIDを左にシフト20ビット private final static long regionIdShift = sequenceBits + workerIdBits; // 時間ミリ秒を左にシフト23ビット private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits; private static long lastTimestamp = -1L; private long sequence = 0L; private final long workerId; private final long regionId; public GameUUID(long workerId, long regionId) { // 範囲を超えた場合、例外を投げます if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0"); if (regionId > maxRegionId || regionId < 0) { throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0"); this.workerId = workerId; this.regionId = regionId; public GameUUID(long workerId) { // 範囲を超えた場合、例外を投げます if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0"); this.workerId = workerId; this.regionId = 0; public long generate() { return this.nextId(false, 0); /** * 実際にコードを生成する * * @param isPadding * @param busId * @return */ private synchronized long nextId(boolean isPadding, long busId) { long timestamp = timeGen(); long paddingnum = regionId; if (isPadding) { paddingnum = busId; if (timestamp < lastTimestamp) { try { throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds"); } catch (Exception e) { e.printStackTrace(); //如果上次生成时间和当前时间相同,在同一毫秒内 if (lastTimestamp == timestamp) { //sequence自增,因为sequence只有10bit,所以和sequenceMask相与一下,去掉高位 sequence = (sequence + 1) & sequenceMask; //判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0 if (sequence == 0) { //自旋等待到下一毫秒 timestamp = tailNextMillis(lastTimestamp); } else { // 如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加, // 为了保证尾数随机性更大一些,最后一位设置一个随机数 sequence = new SecureRandom().nextInt(10; lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence; // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势. private long tailNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); return timestamp; // 現在のタイムスタンプを取得する protected long timeGen() { return System.currentTimeMillis();
使用する場合に注意すべき点は以下の通りです:
成長の傾向を維持するために、一部のサーバーの時間が早く、一部のサーバーの時間が遅いことが避けられます。すべてのサーバーの時間を制御し、NTP時間サーバーからのサーバーの時間のリセットを避ける必要があります。ミリ秒を越える際には、シリアル番号は常に0に戻りますが、0のシリアル番号が多くなり、生成されたIDが取模されると不均等になるため、シリアル番号は毎回0に戻されず、0から9のランダム数。
述べた方法は、自分の必要に応じて選択できます。ゲームサーバーの開発では、ゲームの種類に応じて選択します。例えば、モバイルゲームの場合、簡単で誤りが少ないredisを使用することができます。このゲームの単一サーバーでのIDの生成量はそれほど多くありませんので、十分に満足できます。一方、大規模なワールドゲームサーバーの場合、分散型が主になりますので、snowflakeの方法を使用することができます。上記のsnowflakeコードは例であり、自分の必要に応じてカスタマイズする必要があります。したがって、追加の開発が必要であり、上記の注意事項に注意する必要があります。
以上の方法について、編集者が皆さんに紹介したJavaコードを使用したゲームサーバーでのユニークなID生成方法のまとめです。皆さんに役立つことを願っています。何かご不明な点がございましたら、コメントを残してください。編集者は迅速に回答いたします。このサイトへのサポートにも感謝しております。
声明:この記事の内容はインターネットから収集され、著作権者に帰属します。インターネットユーザーが自発的に貢献し、アップロードしたものであり、このウェブサイトは所有権を持ちません。人工編集は行われていません。また、関連する法的責任を負いません。著作権侵害が疑われる場合は、メールで notice#w までお知らせください。3codebox.com(メールを送信する際、#を@に置き換えてください。侵害を報告する場合、関連する証拠を提供してください。一旦確認がとれましたら、このサイトは侵害された内容をすぐに削除します。)