zoukankan      html  css  js  c++  java
  • 自增长主键Id的另类设计

    一、引言

    在使用ORM框架时,一个表有一个主键是必须的,如果没有主键,就没有办法来唯一的更新一条记录。在Sql Server数据库和Mysql数据库设置自增长的主键是一件很轻松的事情,如果在Oracle数据库中设置自增长的主键是比较繁琐的。本文不讨论数据库里单表的自增长问题,探讨的是多表自增长唯一Id的设计。
    如果各位看官遇到这个多表自增长唯一Id的这个需求,会怎么处理呢?

    二、GUID的介绍

    关于自增长主键的问题,有些人可能会想到.Net中的GUID,先对这个GUID进行测试。

     public void GuidTest()
            {
                string guid = Guid.NewGuid().ToString();
                Console.WriteLine(guid);
                Console.WriteLine(guid.Length);
            }
    GUID Test

    下面是输出结果:

    c8eb1c81-eafa-423b-a1bf-15fd5df829c4
    36

    可以看到这个GUID是一个字符串,长度36位,还真够长的,估计在用的时候,如果想减少位数,可以把横杠-去掉,少了4位,还剩32位,不过还是挺长的。
    以我的观察来看,现在如果谁把数据库的主键设计成这个GUID,还真的是个二货:)
    原因有三,1)太长了,在数据库里占空间  
                  2)排序不方便
                  3)检索时字符串的效率不如整数
    本来对GUID只想一笔而过,看来写得太多了。
     

    三、单机版自增长Id实现

    针对多表下的自增长的唯一的ID,首先想到的就是时间,把这个时间格式化成整形的数字,就可以解决问题。为了减少位数,把日期的年的部分20去掉,毫秒数取两位,共15位。
    为了防止重复,程序每次生成,如果当前生成的Id大于当前时间下的Id,会保存在在xml里,下次程序启动,会检查xml里保存的Id和当前时间下的Id,两者取其大。这样做是为了,防止程序重启,生成的Id重复。当然,为了多线程的安全,顺手lock。代码如下:

    public class IdGenerator
        {
            private static string idXml = "id.xml";
            private static long current = 0;
            private static object obj = new object();
            private static string format = "yyMMddHHmmssfff"; //15位
    
            static IdGenerator()
            {
                var xDoc = XDocument.Load(idXml);
                long last = 0;
                long.TryParse(xDoc.Root.Value, out last);
    
                //每次启动检查最后生成的Id是否大于当前时间下的Id
                long now = long.Parse(DateTime.Now.ToString(format));
                if (last > now)
                {
                    current = last;
                }
                else
                {
                    current = now;
                }
            }
    
            public static long GetId()
            {
                lock (obj)
                {
                    current += 1;
    
                    //如果当前的生成的Id大于当前时间下的Id,就要保存下来
                    if (current > long.Parse(DateTime.Now.ToString(format)))
                    {
                        var xDoc = XDocument.Load(idXml);
                        xDoc.Root.Value = current.ToString();
                        xDoc.Save(idXml);
                    }
                    return current;
                }
            }
        }
    IdGenerator


    四、分布式下的自增长Id实现
    在现实环境下,项目中可能有多个网站或多个应用程序使用这个自增长的Id,这样就要考虑分布式的情况,这里实现分布式使用WCF技术,WCF实现在本地和服务器通过契约来通信,抽象之后,调用服务器代码就像调用本地代码。
    首先定义契约:

    namespace WCFServiceTest
    {
        [ServiceContract]
        public interface IIdGenContract
        {
            [OperationContract]
            long GetIdByWCF();
        }
    }
    IIdGenContract

    该接口里有一个方法GetIdByWCF,这个方法需要服务端来实现。

    namespace WCFServiceTest
    {
        [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, MaxItemsInObjectGraph = Int32.MaxValue)]
        public class IdGenService : IIdGenContract
        {
            public static IdGenService Instance = new IdGenService();
    
            public long GetIdByWCF()
            {
                return IdGenerateHelper.IdGenerator.GetId();
            }
    
        }
    }
    IdGenService

    下一步就是开启这个服务

    namespace WCFServiceTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                ServiceHost host = new ServiceHost(IdGenService.Instance);
                host.Open();
                Console.WriteLine(typeof(IdGenService) + " Opened");
                Console.WriteLine("服务地址:" + host.Description.Endpoints[0].ListenUri);
              
                Console.Read();
            }
        }
    }
    Program

    这里面需要配置文件,如果是网站就是web.config,桌面程序就是app.config,配置文件的详细会在下面的demo代码里。
    服务端完成了,下面就是编写客户端的代码来调用该服务了。客户端通过契约(接口)来和服务端进行通信,也要实现这个接口。

    namespace ConsoleApplication2
    {
        public class IdService : ClientBase<IIdGenContract>, IIdGenContract
        {
            public long GetIdByWCF()
            {
                return base.Channel.GetIdByWCF();
            }
        }
    }
    ClientIdService

    接下来就是测试代码了

    namespace ConsoleApplication2
    {
        /// <summary>
        /// 通过调用WCF服务的方式生成自增长Id
        /// </summary>
        class Program
        {
            static void Main(string[] args)
            {
                long id = new ClientIdService().GetIdByWCF();
                Console.WriteLine(id);
                Console.Read();
            }
        }
    }
    Program

    四、结束
    一切按计划执行,输出一个唯一的Id,本文完整Demo下载。

  • 相关阅读:
    协程初探
    属性传值
    分析代理模式
    上下文菜单与TrackPopupMenu
    走进小作坊(十六)----口碑营销
    线程池QueueUserWorkItem
    exosip
    Spark Core源代码分析: Spark任务运行模型
    微软2014校园招聘笔试试题
    怎样配置Tomcat环境变量
  • 原文地址:https://www.cnblogs.com/lhking/p/3945865.html
Copyright © 2011-2022 走看看