天天看點

一種序列号唯一建生成方案

遇到了幾個需要生産唯一鍵的場景

1:倉儲系統,庫内各類單号需要唯一并帶有一定的業務意義

  要求:AAAA20151212000000001  AAAA為艙内業務編碼,中間8位為對應的年月日,後8位為唯一鍵保障。    

2:電商訂單系統,訂單号需要唯一

  要求:28710080047686   後四位為買家Id(for分庫分表),前面資料為唯一鍵保障位。

3:聊天app系統注冊使用者Id與評論Id唯一鍵需求。

  要求:etx32s    使用者Id唯一值

三個需求中,都要求有一個能保障序列号唯一的鍵值。分布式系統中,保障鍵值唯一的方案較多。比如:簡單粗暴的DB自增序列,MD5(基本唯一),以及在場景一中,當時用的tair(阿裡中間件)做的分布式鎖保障自增唯一。本次介紹一個sequence生成中間件産品的設計方案。

整體方案:

一種序列号唯一建生成方案

ps:

 整體方案思路:

 應用叢集每次擷取一個id端,本地緩存。每次使用時,本地擷取。當本地id段使用完之後,再進行一次遠端調用,更新。

 服務端,DB做主從配置,采用MHA異常主從切換。

 容量分析:

 叢集10wqps,500台伺服器。單機id段設定1w長度,對伺服器端的請求壓力不到1qps,服務端無壓力,且絕大部分為本地擷取,無rpc消耗。   

 example:

 1:用戶端初始化

    //初始化用戶端Util

    public static IdGenerator instance(String... password) {

        IdGenerator ig = generators.get(key);

        if (ig == null) {

            synchronized(generators) {

                ig = generators.get(key);

                if (ig == null) {

                    ig = new IdRangeIncrementGenerator(tmpPassword, idRangeDispatcher);

                    generators.putIfAbsent(key, ig);

                }

            }

        }

        return ig;

    }

    //初始化用戶端序列化段與本地Queue

    public IdRangeIncrementGenerator(final String schema, final String table, final String password,

                                     IdRangeDispatcher dispatcher) throws NoSuchGeneratorException {

        super(dispatcher);

        checkArgument(isNotBlank(password), "The password is blank");

        this.password = password;

        //異步擷取idRange

        backThread = new Thread(new Runnable() {

            @Override

            public void run() {

                while (!Thread.currentThread().isInterrupted()) {

                    try {

                        IdRange idRange = retryNext(password);//調用服務端、走DB擷取Id端

                        backQueue.put(new IdRangeRound(idRange));

                    } catch (InterruptedException ie) {

                        logger.error("interrupted.");

                        Thread.currentThread().interrupt();

                        break;

                    } catch (Throwable e) {

                        logger.error("backThread retryNext() error", e);

                    }

                }

            }

        });

        backThread.setName("Raptor-IdGenerator-backThread");

        backThread.setDaemon(true);

        backThread.start();

        IdRangeRound idRange = reload(idRangeRound);

        String msg = "Can't get idRange by password[" + password + "]";

        checkNotNull(idRange, msg);

    }

2 用戶端擷取Id段使用

//直接本地方法擷取

@Override

    public long next(int num) throws NoMoreIdException {

    checkArgument(num > 0, "The num[" + num + "] must be > 0 ");

        long[] ids = new long[num];

        for (int i = 0; i < num; i++) {

       long id;

       IdRangeRound ir = idRangeRound;

       while ((id = ir.next(1)) == -1) {

       //當本地id段不夠使用時,重新遠端擷取更新id段

           ir = reload(ir);

       }

            ids[i] = id;

        }

        return ids;

    }

    //阻塞線程更新,重新擷取Id端

    private synchronized IdRangeRound reload(IdRangeRound ir) {

        if (ir != idRangeRound) {

            return idRangeRound;

        }

        try {

            return idRangeRound = backQueue.take();

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

    }

3 服務端DB容災方案:MHA

繼續閱讀