高并发网站如何解决数据库主键自增的时候出现重复?

论坛 期权论坛 期权     
匿名的论坛用户   2021-1-8 17:06   5502   10
这种问题应该在各种流量极大的微博,论坛,贴吧等地方都会碰到的吧!
分享到 :
0 人收藏

10 个回复

正序浏览
11#
热心的小回应  16级独孤 | 2021-1-8 17:06:40
说到GUID占用空间比较大,我也觉得的确应该找个方法弄一下,于是用Go语言写了个函数放到Github上了,etworker/idxgen · GitHub

目的就是将每个字符的可选范围从GUID的[0-9a-z]只有16个,变成[0-9a-zA-Z_-]共64个。

权衡之后,选择了8位长度,虽然空间比GUID的小很多,但是实测效果还行,测试时候跑了几个1千万的生成,在我的破笔记本上耗时约30s,偶尔会有1次重复。如果担心唯一性的,就自己实现里面的IsUniqueIdx()函数就可以了。

对了,我把这个东西塞进Docker,部署到云雀里面了,地址是http://idxgen-etworker.alaudacn.me:49662/,刷新一次就出来一个新的ID,不妨试试。
10#
热心的小回应  16级独孤 | 2021-1-8 17:06:39
我们基本都用guid,guid算法的实现各个版本略有不同,不过应该不用担心撞车的问题。guid就是比较占地方。
9#
热心的小回应  16级独孤 | 2021-1-8 17:06:38
主要思路就是“怎么在短时间内生成多个ID不重复”。
利用数据库写入时的自增能实现不重复,但是由于并发量过大会造成延时;
将自增的量直接写在内存或缓存中能加快生成速度,但是可能会掉电丢失,因此可以定时写入数据库中或者文件中(写入时还要对比递增中的和库中的,取大者);
为了保险起见,防止上述情况失效,再在ID中加一个时间,现在一般也就精确到微秒吧,也就是时间(微秒级)+自增因子
如果为了应付更大访问,或者容灾,或者分布式,再加一个机器标志位,也就是机器标志+时间(微秒级)+自增因子
我们现在用这个方案来生成1000个/s左右并发量的ID,算是一个比较稳定安全的方案。
当然,各个位置的长度要考虑数据的量,保存的时间等。如果像淘宝这种,肯定是用户ID+购物记录ID+时间等各个组合成一个购买记录的ID的。毕竟数据量太大了
8#
热心的小回应  16级独孤 | 2021-1-8 17:06:37
如果你只有一个主数据库,那么自增id是不会重复的,但这个地方会成为系统的瓶颈,而且也容易成为一个单点故障。
如果你是主/从数据库,这可以解决单点的问题,但不会解决生成id瓶颈的问题。而且在主数据库挂掉,进行主从切换的时候,这个自增id是可能出问题的。
比较能接受的集群方案是TiDB这种一批一批生成id的做法,这样id可以保证不重复,不过就不能保证依次连续(sequential)了。如果不保证连续的话,我们其实没必要把这个工作非要交给数据库了。
pingcap/docs所以一般载流量极大的互联网应用中,我们就不推荐使用数据库的自增长id了。
很多替代方案,比如说uuid,但有人可能嫌弃uuid太长,或者需要一个long型数字id,可以看下面这个snowflake id算法,估计要翻墙。这个项目已经不再开源了,但有很多各个语言的仿制品。
https://developer.twitter.com/en/docs/basics/twitter-ids.html这里也有一个我以前用go做的snowflake仿制品。像评论区一位朋友说的那样,众多snowflake id实现的问题之一就是ID生成依赖系统时钟,很怕时钟回调。
北南:用Go语言实现的Snowflake ID
7#
热心的小回应  16级独孤 | 2021-1-8 17:06:36
如果自增主键都能出现重复,只能说你的代码有个天大的bug。
6#
热心的小回应  16级独孤 | 2021-1-8 17:06:35
今天刚刚解决的问题
一般是用uuid,而不是自增,自增容易卷入全局锁,而且自增比较容易被破解
但是谈老师指出,uuid用的是secure random
有static blocking code
tim fox也在一次vert.x 2的用户的咨询中谈起
https://groups.google.com/d/topic/vertx/WE8pLRzB2ag如果entropy不够的话
这里有优化的空间,所以采用fast uuid策略
用法也简单,kotlin code
  1. val generator = UUIDGenerator(SecureRandom())if(Random.nextInt(100)==0) generator.reseed()val value = generator.generate().toString()
复制代码
按照原作者的说法,fast uuid是无脑uuid的40倍+
codahale/fast-uuid我们用它来实现session id
5#
热心的小回应  16级独孤 | 2021-1-8 17:06:34
很多方法,举几个栗子:

1、用UUID而非自增字段。这个主要的阻碍在于编程麻烦一点,很多框架不支持,程序员的智力理解不了的也遇到过。另外UUID不可迷信,其实也有出现过UUID撞车的。
2、全局用一个唯一id生成服务,Google公开的架构文档,有介绍他们用一个单独的oracle节点生成全局唯一自增id。买个 Oracle 就为了一个 sequnece ,这脑洞也确实有点大。有钱任性。
3、用一些协商算法,保证每个节点的自增id跟别的节点不冲突。缺点是很难找到一个扩展性良好的算法。
4#
热心的小回应  16级独孤 | 2021-1-8 17:06:33
我比较纳闷,数据库自带的自增主键为啥会出现重复???
拿mysql来说,即使存在master-slave复制,innodb_autoinc_lock_mode=2&&binlog_format=row,保证并发性能的同时,也不会使得从库产生主键冲突吧
3#
热心的小回应  16级独孤 | 2021-1-8 17:06:32
这种问题几年前我就搞定了,哪怕是分布式数据库,用分布式B-Tree,不断append到最后一个page,得到的id自然是全局唯一且单调递增的,当page足够大时,为了不一直产生热点,快速对page进行切分然后把右边子page迅速移到其他节点。
我司的Lealone数据库就是按这个基本原理去做的,根本不用应用层操心,只要像mysql一样定义一个自增型的字段就够了。
2#
热心的小回应  16级独孤 | 2021-1-8 17:06:31
全局id生成器。
我们日订单也有一万,说多不多说少不少,当然比起贴吧微博不在一个量级。
改天介绍一下

--------------------------2015/5/27
1 设置主键自增为何不可取
这样的话,数据库本身是单点,不可拆库,因为id会重复。

2 依赖数据库自增机制达到全局ID唯一
使用如下语句:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样可以保证全局ID唯一,但这个Tickets64表依旧是个单点。

3 依赖数据库自增机制达到全局ID唯一并消除单点
在2的基础上,部署两个(多个)数据库实例,
设置自增步长为2(多个则为实例数),即auto-increment-increment = 2
设置auto-increment-offset分别为1,2.....
这样第一台数据库服务器的自增id为 1 3 5 7 9
第二台为2 4 6 8 10

4 解决每次请求全局ID都读库写库压力过大的问题
比如第一次启动业务服务,会请求一个唯一id为3559
如果是2、3的方法,则id为3559,这样每次都请求数据库,对数据库压力比较大
可以用3559 * 65536(举个例子,并不一定是65536)+ 内存自增变量来作为id
当内存自增变量到达65535时,从数据库重新获取一个自增id
这样即使有多台业务服务器,id也不会重复:
第一台 3559 * 65536 + 1,2,3.....65535
第二台 3560 * 65536 + 1,2,3.....65535
然后第一台到65535了,换一个数据库自增id,这时候可能是3561 * 65536 + 1,2,3....

我们目前采用4
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3118703
帖子:623848
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP