前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >三分钟学习分布式ID方案

三分钟学习分布式ID方案

作者头像
JavaQ
发布2018-07-26 10:34:30
3960
发布2018-07-26 10:34:30
举报
文章被收录于专栏:JavaQJavaQ

为什么使用分布式ID

在分布式系统中,当数据库数据量达到一定量级的时候,需要进行数据拆分、分库分表操作,传统使用方式的数据库自有的自增特性产生的主键ID已不能满足拆分的需求,它只能保证在单个表中唯一,所以需要一个在分布式环境下都能使用的全局唯一ID。

可用方案

1.UUID UUID是指在一台机器上生成的数字,主要由当前日期和时间、时钟序列和全局唯一的IEEE机器识别号组合而成,由一组32位数的16进制数字所构成,是故UUID理论上的总数为16^32=2^128,约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。 优点:简单易用、高效; 缺点:32位的长度太长;使用16进制表示,可读性差;无序,不利于排序。

2.Twitter-Snowflake Snowflake是Twitter公司设计的一套全局唯一ID生成算法。根据算法设计生成的ID共64位,第一位始终为0暂未使用,接着的41位为时间序列(精确到毫秒,41位的长度可以使用69年),紧接着的10位为机器标识(10位的长度最多支持部署1024个节点),最后的12位为计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)。 优点:有序、高效; 缺点:自主开发。

3.MySQL 既然传统使用方式下的数据库自增特性不能满足需求,不如设计单独的库表,单独提供产生全局ID的服务,利用auto_increment特性和replace into语法,例如创建如下表:

代码语言:javascript
复制
CREATE TABLE DISTRIBUTE_ID
(
 ID BIGINT(25) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '全局ID',
 PURPOSES VARCHAR(30) NOT NULL DEFAULT '' COMMENT '用途',
 PRIMARY KEY (ID),
 UNIQUE KEY UK_PURPOSES (PURPOSES)
) ENGINE=InnoDB;

当需要产生全局ID时,执行如下SQL:

代码语言:javascript
复制
REPLACE INTO DISTRIBUTE_ID (PURPOSES) VALUES ('PAYMENT');
SELECT LAST_INSERT_ID();

当然这些都是数据库层面的操作,需要将其封装成服务接口,调用接口即可获取ID。如果需要防止单点故障问题,可以部署两个数据库服务,同时给两个数据库的两个表设置不同的初始值和自增步长。 优点:数据库自增机制,可靠、有序; 缺点:如果多服务器只提供获取ID服务,会产生资源浪费;每次都从数据库获取,不高效。

4.MySQL+缓存 使用MySQL实现的方式有两个缺点,一个是产生资源浪费,一个是不高效。其实,按实际来说,能用前来解决的问题就不算问题,所以第一个不需要太关心,那就剩下效率的问题。既然不高效的原因是每次都操作数据库,那么就减少操作数据库,每次取批量的数据,并结合缓存使用。可以创建如下表:

代码语言:javascript
复制
CREATE TABLE DISTRIBUTE_ID
(
 PURPOSES VARCHAR(30) NOT NULL DEFAULT '' COMMENT '用途',
 INCREMENT INT NOT NULL DEFAULT 1 COMMENT ‘增长步长',
 MIN_VALUE BIGINT NOT NULL DEFAULT 1 COMMENT '最小值',
 MAX_VALUE BIGINT NOT NULL COMMENT '最大值',
 UNIQUE KEY UK_PURPOSES (PURPOSES)
) ENGINE=InnoDB;

在使用之前初始化数据,设置增长步长、最小值和最大值,编写类用于封装这些数据,以PURPOSES值为key,类实例为value,将key-value存放到缓存中,可以使用堆缓存,也可以使用分布式缓存如Redis,下面以堆缓存为例。

代码语言:javascript
复制
publi c class DistributeId implements Serializable {
   private String purposes;
   private long minValue;
   private long maxValue;
   private AtomicLong currentValue;

   public void setMinValue(long minValue) {
       this.minValue = minValue;
       this.currentValue = new AtomicLong(minValue);
   }

   //省略其它setter

   //获取下一个ID
   public long getAndIncrement() {
       long nextValue = currentValue.getAndIncrement();
       if (nextValue > maxValue) {
           return 0;
       }

       return nextValue;
   }
}


public class DistributeIdUtil {
   private Map<String, DistributeId> distributeIds = new HashMap<>();
   private Lock lock = new ReentrantLock();

   public long generateId(String purposes) {
       DistributeId distributeId = distributeIds.get(purposes);
       if(null == distributeId){
           queryDB(purposes);
           return generateId(purposes);
       }

       long id = distributeId.getAndIncrement();
       //超过最大值,重新从数据库获取
       if(id == 0){
           distributeIds.remove(purposes);
           queryDB(purposes);
           return generateId(purposes);
       }

       return id;

   }    


   private void queryDB(String purposes){
       lock.lock();
       try{
           DistributeId distributeId = distributeIds.get(purposes);
           if(null != distributeId){
               return;
           }

           // SET MIN_VALUE = MAX_VALUE+1,MAX_VALUE = MAX_VALUE+INCREMENT
           // 此处省略数据库操作,可以使用存储过程完成
           distributeId = ...;
           distributeIds.put(purposes,distributeId);
       }inally {
           lock.unlock();
       }
   }
}

如果需要防止单点故障问题,部署两个需要注意设置不同步长,同时代码中的自增操作需要换成getAndAdd。

END

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么使用分布式ID
  • 可用方案
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档