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

遇到了几个需要生产唯一键的场景

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

猜你喜欢

转载自yuren1hao.iteye.com/blog/2333216