博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
主键生成
阅读量:5124 次
发布时间:2019-06-13

本文共 4982 字,大约阅读时间需要 16 分钟。

  早上时候想到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
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(); }

 

转载于:https://www.cnblogs.com/ELMND/p/4863577.html

你可能感兴趣的文章
「一本通 4.1 练习 2」简单题
查看>>
Mybatis 系列2-配置文件
查看>>
Buying Feed, 2010 Nov (单调队列优化DP)
查看>>
【网络流24题】No.7 试题库问题 (最大流,二分图多重匹配)
查看>>
一行代码为UITextField添加收键盘功能
查看>>
重启模块与及关开邮件存储设置功能页面-PHP-shell-py
查看>>
DNS协议详解
查看>>
[OJ] Matrix Zigzag Traversal
查看>>
2015-7.7森林探秘季
查看>>
千位分隔符的完整攻略
查看>>
PHP 递归删除目录中文件
查看>>
小甲鱼Python笔记(下)
查看>>
面试题19:二叉树镜像
查看>>
Android端实时音视频开发指南
查看>>
C++ 一键关闭屏幕
查看>>
关于生活
查看>>
基金基础知识
查看>>
loadrunner学习理论之一
查看>>
C++ 初始化列表初始化列表性能问题的简单的探索
查看>>
MyBatis入门
查看>>