• 主键生成


      早上时候想到ID生成这一回事,随便记下。

      我们很多时候会用到数据库。而数据表中的记录基本上都是有主键的。读书的时候,最常见的主键生成方式,就是主键自增。例如:

    `record_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID,自增,全局唯一'

      很多时候系统小,这个策略也是够用的了。然而当系统大了点,要考虑分布式,甚至数据库双写之类,这样的策略是不够的。

      简单归纳一下,我把主键生成分为几种策略:

    • 主键自增
    • 单点管理ID
    • mac地址+时间戳+原子自增

    主键自增

    `record_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID,自增,全局唯一'

      这是相当常用,简单的方式。好处是交给数据库来处理了,研发人员减少了好多工作。最主要的缺点是数据恢复的情况下会主键冲突。举个例子,系统做了双机房,想做一个数据库的异地双向同步。那么当双方还没同步的情况下,可能录入了同样的ID。当然了,只是双机房的话还是可以用 increase by 的方式,把数据库自增步伐修改为奇偶。比如说机房1的主库是基数的ID,机房2的主库是偶数的ID。双向同步创建数据来说就没有冲突了。(双向同步还有好多问题的,并发下的update时序问题等这里不展开讨论)

    单点管理ID

      因为没有参与过实际的线上案例,这里简单说说。就是在一个地方专门管理ID。例如zookeeper、自己提供一个ID生成服务。然而这个ID生成服务是不依赖于数据库的。这样做的好处是可以做ID回收机制。然而实现起来相对麻烦点,而且多机房下也是要多套ID管理服务的。这样的方案并不常见。

    mac地址+时间戳+原子自增

      最后提及的这个方案还是比较常见的。 在分布式的情况下,完全避免了id生成冲突的问题。而且实现成本不高。ID的唯一性由机器的唯一性(机器mac地址)+时间戳+原子自增来保证。 最终生成一个long类型的数值,或者一个字符串。 字符串的好处是可以可以增加一些特定标识在ID中。long类型的好处是ID排序。

    一些代码片段:

        public static final String CLUSTER_APPID_KEY = "cluster.appid";
        private static final Charset utf8Charset = Charset.forName("utf-8");
        private static Pattern ptn = Pattern.compile("([0-9]{1})_([0-9]{1,2})");
        private static final AtomicInteger atomicId = new AtomicInteger(1);
        private static final int APP_ID_INC = 1000000;
        private static int appId = 101 * APP_ID_INC;
    
        static{
            //初始化appId,默认没有配置,为mac地址crc16计算值
            initAppId(null);
        }
        /*
         *根据配置的ID,做解析,配置示例:
         *appId=IdcId_HostId,
         *例如:appId=1_01,appId=1_02;appId=2_01,appId=2_02;
         * */
        public static void initAppId(String cfgAppId) {
            appId = parseAppId(cfgAppId);
            if (0 == appId) {
                appId = generateRandId();
            }
            Logger.warn("IdGenerator: APP-ID: %d", appId);
        }
    
        private static int parseAppId(String cfgAppId) {
            try {
                if (null == cfgAppId) {
                    return 0;
                }
    
                Matcher matcher = ptn.matcher(cfgAppId);
                if (matcher.find()) {
                    String idcId = matcher.group(1);
                    int nIdcId = Integer.parseInt(idcId);
                    String hostId = matcher.group(2);
                    int nHostId = Integer.parseInt(hostId);
                    int appId = nIdcId * 100 + nHostId;
                    return appId * APP_ID_INC;
                }
            } catch (Exception e) {
                //ignore
            }
            return 0;
        }
    
        private static int generateRandId() {
            String mac = UUID.randomUUID().toString();
            try {
                String tmpMac = getMacAddress();
                if (null != tmpMac) {
                    mac = tmpMac;
                }
            } catch (Exception e) {
                //ignore
            }
            int tmpRst = getChecksum(mac);
            if (tmpRst < 999 && tmpRst > 0) {
                return tmpRst * APP_ID_INC;
            }
            //大于999,取余数
            int mod = tmpRst % 999;
            if (mod == 0) {
                //不允许取0
                mod = 1;
            }
            return mod * APP_ID_INC;
        }
    
        private static String getMacAddress() throws Exception {
            Enumeration<NetworkInterface> ni = NetworkInterface.getNetworkInterfaces();
            while (ni.hasMoreElements()) {
                NetworkInterface netI = ni.nextElement();
                if (null == netI) {
                    continue;
                }
                byte[] macBytes = netI.getHardwareAddress();
                if (netI.isUp() && !netI.isLoopback() && null != macBytes && macBytes.length == 6) {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0, nLen = macBytes.length; i < nLen; i++) {
                        byte b = macBytes[i];
                        //与11110000作按位与运算以便读取当前字节高4位  
                        sb.append(Integer.toHexString((b & 240) >> 4));
                        //与00001111作按位与运算以便读取当前字节低4位  
                        sb.append(Integer.toHexString(b & 15));
                        if (i < nLen - 1) {
                            sb.append("-");
                        }
                    }
                    return sb.toString().toUpperCase();
                }
            }
            return null;
        }
    
        /**
         * 获取对应的CRC16校验码
         * @param input 待校验的字符串
         * @return 返回对应的校验和
         */
        private static int getChecksum(String input) {
            if (null == input) {
                return 0;
            }
            byte[] data = input.getBytes(utf8Charset);
            CRC16 crc16 = new CRC16();
            for (byte b : data) {
                crc16.update(b);
            }
            return crc16.value;
        }
    
        /**
         * 获取随机数,加大随机数位数,是为了防止高并发,且单个并发中存在循环获取ID的场景
         * 如果您的应用并发有200以上,且每个并发中都存在循环调用获取ID的场景,可能会发生ID冲突
         * 对应的解决方法是:在循环逻辑中加入休眠1-5ms的逻辑
         * @return
         */
        private static int getRandNum() {
            int num = atomicId.getAndIncrement();
            if (num >= 999999) {
                atomicId.set(0);
                return atomicId.getAndIncrement();
            }
            return num;
        }
        
        public static Long getId() {
           
            long id = getBasicId();
            return Long.valueOf(id);
        }
    
        public static long getBasicId() {
            return (System.currentTimeMillis() / 1000) * 1000000000 + appId + getRandNum();
        }
  • 相关阅读:
    《机器学习》周志华 习题答案8.5
    《机器学习》周志华 习题答案8.3
    《机器学习》周志华 习题答案7.3
    《机器学习》周志华 习题答案6.2
    《机器学习》周志华 习题答案4.3
    Python使用wxPython、py2exe编写桌面程序-乾颐堂
    python生成验证码,文字转换为图片-乾颐堂
    python使用wmi模块获取windows下的系统信息监控系统-乾颐堂
    Python图像处理库:Pillow 初级教程-乾颐堂
    python的metaclass浅析-乾颐堂
  • 原文地址:https://www.cnblogs.com/ELMND/p/4863577.html
Copyright © 2020-2023  润新知