在高并发分布式系统中生成全局唯一Id

单独开一个数据库,获取全局唯一的自增序列号或各表的MaxId

1)         使用自增序列号表

专门一个数据库,生成序列号。开启事物,每次操作插入时,先将数据插入到序列表并返回自增序列号用于做为唯一Id进行业务数据插入。

注意:需要定期清理序列表的数据以保证获取序列号的效率;插入序列表记录时要开启事物。

使用此方案的问题是:每次的查询序列号是一个性能损耗;如果这个序列号列暴了,那就杯具了,你不知道哪个表使用了哪个序列,所以就必须换另一种唯一Id方式如GUID。

2)         使用MaxId表存储各表的MaxId值

专门一个数据库,记录各个表的MaxId值,建一个存储过程来取Id,逻辑大致为:开启事物,对于在表中不存在记录,直接返回一个默认值为1的键值,同时插入该条记录到table_key表中。而对于已存在的记录,key值直接在原来的key基础上加1更新到MaxId表中并返回key。

使用此方案的问题是:每次的查询MaxId是一个性能损耗;不过不会像自增序列表那么容易列暴掉,因为是摆表进行划分的。

详细可参考:《使用MaxId表存储各表的MaxId值,以获取全局唯一Id》

                   我截取此文中的sql语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
第一步:创建表
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
)
as
begin
      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
end

感谢园友的好建议:

1.         (@辉_辉)建议给table_key中为每个表初始化一条key为1的记录,这样就不用每次if来判断了。

2.         (@乐活的CodeMonkey)建议给存储过程中数据库事物隔离级别提高一下,因为出现在CS代码层上使用如下事物代码会导致并发重复问题.

1
2
3
4
5
6
7
8
TransactionOptions option = new  TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadUncommitted;
option.Timeout = new  TimeSpan(0, 10, 0);
  
using  (TransactionScope transaction = new  TransactionScope(TransactionScopeOption.RequiresNew, option))
{
         //调用存储过程
}

在咨询过DBA后,这个存储过程提高数据库隔离级别会加大数据库访问压力,导致响应超时问题。所以这个建议我们只能在代码编写宣导上做

3.         (@土豆烤肉)存储过程中不使用事物,一旦使用到事物性能就急剧下滑。直接使用UPDATE获取到的更新锁,即SQL SERVER会保证UPDATE的顺序执行。(已在用户过千万的并发系统中使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create  procedure  [dbo].[up_get_table_key]
(
    @table_name     varchar (50),
    @key_value      int  output
)
as
begin
 
     SET  NOCOUNT ON ;
     DECLARE  @maxId INT
     UPDATE  table_key
     SET  @maxId = key_value,key_value = key_value + 1
     WHERE  table_name=@table_name
     SELECT  @maxId
 
end

结论:适用中型应用,此方案解决了分表,关联表插入记录的问题。但是无法满足高并发性能要求。同时也存在单点问题,如果这个数据库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较短,友好性比较好。(强烈推荐)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值