主键生成

  早上时候想到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();
    }
时间: 2024-08-21 19:32:40

主键生成的相关文章

Hibernate之:各种主键生成策略与配置详解

1.assigned 主键由外部程序负责生成,在 save() 之前必须指定一个.Hibernate不负责维护主键生成.与Hibernate和底层数据库都无关,可以跨数据库.在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免. <id name="id" column="id"> <generator class="assigned" /> </id&g

hibernate系列笔记(4)---主键生成策略

主键生成策略 常见的生成策略分为六种 1.increment 由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的数据库,因此可以跨数据库. <id name="id" column="id"> <generator class="increment" /> </id> Hibernate调用org.hibernate.id.

160727、自定义hibernate主键生成策略生成字符串+数字自增长

需求:需要自增长注解如MyId0001.MyId0002.MyId0003 实现:实现这个接口org.hibernate.id.IdentifierGenerator 一.MyIdGenerator.java(测试用,实际项目中获取链接等可以改变) import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import

Hibernate(4)——主键生成策略、CRUD 基础API区别的总结 和 注解的使用

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下: hibernate的主键生成策略 UUID 配置的补充:hbm2ddl.auto属性用法 注解还是配置文件 hibernate注解的基本用法 使用Session API CRUD操作对象,以及对象状态的转换 hibernate缓存的概念 get()/load()的区别到底是什么,源码分析 代理模式实现的懒加载 saveOrUpdate()/merge()的区别 Assigned(常用,一般情况使用很方便):

MyBatis对不同数据库的主键生成策略

本文转自:http://289972458.iteye.com/blog/1001851  http://hi.baidu.com/zim_it/blog/item/8a2bd11205f5b56ecb80c4b7.html 在使用MyBatis做持久层时,insert语句默认是不返回记录的主键值,而是返回插入的记录条数:如果业务层需要得到记录的主键时,可以通过配置的方式来完成这个功能 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录,如Oracle.D

分布式主键生成策略

很多分布式的应用系统,会有这个需求:唯一主键的生成 分布式唯一主键生成架构: SQL操作: 如有不明请留言. 分布式主键生成策略,布布扣,bubuko.com

JPA主键生成策略

@GeneratedValue: 为一个实体类生成一个唯一标识的主键(JPA要求每一个实体Entity,必须有且只有一个主键).它有两个属性,分别是strategy和generator. generator:默认为空字符串,它定义了主键生成器的名称,对应的生成器有两个:对应于同名的主键生成器@SequenceGenerator和@TableGenerator. strategy:一共有四种,被定义在枚举类GenerationType中,包括:TABLE, SEQUENCE, IDENTITY 和

[转]hibernate主键生成策略

1.assigned 主键由外部程序负责生成,在 save() 之前必须指定一个.Hibernate不负责维护主键生成.与Hibernate和底层数据库都无关,可以跨数据库.在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免. <id name="id" column="id"> <generator class="assigned" /> </id&g

hibernate主键生成策略

引用:http://www.cnblogs.com/hoobey/p/5508992.html 1.assigned 主键由外部程序负责生成,在 save() 之前必须指定一个.Hibernate不负责维护主键生成.与Hibernate和底层数据库都无关,可以跨数据库.在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免. <id name="id" column="id"> <gene

Hibernate常用主键生成策略

1.assign:适合于应用程序维护的自然主键. 2.increment:代理主键,适合于所有数据库,由hibernate维护主键自增,和底层数据库无关,但是不适合于2个或以上hibernate进程. 3.identity:代理主键,适合于mysql或ms sql server等支持自增的dbms,主键值不由hibernate维护. 4.sequence:代理主键,适合于oracle等支持序列的dbms,主键值不由hibernate维护,由序列产生. 5.native:代理主键,根据底层数据库的