-
2021-01-27 22:08:30
在数据库分表或者程序自己需要唯一id的情况下,我们需要一个生成唯一id的方案。
可以编写一个综合时间和某些特征生成唯一id的程序,也可以考虑使用数据库里自增id的特性来实现这个需求,下面举个mysql的例子。
首先创建一个专门生成id的表,其中id字段是主键,replace_key字段为唯一键。CREATE TABLE `ticket` ( `id` bigint(20) unsigned NOT NULL auto_increment, `replace_key` char(1) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY `replace_key` (`replace_key`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=10001;
每次需要生成id时,利用replace into语句生成新的记录将旧的记录替换掉,然后返回此id即可。REPLACE INTO `ticket` (`replace_key`) VALUES ('a');SELECT LAST_INSERT_ID();
推荐mysql视频教程,地址:https://www.php.cn/course/list/51.html
更多相关内容 -
全局唯一序号生成方案
2020-11-11 22:15:04全局唯一序列号设计方案 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。 ...全局唯一序列号设计方案
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。
一、全局唯一ID具备下面几个特性
1、全局唯一性:不能出现重复的ID
2、趋势递增:按照一定规则有序递增
3、单调递增:保证下一个ID一定大于上一个ID
4、信息安全:特定场景下连续递增ID的安全性单调递增与信息安全两个特性是互斥的,无法同时满足
分布式系统架构中,除了需要满足ID生成自身的需求外,还需要高可用性,高性能。二、方案一:数据库自增长序列或字段
比如oracle的sequence,mysql的auto_increment
【1】、优点
实现简单
能够保证唯一性
能够保证递增性【2】、缺点
扩展性差,性能有上限
可用性难以保证,有单点故障的风险
数据合并,数据迁移比较麻烦三、方案二:UUID
常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。
【1】、优点
实现简单
性能高,本地生成,不会有网络开销
数据合并,数据迁移比较简单【2】、缺点
没有排序,无法保证趋势递增
可读性差,不直观
UUID过长,往往用32或48位字符串表示,作为主键建立索引查询效率低四、方案三:Redis生成ID
当使用数据库来生成ID性能达不到要求的时候,可以采用Redis来生成ID。利用Redis的原子操作 INCR和INCRBY来实现。使用Redis集群可以获取更高的吞吐量,还可以防止单点故障问题。假如一个集群中有3台Redis。可以初始化每台Redis的值分别是1,2,3,然后步长都是3(主Redis机器数)。每个Redis生成的ID为:
A:1,4,7,10,13
B:2,5,8,11,14
C:3,6,9,12,15【1】、优点
不依赖于数据库,性能优于数据库
数字ID排序,对分页或排序很有帮助【2】、缺点
1、如果系统中没有Redis,还需要引入新的组件,增加系统复杂度
2、需要编码和配置的工作量比较大五、方案四:数据库segment
通过一个序列表记录当前序列号,机器每次从序列表中获取一定步长的序列数然后缓存再本地,等用完后再重新从步长表获取,本地可以用的序号数可以再做放大,如放大1000倍 X 1000;那么实际上本地可用的序列个数=步长大小 X 放大倍数。读写数据库的频率从1减小到了1/(step * 放大倍数)
【1】、优点:
大大降低了数据库的访问频率
【2】、缺点:
性能较差,例如几百个应用同时来获取这条序列记录的话行锁等待时间长
TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
DB宕机会造成整个系统不可用。
【3】、优化方案:双buffer
取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。
为此,我们希望DB取号段的过程能够做到无阻塞,不需要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。
六、方案五:数据库自增序列改进方案
跟通过数据库步长的方式类似,生成一张序列表,表结构如下所示,当应用需要获取序号的时候向该表中插入一条数据,记录访问应用的ip信息,然后得到一个唯一的自增的id,这个【(id-1)X 应用设置的步长】 就是应用起始的序列号,【id X 应用设置的步长】就是应用最大的序列号;例如每台机器可得步长为1000,那么插入完记录后该应用得到的序列范围就是0-1000;当这1000个序号用完后就在访问步长表再插入一条记录,得到新的id重新计算新的序列号,同时删除该机器ip id比当前id小的记录。
CREATE TABLE `coupon_no_sequence` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', `HOST_NAME` varchar(64) NOT NULL COMMENT 'host name', `PORT` varchar(64) NOT NULL COMMENT 'port', `TYPE` int(2) NOT NULL COMMENT 'node type: ACTUAL or CONTAINER', PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1659 DEFAULT CHARSET=utf8 COMMENT='券号序列表';
实例:券号生成策略
实现代码
【1】、spring配置
<bean id="couponService" class="com.suning.ebuy.cous.components.CouponSequenceService" lazy-init="false"> <!-- ID号分段的最大值,如果不配置,代码中默认是999999999999--> <property name="segmentMaxIdValue" value="999999999999"/> <!-- 每个ID号分段的ID数(必须是10的整数次幂,如果不配置,代码中默认是1000) --> <property name="perSegmentQty" value="1000"/> <!--号段表名--> <property name="sequenceTableName" value="coupon_no_sequence"/> <property name="idSegmentDao" ref="IdSegmentDao"/> </bean>
【2】、获取券号的生成方法
券号获取服务,前面补0补齐13位并在前面拼接上券类型的标记
public class CouponSequenceService { /** * 当前序号 */ private long currentId = 0L; /** * 当前分段最大的序号 */ private long currentMaxId = 0L; /** * 最大段号 */ private long segmentMaxIdValue = 999999999999L; /** * 每段段长 */ private int perSegmentQty = 1000; /** * 序列表表名 */ private String sequenceTableName = "id_segement"; private IdSegmentDao idSegmentDao; /** * * 功能描述: 获取券号 * * @param * @return * @see [相关类/方法](可选) * @since [产品/模块版本](可选) */ public synchronized String getNextLongSequence() { // 如果本地没有序号剩余,获取新的分段好,并重置起始序号 if (this.currentId == this.currentMaxId) { this.getNextSegmentAndReset(); } // 本地序号有剩余,直接+1 ++this.currentId; return Long.toString(this.currentId); } /** * * 功能描述: 获取新的分段号 * * @param * @return * @see [相关类/方法](可选) * @since [产品/模块版本](可选) */ private void getNextSegmentAndReset() { try { long segment = assignIdSegment(sequenceTableName); if (segment > this.segmentMaxIdValue) { throw new UidGenerateException("ID超过了最大范围!"); } // mysql id 是从0开始,因此当前起始序号 = (id -1) * 段长 this.currentId = (segment - 1L) * (long) this.perSegmentQty; // 最大的序列号 = id * 端长 this.currentMaxId = segment * (long) this.perSegmentQty; } catch (Exception e) { throw new UidGenerateException("获取ID号分段异常", e); } } /** * * 功能描述: 从数据库获取段号,段号即id * * @param tableName * @return * @see [相关类/方法](可选) * @since [产品/模块版本](可选) */ @Transactional public long assignIdSegment(String tableName) { IdSegementEntity idSegementEntity = new IdSegementEntity(); idSegementEntity.setHostName(NetUtils.getLocalAddress()); idSegementEntity.setTableName(tableName); // 插入一条记录 idSegmentDao.addIdSegment(idSegementEntity); // 拿到id = 段号 Long idSegment = this.idSegmentDao.queryIdSegmentByIP(idSegementEntity); idSegementEntity.setId(idSegment); // 删除该ip id < idSegment 的记录 idSegmentDao.deleteIdSegment(idSegementEntity); return idSegment; } public long getSegmentMaxIdValue() { return segmentMaxIdValue; } public void setSegmentMaxIdValue(long segmentMaxIdValue) { this.segmentMaxIdValue = segmentMaxIdValue; } public int getPerSegmentQty() { return perSegmentQty; } public void setPerSegmentQty(int perSegmentQty) { this.perSegmentQty = perSegmentQty; } public String getSequenceTableName() { return sequenceTableName; } public void setSequenceTableName(String sequenceTableName) { this.sequenceTableName = sequenceTableName; } public IdSegmentDao getIdSegmentDao() { return idSegmentDao; } public void setIdSegmentDao(IdSegmentDao idSegmentDao) { this.idSegmentDao = idSegmentDao; } }
【3】、SQL
<!--删除过期的ID号段--> <sql id="delete_id_segment" jdbcTimout="3"> <![CDATA[ DELETE FROM ${TABLENAME} WHERE HOST_NAME = :HOSTNAME AND ID < :ID ]]> </sql> <!--查询正在使用的ID号段--> <sql id="query_id_segment_by_ip" jdbcTimout="3"> <![CDATA[ SELECT t.ID FROM ${TABLENAME} t WHERE t.HOST_NAME = :HOSTNAME ORDER BY t.ID DESC LIMIT 1 ]]> </sql> <!--新增ID号段--> <sql id="insert_id_segment" jdbcTimout="3"> <![CDATA[ INSERT INTO ${TABLENAME} (HOST_NAME,PORT,TYPE) VALUES (:HOSTNAME,:PORT,:TYPE) ]]> </sql>
七、方案六:雪花算法
【1】雪花算法原理
- 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
- 41bit-时间戳,用来记录时间戳,毫秒级。
41位可以表示2199023255552个数字,
如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2199023255552,减1是因为可表示的数值范围是从0开始算的,而不是1。
也就是说41位可以表示2199023255552个毫秒的值,转化成单位年则是69年 - 10bit-工作机器id,用来记录工作机器id。
可以部署在1024个节点,包括5位datacenterId和5位workerId
5位(bit)可以表示的最大正整数是31,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId - 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
12位(bit)可以表示的最大正整数是4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
【2】Snowflake 存在的问题
snowflake 不依赖数据库,也不依赖内存存储,随时可生成 ID,这也是它如此受欢迎的原因。但因为它在设计时通过时间戳来避免对内存和数据库的依赖,所以它依赖于服务器的时间。上面我们提到了 Snowflake 的 4 段结构,实际上影响 ID 大小的是较高位的值,由于最高位固定为 0,遂影响 ID 大小的是中位的值,也就是时间戳。
试想,服务器的时间发生了错乱或者回拨,这就直接影响到生成的 ID,有很大概率生成重复的 ID且一定会打破递增属性。这是一个致命缺点,你想想,支付订单和购买订单的编号重复,这是多么严重的问题!
另外,由于它的中下位和末位 bit 数限制,它每毫秒生成 ID 的上限严重受到限制。由于中位是 41 bit 的毫秒级时间戳,所以从当前起始到 41 bit 耗尽,也只能坚持 70 年。
再有,程序获取操作系统时间会耗费较多时间,相比于随机数和常数来说,性能相差太远,这是制约它生成性能的最大因素。
-
分布式数据库唯一序号解决方案
2017-03-16 00:04:38随着互联网飞速发展,尤其像微博,移动支付行业,用户数据成几何倍数增长,传统... 在享受分库分表带来查询速度提升便利的同时,这种切分方案也带来一些问题,例如数据路由,唯一序号等,今天主要说一下唯一序号解决方随着互联网飞速发展,尤其像微博,移动支付行业,用户数据成几何倍数增长,传统的单库已无法满足业务的增长速度,于是就有了分库分表,对业务表进行垂直切分和水平切分,将数据存放在多个数据库中,这每个数据库中存放的业务数据就会相应减少,利于应用访问数据库速度。
在享受分库分表带来查询速度提升便利的同时,这种切分方案也带来一些问题,例如数据路由,唯一序号等,今天主要说一下唯一序号解决方案
第一种解决方案:使用UUID随机数,这种方案优点是使用简单,缺点是产生的随机数是128位,并且没有业务规则,不连续,占用数据库空间多,在业务数据量大时,会有性能下降
第二种解决方案:使用Twitter的雪花算法(snowflake),此算法产生序号效率极高,每秒能产生26W个ID,但是也存在缺点,非常依赖NTP服务,如果NTP失效,服务器的时间滞后,则此雪花算法就会等待服务器时间追上,才会生成新的ID
今天就说一下新的解决方案,其架构图如下所示
此方案的好处是,在同城双机房的情况下,可以部署2台mysql数据库,做双主半同步,这样可以保证数据的一致性,如果2台mysql服务器都运行正常,则同机房的应用则访问同机房的mysql数据库,可以事先定制好规则,机房A的应用访问机房A的mysql数据库表seq_a,并只获取奇数的序号,机房B的应用访问机房B的mysql数据库表seq_b,并只获取偶数的序号,2个机房业务均衡,基本无序号空洞产生。
如果机房A的mysql数据库宕机,则分布式ID生成应用会自动切换数据源,去访问机房B的mysql数据库,并正常读写seq_a表,并只获取奇数的序号,以此来实现架构的高可用。
下面是我的公众号二维码,欢迎添加
-
自增序号、唯一ID、唯一编码生成器
2021-04-21 16:56:12自增序号、唯一ID、唯一编码生成器 1、序号发生器生成唯一编码 import com.github.pagehelper.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data....自增序号、唯一ID、唯一编码生成器
1、序号发生器生成唯一编码
import com.github.pagehelper.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.List; @Component public class SeqGeneratorUtil { @Autowired private StringRedisTemplate redisTemplate; /** * 自增序列号 * prefix 前缀 * numLength 要生成多少位的数字 */ public String SeqGenerator(String prefix, int numLength) { String upperCode = ""; Long size = redisTemplate.opsForList().size(prefix);//查找以prefix作为key值的数据长度 if (size > 0) {//有数据 List leve = redisTemplate.opsForList().range(prefix, 0, -1);//获取该key下面的所有值 (-1所有值;1下一个值) upperCode = Collections.max(leve).toString();//返回最大值 } String returnCode = ""; int Suffix;//后缀数字 if (!StringUtil.isEmpty(upperCode)) {//有数据 String sequence = upperCode.substring(prefix.length());//截取前缀开始的后面数字 Suffix = Integer.parseInt(sequence); Suffix++;//最后的序号加一 } else {//没有数据 Suffix = 1; } returnCode = prefix + String.format("%0" + numLength + "d", Suffix);//后缀不够numLength长,前面补充0 redisTemplate.opsForList().rightPush(prefix, returnCode);//存入Redis return returnCode; } }
2、利用redis的incr生成唯一编码
-
java uuid 随机生成唯一序列号 | 学步园
2021-02-26 09:19:05由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在 生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得... -
在Oracle中生成唯一和连续数字的最佳方法
2021-04-30 08:17:49保留当前序列-可以使用以下命令将值重置为当前存储在表中的最大值:-- ---------------------------------- Purpose..: Resets the sequences-- --------------------------------DECLARE-- record of temp data ... -
el-table树形表格表单验证(列表生成序号)
2020-11-20 12:20:31首先需要生成一个序号用来确定表单验证的目标row,通过广度优先遍历,以1,1.1,1.1.1的规则对树形列表生成确定唯一值的索引,因为列表自身可以做CURD,因此需要每次列表的item增加或减少时重新调用生成索引的方法。... -
U盘唯一物理编号查看器
2010-09-27 12:03:25U盘物理编号查看器 查看物理编号 u盘的身份 -
用sql生成唯一的特殊序列号
2019-06-17 08:51:38 -
SqlServer数据库之给表添加序号
2019-09-24 23:18:16简单给查询表添加序号 SELECT row_number() over(order by inserttime asc) as id,* FROM [RecordDrawScore] 转载于:https://www.cnblogs.com/nnnnnn/p/10948496.html -
PHP获取唯一标识UUID
2020-08-17 14:26:51封装函数如下: function uuid($prefix = '') { $chars = md5(uniqid(mt_rand(), true)); $uuid = substr($chars, 0, 8) .... $uuid .= substr($chars, 8, 4) .... $uuid .= substr($chars, 12, 4) .... -
c# 自动序号生成
2018-11-09 11:14:17针对硬件绑定 软件序列号生成 , 序列号检测, 相当的好用,都加了注释 -
全局唯一序列号的生成
2020-03-21 18:28:22概要:借助数据库自增主键实现全局唯一序列号的生成;将自增主键放大后,形成区间号段,在内存中分配,从而避免频繁的IO,当达到号段最大值时,重新从数据库获取号段。 一、搭建测试 application.yml配置 server: ... -
SQL server 自增ID–序号自动增加的字段操作
2020-12-14 07:49:211、SQL server中创建表,带有自增列。 create table Test_Table(Id int Identity(1,1), Name varchar(20)); 这里用到了IDENTITY 关键字。IDENTITY(a,b),a b均为正整数,a表示开始数,b表示步长。... -
Java按规则生成唯一编号
2021-01-23 11:08:14生成规则:两位年+两位月+两位日+8位随机数 public static void main(String[] args) { String s = new SimpleDateFormat("yyMMdd").format(new Date()); System.out.println(s); StringBuilder str=new ... -
Java并发编程-生成唯一序列号
2021-02-12 14:37:11所用到的并发编程库import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.ReentrantReadWriteLock;.../**** ID生成器接口, 用于生成全局唯一的ID流水号** @author Ivan.Ma*/public... -
怎么样才能获取唯一的CPU序列号
2018-11-27 07:32:52怎么才能获取唯一的CPU序列号,看到网上有说 Intel Pentium III 以后CPU唯一的序列号都获取不了? 经过验证,用 wmic CPU get ProcessorID 命令得到的CPU序列号不是唯一的,想问问有什么方法可以得到CPU唯一的... -
如何根据当前时间生成唯一编号
2021-02-26 11:34:03是当前时间的唯一的编号……System.currentTimeMillis() 只是获取当前的时间戳,单位是毫秒,但是这并不是唯一的.如果你在1毫秒中进行了两次操作,那么这两个ID就是相等的.问题的解决看你要求的精度如何了.一般可以使用... -
java数据库唯一id生成工具类
2020-08-26 02:21:51主要为大家详细介绍了java数据库唯一id生成工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
生成唯一编号的方法
2019-02-21 09:39:511、这种方法是通过时间来生成唯一的编号,在页面获取年月日时分秒毫秒不重复的编号,首先获取每个时间然后在把各个时间全部相加起来,得到编号,最后给需要生成编号的文本框赋值。如果只获取到秒都可能重复,所以... -
java生成8位UUID
2015-09-18 12:49:04java 生成8位UUID,解决UUID2太长的问题,欢迎下载。后续代码,陆续放出 -
获取SD卡的唯一标识
2017-10-13 17:24:48因为项目的需求,需要获取SD卡的唯一标识,找了多方资料,最后在google上面看到解决办法,在这里做笔记以防止忘记 我的demo代码 private String getSDCid(){ StringBuilder stringB = new StringBuilder(); ... -
Guid生成唯一ID编号
2017-10-13 16:27:56function GetGUID: string; var i: Integer; GUID: TGUID; begin i := 0; Result := ''; while i begin if CoCreateGuid(GUID) = S_OK then begin SetLength(Result, 32); -
java生成唯一序列号,UUID的应用
2017-03-25 10:47:49java中生成不重复的唯一序列号具有很大的应用场景,UUID提供了这样的一个方法。生成如下格式的序列号:46049c6d-282e-434b-9c16-025c91ddd23d只需要调用UUID类的静态方法,如下所示:UUID.randomUUID().toString();... -
Java通过UUID随机生成36位、32位唯一识别码(唯一字符串)
2019-03-06 21:53:24import java.util.UUID... * 通过UUID随机生成36位、32位唯一识别码(唯一字符串) * @author 小强 * */ public class Test { public static void main(String[] args) { int i = 0; while(i < 10)... -
c# 按日期+序号进行自动编号
2015-10-03 14:01:20在网上搜了半天,都没有看到满意的程序。只好参照别人写的例子,自己写了。编号是按照年份和月份+三位序号自动生成的,用的是SQL server数据库,用VS2010编写的。调试已通过。希望给同为新手的各位一个参考。 -
mysql 加序号
2021-01-18 22:30:00名称 该图元的标识,请确保在当前服务编排中唯一。 系统自动填充。 描述 该图元的描述信息。 选填项,建议填写图元的作用。 功能面栏目介绍 通过对数据管理页面介绍,让您更直观、快捷地了解数据管理服务。 图2 ... -
生成36位全局唯一数(GUID、UUID)
2016-09-21 20:28:311.Java import java.util.UUID; UUID.randomUUID(); 2.C# using System; Guid.NewGuid().ToString() 3.SQL Server SELECTcast(NEWID() AS VARCHAR(36)) AS UUID ...SELECT sys_gu -
Android获取设备的唯一识别码|设备号|序号|UUID
2021-12-24 16:11:22如何获取一个能唯一标识每台Android设备的序号? 这个问题有很多答案,但是他们中的大部分只在某些情况下有效。 根据测试: 所有的设备都可以返回一个TelephonyManager.getDeviceId() 所有的GSM设备 (测试设备都... -
javascript算法题 求任意一个1-9位不重复的N位数在该组合中的大小排列序号
2020-12-09 17:39:03输出:[123(1) , 132(2) , 213(3) , 231(4) , 312(5) , 321(6)]—>X=2 首先看到题目想到的是生成一个从少到大的全排列的数组,然后再遍历数组得到对应的序号(数组下标加1),又或者想到一个个从小到大的生成push进...