背景:以前一直使用数据库的自增ID作为数据表的主键,ID的唯一性由数据库控制,在传统单机系统环境下,该方式一直是比较主流的方案。然而特定情况下,需要在插入数据前就获得记录的ID,对以该ID为基础做一些业务处理(如生成唯一业务编码),这就需要在应用层面由程序控制来生成该ID。虽然应用程序可以根据时间,机器MAC,随机数等一些因子来生成ID,或者直接使用UUID;但这些方案生成的ID长度均较大,无法兼容INT类型,由此产生了方案一。
方案一:
在程序中定义一个INT值,记录当前使用的ID值,新对象的ID为该INT值加一。为保障多线程安全,使用轻量级AtomicInteger存储(也可使用ReentrantLock)。为了系统重启后仍然保持之前的ID,每次更新ID时同时将最新值记录到数据库,系统重启后优先从数据库中读取。
方案一改进:
每次更新数据库性能太低,增加CACHE_SIZE属性,采用分配策略,程序中优先一次性预申请多个ID备用,只有当缓存的ID都用完后才再次向数据库申请新的ID,并更新数据库中的最新值。该方案可以满足大部分的应用需要,并且性能还不错。
方案一的缺点:
无法在分布式环境下使用。由于数据锁是应用内控制的,在分布式环境下将失效,多个请求在向数据库申请新的ID时,会出现重复读的情况,申请ID的请求是不允许并发的,需要通过数据库层面的加锁才能实现请求的串行化。
方案一的再次改进(数据库加锁):
以MySql为例,在申请前加写锁:LOCK TABLE TABLE_IDENTITY WRITE,将其他进程的读请求锁定,申请完后解锁:UNLOCK TABLES。后续请求读取的将是更新后的新ID。
方案一的再次改进(数据分区):
为每个分布式节点分配一个唯一ID(如1,2,3),将ID按分布式节点数切分为多个数据块(如100,200,300),第一个节点可用ID范围为1-100,第二个节点可用范围为101-200,第三个节点可用范围为201-300。在系统规模可估的情况下,该方案可有效解决并发冲突,而且无需加锁。