微信收藏里的两条微信信息不见了了,是关于我女友的前夫的信息,我女友说不是她删的!可是我用的苹果7,

高并发分布式系统中生成大局唯一Id汇总 - 软件架构设计当前位置:& &&&高并发分布式系统中生成大局唯一Id汇总高并发分布式系统中生成大局唯一Id汇总&&网友分享于:&&浏览:0次高并发分布式系统中生成全局唯一Id汇总数据在分片时,典型的是分库分表,就有一个全局ID生成的问题。单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求:& &1 不能有单点故障。& &2 以时间为序,或者ID里包含时间。这样一是可以少一个索引,二是冷热数据容易分离。& &3 可以控制ShardingId。比如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易。& &4 不要太长,最好64bit。使用long比较好操作,如果是96bit,那就要各种移位相当的不方便,还有可能有些组件不能支持这么大的ID。
一 twitter twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。1 41位的时间序列(精确到毫秒,41位的长度可以使用69年)2 10位的机器标识(10位的长度最多支持部署1024个节点) 3 12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号) 最高位是符号位,始终为0。优点:高性能,低延迟;独立的应用;按时间有序。 缺点:需要独立的开发和部署。
java 实现代码
public class IdWorker {
private final long workerId;
private final static long twepoch = 7L;
private long sequence = 0L;
private final static long workerIdBits = 4L;
public final static long maxWorkerId = -1L ^ -1L && workerIdB
private final static long sequenceBits = 10L;
private final static long workerIdShift = sequenceB
private final static long timestampLeftShift = sequenceBits + workerIdB
public final static long sequenceMask = -1L ^ -1L && sequenceB
private long lastTimestamp = -1L;
public IdWorker(final long workerId) {
if (workerId & this.maxWorkerId || workerId & 0) {
throw new IllegalArgumentException(String.format(
"worker Id can't be greater than %d or less than 0",
this.maxWorkerId));
this.workerId = workerId;
public synchronized long nextId() {
long timestamp = this.timeGen();
if (this.lastTimestamp == timestamp) {
this.sequence = (this.sequence + 1) & this.sequenceM
if (this.sequence == 0) {
System.out.println("###########" + sequenceMask);
timestamp = this.tilNextMillis(this.lastTimestamp);
this.sequence = 0;
if (timestamp & this.lastTimestamp) {
throw new Exception(
String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
this.lastTimestamp - timestamp));
} catch (Exception e) {
e.printStackTrace();
this.lastTimestamp =
long nextId = ((timestamp - twepoch && timestampLeftShift))
| (this.workerId && this.workerIdShift) | (this.sequence);
System.out.println("timestamp:" + timestamp + ",timestampLeftShift:"
+ timestampLeftShift + ",nextId:" + nextId + ",workerId:"
+ workerId + ",sequence:" + sequence);
return nextId;
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp &= lastTimestamp) {
timestamp = this.timeGen();
private long timeGen() {
return System.currentTimeMillis();
public static void main(String[] args){
IdWorker worker2 = new IdWorker(2);
System.out.println(worker2.nextId());
2 来自Flicker的解决方案因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。一个生成64位ID方案具体就是这样的: 先创建单独的数据库(eg:ticket),然后创建一个表:
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM
当我们插入记录后,执行SELECT * from Tickets64,查询结果就是这样的:
+-------------------+------+| id
| stub |+-------------------+------+| 90423 |
a |+-------------------+------+在我们的应用端需要做下面这两个操作,在一个事务会话里提交:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样我们就能拿到不断增长且不重复的ID了。 到上面为止,我们只是在单台数据库上生成ID,从高可用角度考虑,接下来就要解决单点故障问题:Flicker启用了两台数据库服务器来生成ID,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1
TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2
最后,在客户端只需要通过轮询方式取ID就可以了。
优点:充分借助数据库的自增ID机制,提供高可靠性,生成的ID有序。缺点:占用两个独立的MySQL实例,有些浪费资源,成本较高。
UUID生成的是length=32的16进制格式的字符串,如果回退为byte数组共16个byte元素,即UUID是一个128bit长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成UUID。从理论上讲,如果一台机器每秒产生个GUID,则可以保证(概率意义上)3240年不重复优点:(1)本地生成ID,不需要进行远程调用,时延低(2)扩展性好,基本可以认为没有性能上限缺点:(1)无法保证趋势递增(2)uuid过长,往往用字符串表示,作为主键建立索引查询效率低,常见优化方案为&转化为两个uint64整数存储&或者&折半存储&(折半后不能保证唯一性)四 基于redis的分布式ID生成器首先,要知道redis的EVAL,EVALSHA命令:原理
利用redis的lua脚本执行功能,在每个节点上通过lua脚本生成唯一ID。 生成的ID是64位的:
使用41 bit来存放时间,精确到毫秒,可以使用41年。使用12 bit来存放逻辑分片ID,最大分片ID是4095使用10 bit来存放自增长ID,意味着每个节点,每毫秒最多可以生成1024个ID比如GTM时间 Fri Mar 13 10:00:00 CST 2015 ,它的距1970年的毫秒数是 0,假定分片ID是53,自增长序列是4,则生成的ID是:
8054276 = 0 && 22 + 53 && 10 + 41redis提供了TIME命令,可以取得redis服务器上的秒数和微秒数。因些lua脚本返回的是一个四元组。
second, microSecond, partition, seq客户端要自己处理,生成最终ID。
((second * 1000 + microSecond / 1000) && (12 + 10)) + (shardId && 10) +五 MongoDB文档(Document)全局唯一ID
为了考虑分布式,&_id&要求不同的机器都能用全局唯一的同种方法方便的生成它。因此不能使用自增主键(需要多台服务器进行同步,既费时又费力),因此选用了生成ObjectId对象的方法。
ObjectId使用12字节的存储空间,其生成方式如下:
|0|1|2|3|4|5|6 |7|8|9|10|11|
|机器ID|PID|计数器
前四个字节时间戳是从标准纪元开始的时间戳,单位为秒,有如下特性:
&1 时间戳与后边5个字节一块,保证秒级别的唯一性;&2 保证插入顺序大致按时间排序;&3 隐含了文档创建时间;&4 时间戳的实际值并不重要,不需要对服务器之间的时间进行同步(因为加上机器ID和进程ID已保证此值唯一,唯一性是ObjectId的最终诉求)。
机器ID是服务器主机标识,通常是机器主机名的散列值。
同一台机器上可以运行多个mongod实例,因此也需要加入进程标识符PID。
前9个字节保证了同一秒钟不同机器不同进程产生的ObjectId的唯一性。后三个字节是一个自动增加的计数器(一个mongod进程需要一个全局的计数器),保证同一秒的ObjectId是唯一的。同一秒钟最多允许每个进程拥有(256^3 = )个不同的ObjectId。
总结一下:时间戳保证秒级唯一,机器ID保证设计时考虑分布式,避免时钟同步,PID保证同一台服务器运行多个mongod实例时的唯一性,最后的计数器保证同一秒内的唯一性(选用几个字节既要考虑存储的经济性,也要考虑并发性能的上限)。
"_id"既可以在服务器端生成也可以在客户端生成,在客户端生成可以降低服务器端的压力。
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 1234567891011 Copyright & &&版权所有 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
分布式数据库中全局唯一主键生成策略的设计与实现
下载积分:1000
内容提示:分布式数据库中全局唯一主键生成策略的设计与实现
文档格式:DOC|
浏览次数:317|
上传日期: 14:54:28|
文档星级:
全文阅读已结束,如果下载本文需要使用
 1000 积分
下载此文档
该用户还上传了这些文档
分布式数据库中全局唯一主键生成策略的设计与实现
官方公共微信2475人阅读
并发与多线程(2)
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上。最近还写了一个发邮件的组件以及性能测试请看&&,还弄了个MSSQL参数化语法生成器,会在9月整理出来,有兴趣的园友可以关注下我的博客。
分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案。我和我的小伙伴们也讨论了这个主题,我受益匪浅啊……
博文示例:
1.&&&&&&&&&
2.&&&&&&&&&
今天分享的主题是:如何在高并发分布式系统中生成全局唯一Id。
但这篇博文实际上是“半分享半讨论”的博文:
1)&&&&&&&&&半分享是我将说下我所了解到的关于今天主题所涉及的几种方案。
2)&&&&&&&&&半讨论是我希望大家对各个方案都说说自己的见解,更加希望大家能提出更好的方案。(我还另外提问在此:上面已有几位园友回复(感谢dudu站长的参与),若你们有见解和新方案就在本博文留言吧,方便我整理更新到博文中,谢谢!)
我了解的方案如下……………………………………………………………………
1、&&使用数据库自增Id
优势:编码简单,无需考虑记录唯一标识的问题。
1)&&&&&&&&&在大表做水平分表时,就不能使用自增Id,因为Insert的记录插入到哪个分表依分表规则判定决定,若是自增Id,各个分表中Id就会重复,在做查询、删除时就会有异常。
2)&&&&&&&&&在对表进行高并发单记录插入时需要加入事物机制,否则会出现Id重复的问题。
3)&&&&&&&&&在业务上操作父、子表(即关联表)插入时,需要在插入数据库之前获取max(id)用于标识父表和子表关系,若存在并发获取max(id)的情况,max(id)会同时被别的线程获取到。
4)&&&&&&&&&等等。
结论:适合小应用,无需分表,没有高并发性能要求。
2、&&单独开一个数据库,获取全局唯一的自增序列号或各表的MaxId
1)&&&&&&&&&使用自增序列号表
专门一个数据库,生成序列号。开启事物,每次操作插入时,先将数据插入到序列表并返回自增序列号用于做为唯一Id进行业务数据插入。
注意:需要定期清理序列表的数据以保证获取序列号的效率;插入序列表记录时要开启事物。
使用此方案的问题是:每次的查询序列号是一个性能损耗;如果这个序列号列暴了,那就杯具了,你不知道哪个表使用了哪个序列,所以就必须换另一种唯一Id方式如GUID。
2)&&&&&&&&&使用MaxId表存储各表的MaxId值
专门一个数据库,记录各个表的MaxId值,建一个存储过程来取Id,逻辑大致为:开启事物,对于在表中不存在记录,直接返回一个默认值为1的键值,同时插入该条记录到table_key表中。而对于已存在的记录,key值直接在原来的key基础上加1更新到MaxId表中并返回key。
使用此方案的问题是:每次的查询MaxId是一个性能损耗;不过不会像自增序列表那么容易列暴掉,因为是摆表进行划分的。
详细可参考:
&&&&&&&&&&&&&&&&&&&我截取此文中的sql语法如下:
第一步:创建表
create&table&table_key
&&&&&&&table_name&&
varchar(50)
not&null&primary&key,
&&&&&&&key_value&&&
int&&&&&&&&&not&null
第二步:创建存储过程来取自增ID
create&procedure&up_get_table_key
&&&@table_name&&&&
varchar(50),
&&&@key_value&&&&&
int&output
&&&&&begin&tran
&&&&&&&&&declare&@key&&int
&&&&&&&&&&
&&&&&&&&&--initialize the key with 1
&&&&&&&&&set&@key=1
&&&&&&&&&--whether the specified table is exist
&&&&&&&&&if
not&exists(select&table_name
from&table_key
where&table_name=@table_name)
&&&&&&&&&&&&begin
&&&&&&&&&&&&&&insert&into&table_key
values(@table_name,@key)&&&&&&&
--default key vlaue:1
&&&&&&&&&&&&end
&&&&&&&&&-- step increase
&&&&&&&&&else&&&
&&&&&&&&&&&&begin
&&&&&&&&&&&&&&&&select&@key=key_value
from&table_key
with&(nolock) where&table_name=@table_name
&&&&&&&&&&&&&&&&set&@key=@key+1
&&&&&&&&&&&&&&&&--update the key value by table name
&&&&&&&&&&&&&&&&update&table_key
set&key_value=@key&where&table_name=@table_name
&&&&&&&&&&&&end
&&&&&&&&--set ouput value
&&&&set&@key_value=@key
&&&&--commit tran
&&&&commit&tran
&&&&&&&&if @@error&0
&&&&&&rollback&tran
感谢园友的好建议:
1.&&&&&&&&&()建议给table_key中为每个表初始化一条key为1的记录,这样就不用每次if来判断了。
2.&&&&&&&&&()建议给存储过程中提高一下,因为出现在CS代码层上使用如下事物代码会导致并发重复问题.
TransactionOptions option =
new&TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadU
option.Timeout = new&TimeSpan(0, 10, 0);
using&(TransactionScope transaction =
new&TransactionScope(TransactionScopeOption.RequiresNew, option))
&&&&&&&&//调用存储过程
在咨询过DBA后,这个存储过程提高数据库隔离级别会加大数据库访问压力,导致响应超时问题。所以这个建议我们只能在代码编写宣导上做。
3.&&&&&&&&&()存储过程中不使用事物,一旦使用到事物性能就急剧下滑。直接使用UPDATE获取到的更新锁,即SQL
SERVER会保证UPDATE的顺序执行。(已在用户过千万的并发系统中使用)
create&procedure&[dbo].[up_get_table_key]
&&&@table_name&&&&
varchar(50),
&&&@key_value&&&&&
int&output
&&&&SET&NOCOUNT
&&&&DECLARE&@maxId
&&&&UPDATE&table_key
&&&&SET&@maxId = key_value,key_value = key_value + 1
&&&&WHERE&table_name=@table_name
&&&&SELECT&@maxId
结论:适用中型应用,此方案解决了分表,关联表插入记录的问题。但是无法满足高并发性能要求。同时也存在单点问题,如果这个数据库cash掉的话……
我们目前正头痛这个问题,因为我们的高并发常常出现数据库访问超时,瓶颈就在这个MaxId表。我们也有考虑使用分布式缓存(eg:memcached)缓存第一次访问MaxId表数据,以提高再次访问速度,并定时用缓存数据更新一次MaxId表,但我们担心的问题是:
a)&&&&&&&&&倘若缓存失效或暴掉了,那缓存的MaxId没有更新到数据库导致数据丢失,必须停掉站点来执行Select max(id)各个表来同步MaxId表。
b)&&&&&&&&&分布式缓存不是一保存下去,其他服务器上就立马可以获取到的,即数据存在不确定性。(其实也是缓存的一个误用,缓存应该用来存的是频繁访问并且很少改动的内容)
&&&&&&&&&改进方案:
整体思想:建立两台以上的数据库ID生成服务器,每个服务器都有一张记录各表当前ID的MaxId表,但是MaxId表中Id的增长步长是服务器的数量,起始值依次错开,这样相当于把ID的生成散列到每个服务器节点上。例如:如果我们设置两台数据库ID生成服务器,那么就让一台的MaxId表的Id起始值为1(或当前最大Id+1),每次增长步长为2,另一台的MaxId表的ID起始值为2(或当前最大Id+2),每次步长也为2。这样就将产生ID的压力均匀分散到两台服务器上,同时配合应用程序控制,当一个服务器失效后,系统能自动切换到另一个服务器上获取ID,从而解决的单点问题保证了系统的容错。(Flickr思想)
但是要注意:1、多服务器就必须面临负载均衡的问题;2、倘若添加新节点,需要对原有数据重新根据步长计算迁移数据。
结论:适合大型应用,生成Id较短,友好性比较好。(强烈推荐)
3、&&Sequence特性
这个特性在SQL Server 2012、Oracle中可用。这个特性是数据库级别的,允许在多个表之间共享序列号。它可以解决分表在同一个数据库的情况,但倘若分表放在不同数据库,那将共享不到此序列号。(eg:Sequence使用场景:你需要在多个表之间公用一个流水号。以往的做法是额外建立一个表,然后存储流水号)
相关Sequence特性资料:
结论:适用中型应用,此方案不能完全解决分表问题,而且无法满足高并发性能要求。同时也存在单点问题,如果这个数据库cash掉的话……
4、&&通过数据库集群编号+集群内的自增类型两个字段共同组成唯一主键
优点:实现简单,维护也比较简单。
缺点:关联表操作相对比较复杂,需要两个字段。并且业务逻辑必须是一开始就设计为处理复合主键的逻辑,倘若是到了后期,由单主键转为复合主键那改动成本就太大了。
结论:适合大型应用,但需要业务逻辑配合处理复合主键。
5、&&通过设置每个集群中自增&ID&起始点(auto_increment_offset),将各个集群的ID进行绝对的分段来实现全局唯一。当遇到某个集群数据增长过快后,通过命令调整下一个&ID&起始位置跳过可能存在的冲突。
优点:实现简单,且比较容易根据&ID&大小直接判断出数据处在哪个集群,对应用透明。缺点:维护相对较复杂,需要高度关注各个集群&ID&增长状况。
结论:适合大型应用,但需要高度关注各个集群&ID&增长状况。
GUID通常表示成32个16进制数字(0-9,A-F)组成的字符串,如:{21EC2020-3AEA-1069-A2DD-D},它实质上是一个128位长的二进制整数。
GUID制定的算法中使用到用户的网卡MAC地址,以保证在计算机集群中生成唯一GUID;在相同计算机上随机生成两个相同GUID的可能性是非常小的,但并不为0。所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。
优点:GUID是最简单的方案,跨平台,跨语言,跨业务逻辑,全局唯一的Id,数据间同步、迁移都能简单实现。
1)&&&&&&&&&存储占了32位,且无可读性,返回GUID给客户显得很不专业;
2)&&&&&&&&&占用了珍贵的聚集索引,一般我们不会根据GUID去查单据,并且插入时因为GUID是无需的,在聚集索引的排序规则下可能移动大量的记录。
有两位园友主推GUID,无须顺序GUID方案原因如下:
&&&&&&&&&&&GUID无序在并发下效率高,并且一个数据页内添加新行,是在B树内增加,本质没有什么数据被移动,唯一可能的,是页填充因子满了,需要拆页。而GUID方案导致的拆页比顺序ID要低太多了(数据库不是很懂,暂时无法断定,大家自己认识)
&&&&&&&&&&&&&&&&我们要明白id是什么,是身份标识,标识身份是id最大的业务逻辑,不要引入什么时间,什么用户业务逻辑,那是另外一个字段干的事,使用base64(guid,uuid),是通盘考虑,完全可以更好的兼容nosql,key-value存储。
(推荐),但是倘若你系统一开始没有规划一个业务Id,那么将导致大量的改动,所以这个方案的最佳状态是一开始就设计业务Id,当然业务Id的唯一性也是我们要考虑的。
结论:适合大型应用;生成的Id不够友好;占据了32位;索引效率较低。
1)&&&&&&&&&(提点)在SQL
Server 2005中新增了NEWSEQUENTIALID函数。
详细请看:
在指定计算机上创建大于先前通过该函数生成的任何&GUID&的&GUID。&newsequentialid&产生的新的值是有规律的,则索引B+树的变化是有规律的,就不会导致索引列插入时移动大量记录的问题。
但一旦服务器重新启动,其再次生成的GUID可能反而变小(但仍然保持唯一)。这在很大程度上提高了索引的性能,但并不能保证所生成的GUID一直增大。SQL的这个函数产生的GUID很简单就可以预测,因此不适合用于安全目的。
a)&&&&&&&&&只能做为数据库列的DEFAULT VALUE,不能执行类似SELECT NEWSEQUENTIALID()的语句.
b)&&&&&&&&&如何获得生成的GUID.
如果生成的GUID所在字段做为外键要被其他表使用,我们就需要得到这个生成的值。通常,PK是一个IDENTITY字段,我们可以在INSERT之后执行&SELECT SCOPE_IDENTITY()来获得新生成的ID,但是由于NEWSEQUENTIALID()不是一个INDETITY类型,这个办法是做不到了,而他本身又只能在默认值中使用,不可以事先SELECT好再插入,那么我们如何得到呢?有以下两种方法:
--1. 定义临时表变量
DECLARE&@outputTable
TABLE(ID uniqueidentifier)
INSERT&INTO&TABLE1(col1, col2)
OUTPUT&INSERTED.ID
INTO&@outputTable
VALUES('value1',
FROM&@outputTable
--2. 标记ID字段为ROWGUID(一个表只能有一个ROWGUID)
INSERT&INTO&TABLE1(col1, col2)
VALUES('value1',
--在这里,ROWGUIDCOL其实相当于一个别名
SELECT&ROWGUIDCOL
FROM&TABLE1
结论:适合大型应用,解决了GUID无序特性导致索引列插入移动大量记录的问题。但是在关联表插入时需要返回数据库中生成的GUID;生成的Id不够友好;占据了32位。
2)&&&&&&&&&“COMB”(combined guid/timestamp,意思是:组合GUID/时间截)
(感谢:&,&)
COMB数据类型的基本设计思路是这样的:既然GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么能不能通过组合的方式,保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与GUID组合起来,在保留GUID的唯一性的同时增加了有序性,以此来提高索引效率。
在NHibernate中,COMB型主键的生成代码如下所示:
/// &summary& /// Generate a new &see cref=&Guid&/& using the comb algorithm.
/// &/summary&
private&Guid GenerateComb()
&&&&byte[] guidArray = Guid.NewGuid().ToByteArray();
&&&&DateTime baseDate =
new&DateTime();
&&&&DateTime now = DateTime.N
&&&&// Get the days and milliseconds which will be used to build&&&
&&&&//the byte string&&&
&&&&TimeSpan days =
new&TimeSpan(now.Ticks - baseDate.Ticks);
&&&&TimeSpan msecs = now.TimeOfD
&&&&// Convert to a byte array&&&&&&&
&&&&// Note that SQL Server is accurate to 1/300th of a&&&
&&&&// millisecond so we divide by 3.333333&&&
&&&&byte[] daysArray = BitConverter.GetBytes(days.Days);
&&&&byte[] msecsArray = BitConverter.GetBytes((long)
&&&&&&(msecs.TotalMilliseconds / 3.333333));
&&&&// Reverse the bytes to match SQL Servers ordering&&&
&&&&Array.Reverse(daysArray);
&&&&Array.Reverse(msecsArray);
&&&&// Copy the bytes into the guid&&&
&&&&Array.Copy(daysArray, daysArray.Length - 2, guidArray,
&&&&&&guidArray.Length - 6, 2);
&&&&Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,
&&&&&&guidArray.Length - 4, 4);
&&&&return&new&Guid(guidArray);
结论:适合大型应用。即保留GUID的唯一性的同时增加了GUID有序性,提高了索引效率;解决了关联表业务问题;生成的Id不够友好;占据了32位。(强烈推荐)
3)&&&&&&&&&长度问题,使用Base64或Ascii85编码解决。(要注意的是上述有序性方案在进行编码后也会变得无序)
GUID:{3FF89-11D3-9A0C-1}
当需要使用更少的字符表示GUID时,可能会使用Base64或Ascii85编码。Base64编码的GUID有22-24个字符,如:
7QDBkvCA1+B9K/U0vrQx1A
7QDBkvCA1+B9K/U0vrQx1A==
Ascii85编码后是20个字符,如:
5:$Hj:Pf\4RLB9%kU\Lj
&&&&&&&&&&&&&&&&&&&代码如:
&&&&&&&&&Guid guid = Guid.NewGuid();
&&&&&&&&&byte[] buffer = guid.ToByteArray();
&&&&&&&&&var shortGuid = Convert.ToBase64String(buffer);
&&&&&&&&&&&&&&&&&&&结论:适合大型应用,缩短GUID的长度。生成的Id不够友好;索引效率较低。
7、&&GUID TO Int64
对于GUID的可读性,有园友给出如下方案:(感谢:)
/// &summary&
/// 根据GUID获取19位的唯一数字序列
/// &/summary&
public&static&long&GuidToLongID()
&&&&byte[] buffer = Guid.NewGuid().ToByteArray();
&&&&return&BitConverter.ToInt64(buffer, 0);
即将GUID转为了19位数字,数字反馈给客户可以一定程度上缓解友好性问题。EG:
GUID: cfdab168-211d-41e6-8634-ef5ba6502a22&&&&(不友好)
Int64: 9746068&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&(友好性还行)
不过我的小伙伴说ToInt64后就不唯一了。因此我专门写了个并发测试程序,后文将给出测试结果截图及代码简单说明。
(唯一性、业务适合性是可以权衡的,这个唯一性肯定比不过GUID的,一般程序上都会安排错误处理机制,比如异常后执行一次重插的方案……)
结论:适合大型应用,生成相对友好的Id(纯数字)------因简单和业务友好性而推荐。
8、&&自己写编码规则
优点:全局唯一Id,符合业务后续长远的发展(可能具体业务需要自己的编码规则等等)。
缺陷:根据具体编码规则实现而不同;还要考虑倘若主键在业务上允许改变的,会带来外键同步的麻烦。
我这边写两个编码规则方案:(可能不唯一,只是个人方案,也请大家提出自己的编码规则)
1)&&&&&&&&&12位年月日时分秒+3位服务器编码+3位表编码+5位随机码&&(这样就完全单机完成生成全局唯一编码)---共23位
缺陷:因为附带随机码,所以编码缺少一定的顺序感。(生成高唯一性随机码的方案稍后给给出程序)
2)&&&&&&&&&12位年月日时分秒+3位服务器编码+3位表编码+5位流水码&&(这样流水码就需要结合数据库和缓存)---共23位
缺陷:因为使用到流水码,流水码的生成必然会遇到和MaxId、序列表、Sequence方案中类似的问题
(为什么没有毫秒?毫秒也不具备业务可读性,我改用5位随机码、流水码代替,推测1秒内应该不会下99999[五位]条语法)
结论:适合大型应用,从业务上来说,有一个规则的编码能体现产品的专业成度。(强烈推荐)
GUID生成Int64值后是否还具有唯一性测试
主要测试思路:
1.&&&&&&&&&根据内核数使用多线程并发生成Guid后再转为Int64位值,放入集合A、B、…N,多少个线程就有多少个集合。
2.&&&&&&&&&再使用Dictionary字典高效查key的特性,将步骤1中生成的多个集合全部加到Dictionary中,看是否有重复值。
示例注解:测了&Dictionary&long,bool&&最大容量就在5999470左右,所以每次并发生成的唯一值总数控制在此范围内,让测试达到最有效话。
主要代码:
for&(int&i = 0; i &= Environment.ProcessorCount - 1; i++)
&&&&ThreadPool.QueueUserWorkItem(
&&&&&&&&(list) =&
&&&&&&&&&&&&List&long& tempList = list
as&List&long&;
&&&&&&&&&&&&for&(int&j = 1; j & listL j++)
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&byte[] buffer = Guid.NewGuid().ToByteArray();
&&&&&&&&&&&&&&&&tempList.Add(BitConverter.ToInt64(buffer, 0));
&&&&&&&&&&&&}
&&&&&&&&&&&&barrier.SignalAndWait();
&&&&&&&&}, totalList[i]);
测试数据截图:&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
数据一(循环1000次,测试数:)
数据二(循环5000次,测试数:)--跑了一个晚上……
感谢的专业回答:(大家分析下,我数学比较差,稍后再说自己的理解)
GUID桶数量:(2 ^ 4) ^ 32 = 2 ^ 128
Int64桶数量:&2 ^ 64
倘若每个桶的机会是均等的,则每个桶的GUID数量为:
(2 ^ 128) / (2 ^ 64) = 2 ^ 64 =
也就是说,其实重复的机会是有的,只是概率问题。
楼主测试数是,发生重复的概率是:
1 - ((1 - (1 / (2 ^ 64))) ^ )&≈&1 - ((1 - 1 /&(2 ^ 64)) ^ (2 ^ 32)) & 1 - 1 + 1 / (2 ^ 32) = 1 / (2 ^ 32)&≈&2.
(唯一性、业务适合性是可以权衡的,这个唯一性肯定比不过GUID的,一般程序上都会安排错误处理机制,比如异常后执行一次重插的方案……)
(唯一性、业务适合性是可以权衡的,这个唯一性肯定比不过GUID的,一般程序上都会安排错误处理机制,比如异常后执行一次重插的方案……)
结论:GUID转为Int64值后,也具有高唯一性,可以使用与项目中。
Random生成高唯一性随机码
我使用了五种Random生成方案,要Random生成唯一主要因素就是种子参数要唯一。(这是比较久以前写的测试案例了,一直找不到合适的博文放,今天终于找到合适的地方了)
不过该测试是在单线程下的,多线程应使用不同的Random实例,所以对结果影响不会太大。
1.&&&&&&&&&使用Environment.TickCount做为Random参数(即Random的默认参数),重复性最大。
2.&&&&&&&&&使用DateTime.Now.Ticks做为Random参数,存在重复。
3.&&&&&&&&&使用unchecked((int)DateTime.Now.Ticks)做为Random参数,存在重复。
4.&&&&&&&&&使用Guid.NewGuid().GetHashCode()做为random参数,测试不存在重复(或存在性极小)。
5.&&&&&&&&&使用RNGCryptoServiceProvider做为random参数,测试不存在重复(或存在性极小)。
&&&&&&&&static int GetRandomSeed()
&&&&&&&&&&&&byte[] bytes = new byte[4];
&&&&&&&&&&&&System.Security.Cryptography.RNGCryptoServiceProvider rng
= new System.Security.Cryptography.RNGCryptoServiceProvider();
&&&&&&&&&&&&rng.GetBytes(bytes);
&&&&&&&&&&&&return BitConverter.ToInt32(bytes, 0);
测试结果:
结论:随机码使用RNGCryptoServiceProvider或Guid.NewGuid().GetHashCode()生成的唯一性较高。
一些精彩评论(部分更新到原博文对应的地方)
数据库文件体积只是一个参考值,可水平扩展系统性能(如nosql,缓存系统)并不和文件体积有高指数的线性相关。
如taobao/qq的系统比拼byte系统慢,关键在于索引的命中率,缓存,系统的水平扩展。
如果数据库很少,你搞这么多byte能提高性能?
如果数据库很大,你搞这么多byte不兼容索引不兼容缓存,不是害自已吗?
如果数据库要求伸缩性,你搞这么多byte,需要不断改程序,不是自找苦吗?
如果数据库要求移植性,你搞这么多byte,移植起来不如重新设计,这是不是很多公司不断加班的原因?
不依赖于数据存储系统是分层设计思想的精华,实现战略性能最大化,而不是追求战术单机性能最大化。
不要迷信数据库性能,不要迷信三范式,不要使用外键,不要使用byte,不要使用自增id,不要使用存储过程,不要使用内部函数,不要使用非标准sql,存储系统只做存储系统的事。当出现系统性能时,如此设计的数据库可以更好的实现迁移数据库(如mysql-&oracle),实现nosql改造((mongodb/hadoop),实现key-value缓存(redis,memcache)。
很多程序员有对性能认识有误区,如使用存储过程代替正常程序,其实使用存储过程只是追求单服务器的高性能,当需要服务器水平扩展时,存储过程中的业务逻辑就是你的噩运。
除数字日期,能用字符串存储的字段尽量使用字符串存储,不要为节省那不值钱的1个g的硬盘而使用类似字节之类的字段,进而大幅牺牲系统可伸缩性和可扩展性。
不要为了追求所谓的性能,引入byte,使用byte注定是短命和难于移植,想想为什么html,email一直流行,因为它们使用的是字符串表示法,只要有人类永远都能解析,如email把二进制转成base64存储。除了实时系统,视频外,建议使用字符串来存储数据,系统性能的关键在于分布式,在于水平扩展。
本次博文到此结束,希望大家对本次主题“如何在高并发分布式系统中生成全局唯一Id”多提出自己宝贵的意见。另外看着感觉舒服,还请多帮推荐…推荐……
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:149289次
积分:1513
积分:1513
排名:千里之外
原创:18篇
转载:49篇
(1)(12)(4)(1)(1)(5)(1)(2)(8)(10)(3)(3)(6)(9)(1)

我要回帖

更多关于 微信信息不见了 的文章

 

随机推荐