重构 - 分布式ID设计方案

分布式ID的生成方案有很多种,在网上都可以搜到。在这里详细介绍一下我们目前项目所用到的实际方案。

一. ID值获取及表结构设计

(1)获取ID值的接口

获取唯一ID的接口方法是由我们一个基础功能微服务提供,URL如下:

<IP>:<Port>/sequence/getSequence 

请求参数为:String prefix(ID值的类型,一种类型对应ID值的一个前缀,比如TEST_NO对应ID前缀NO,只是起一个对应关系)

eg:入参prefix=TEST_NO,返回新ID: NO2018073000000054

       入参prefix=REQ_MY,返回新ID:MY2018073000000054

(2)ID值组成

比如:NO2018073000000054

最前面的NO为设置的ID前缀,紧接着是生成的年月日(20180730, 共8位),最后几位是递增的数值(总长度为下表定义的seq_len值)。具体看下面二中的test_currVal存储过程的实现。

(3)表结构分析

涉及的表就一张,结构如下:

CREATE TABLE `id_sequence` (
  `name` varchar(50) COLLATE utf8mb4_bin NOT NULL,
  `current_value` int(11) NOT NULL,
  `increment` int(11) NOT NULL DEFAULT '5',
  `cur_date` date DEFAULT NULL,
  `default_value` int(11) DEFAULT NULL,
  `seq_len` int(8) DEFAULT NULL,
  `prefix` varchar(10) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

其中,

name:分配器名字,表示有多个用于分配ID值的分配器;

current_value:当前ID值取的值;

increment:固定步长,这里是5;

cur_date:当前日期,用于ID值的中间几位;

default_value:刚插入数据时的默认初始值;

seq_len:ID值最后递增位数的长度;

prefix:ID值的前缀;

(4)往该表预插入数据

INSERT INTO `id_sequence` (`name`, `current_value`, `increment`, `cur_date`, `default_value`, `seq_len`, `prefix`) VALUES ('TEST_NO_0', 1, 5, '2018-07-24', 1, 8, 'NO');
INSERT INTO `id_sequence` (`name`, `current_value`, `increment`, `cur_date`, `default_value`, `seq_len`, `prefix`) VALUES ('TEST_NO_1', 2, 5, '2018-07-24', 2, 8, 'NO');
INSERT INTO `id_sequence` (`name`, `current_value`, `increment`, `cur_date`, `default_value`, `seq_len`, `prefix`) VALUES ('TEST_NO_2', 3, 5, '2018-07-24', 3, 8, 'NO');
INSERT INTO `id_sequence` (`name`, `current_value`, `increment`, `cur_date`, `default_value`, `seq_len`, `prefix`) VALUES ('TEST_NO_3', 4, 5, '2018-07-24', 4, 8, 'NO');
INSERT INTO `id_sequence` (`name`, `current_value`, `increment`, `cur_date`, `default_value`, `seq_len`, `prefix`) VALUES ('TEST_NO_4', 5, 5, '2018-07-24', 5, 8, 'NO');

表数据:

name current_value increment cur_date default_value seq_len prefix
TEST_NO_0 1 5 2018-07-24 1 8 NO
TEST_NO_1 2 5 2018-07-24 2 8 NO
TEST_NO_2 3 5 2018-07-24 3 8 NO
TEST_NO_3 4 5 2018-07-24 4 8 NO
TEST_NO_4 5 5 2018-07-24 5 8 NO

可以看出,这里采用的方案是根据不同节点固定步长的方式。

(5)现通过(1)的接口新增一个ID:NO2018072400000008此时表数据变成:

name current_value increment cur_date default_value seq_len prefix
TEST_NO_0 1 5 2018-07-24 1 8 NO
TEST_NO_1 2 5 2018-07-24 2 8 NO
TEST_NO_2 8 5 2018-07-24 3 8 NO
TEST_NO_3 4 5 2018-07-24 4 8 NO
TEST_NO_4 5 5 2018-07-24 5 8 NO

可以看到,只有TEST_NO_2的current_value由3变成了8(因为步长increment为5),而取TEST_NO_2这条数是随机的。

二. 接口实现

获取ID接口的实现,其实就是调用test_nextVal存储过程来获取一个新ID。

首先看:

1. test_nextVal

BEGIN
DECLARE seqName VARCHAR(64);
set @seqName=CONCAT(seq_name,"_",ROUND(RAND()*4));
UPDATE id_sequence SET current_value = if (cur_date=current_date,current_value + increment,defult_value),cur_date=current_date WHERE name = @seqName; 

RETURN test_currVal(@seqName);  
END

可以看出,

(1)test_nextVal的入参是seq_name,这里传的是“TEST_NO”。而seqName=CONCAT(seq_name,"_",ROUND(RAND()*4))的作用是根据seq_name和随机生成(0-4)的数拼接成name值,这个作为后面更新的name条件值,此时就确定了要获取哪条数据(TEST_NO_0到TEST_NO_4中一条)的值。

(2)而UPDATE语句则是判断cur_date是不是当前日期(current_date是mysql函数),如果是,则current_value取当前值加上步长(increment=5,这样保证当天取的ID值都是从当前值进行递增),如果不是,则current_value取默认值(default_value,也即初始值,这样保证新一天取的ID值都是从初始值开始递增)。

(3)将seqName作为参数传入test_currVal存储过程里。

2. test_currVal

BEGIN
  DECLARE retVal VARCHAR(64);
  SET retval="-999999999";
  SELECT  CONCAT(prefix,DATE_FORMAT(CURRENT_DATE,'%Y%m%d'),LPAD(CAST(current_value AS CHAR),seq_len,0)) INTO retVal  FROM id_sequence  WHERE name = seq_name;  
  RETURN retVal; 
END

在这里,

(1)定义一个VARCHAR(64)的retVal值,默认值是-999999999,只有出错情况下才返回该值。

(2)CONCAT处就定义了ID值是怎么组成的:

前缀prefix(这里是NO) +  年月日DATE_FORMAT(CURRENT_DATE,'%Y%m%d')) +  seq_len长度的递增数字LPAD是mysql填充字符串函数,总长为seq_len=8,不够长度8时,左边以0补充;而CAST是mysql转换函数,将current_value转为CHAR类型)

猜你喜欢

转载自blog.csdn.net/u011692924/article/details/81184700