从Redis生成数据表主键标识

对于MySql的全局ID(主键),我们一般采用自增整数列、程序生成GUID、单独的表作为ID生成器,这几种方案各有优劣,最终效率都不能说十分理想(尤其海量数据下),其实通过Redis的INCR可以很方便生成自增数,因为是操作缓存,生成的效率也不错。 

插入数据库的主键也是连续增长的,配合索引,读取效率也很高。

下面是从Redis中获取新的自增数的代码:

public sealed class Utils
    {
        private static readonly object sequence_locker = new object();

        /// <summary>
        /// 从Redis获取一个自增序列标识
        /// </summary>
        /// <param name="key">键名</param>
        /// <param name="getting">获取序列标识替代生成方法(若缓存中不存在)</param>
        public static int NewSequenceFromRedis(string key, Func<int> alternative)
        {
            if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
            lock (sequence_locker)
            {
                RedisHelper redis = new RedisHelper(1); //in db1
                long value = redis.StringIncrement(key, 1);
                if (value > Int32.MaxValue || value < Int32.MinValue) throw new OverflowException("The sequence overflow.");
                if (value <= 1 && alternative != null)
                {
                    value = alternative();
                    redis.StringSet(key, value.ToString());  //update
                }

                return (int)value;
            }
        }
    }

 我的项目用的Repository模式,所以获取新主键的方法我写到Repository父类中(在接口IRepository中有定义),这样各个Repository可以重载属性TableName,当然你完全可以把NewIdentity独立出去作为公共方法,只要传入TableName即可

public abstract class RepositoryBase : IRepository
    {
        protected IDbConnection _db;
        public RepositoryBase(IDbConnection connection)
        {
            _db = connection;
        }

        protected virtual string TableName { get; }

        public virtual int NewIdentity()
        {
            if (string.IsNullOrEmpty(this.TableName))
                throw new NoNullAllowedException("TableName is null.");

            var redisKey = $"Sequence_{TableName}.Id";  //eg. Sequence_lottery.Id, Sequence_player.Id
            var id = Utils.NewSequenceFromRedis(redisKey, () =>
            {
                //如果从Redis中没获取到主键标识(比如Redis键被删除),则用数据表最大标识+1替代
                return _db.ExecuteScalar<int>("SELECT MAX(id) AS MaxId FROM " + TableName) + 1;
            });
            return id;
        }
    }

 下面是测试代码,并且用StopWatch测试每次执行效率:

using (var ctx = DI.Resolve<IRepositoryContext>())
{
    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
    var userId = ctx.Resolve<IUserRepository>().NewIdentity();
    sw.Stop();
    Console.WriteLine("userId={0}, elapsed: {1}ms", userId, sw.ElapsedMilliseconds);

    sw.Restart();
    var gameId = ctx.Resolve<IGameRepository>().NewIdentity();
    sw.Stop();
    Console.WriteLine("gameId={0}, elapsed: {1}ms", gameId, sw.ElapsedMilliseconds);

    sw.Restart();
    var roomId = ctx.Resolve<IGameRepository>().NewRoomIdentity();
    sw.Stop();
    Console.WriteLine("roomId={0}, elapsed: {1}ms", roomId, sw.ElapsedMilliseconds);

    sw.Restart();
    var betItemId = ctx.Resolve<IGameRepository>().NewBetItemIdentity();
    sw.Stop();
    Console.WriteLine("betItemId={0}, elapsed: {1}ms", betItemId, sw.ElapsedMilliseconds);

    sw.Restart();
    var lotteryId = ctx.Resolve<ILotteryRepository>().NewIdentity();
    sw.Stop();
    Console.WriteLine("lotteryId={0}, elapsed: {1}ms", lotteryId, sw.ElapsedMilliseconds);

    //省略的代码。。。
}

 运行结果如下,除第一次获取主键开销98毫秒(估计建立redis连接有关),后面的几乎都是0毫秒(Redis本来就飞快,这里不用考虑数据库连接开闭的时间消耗)

 

查看Redis中的键值:

 

当然,代码还需要完善,比如Redis挂了的情况,ID主键可以读取MAX(ID)+1来替代主键生成,但是Redis又恢复后,自增数怎么同步

posted @ 2017-10-02 03:36  felixnet  阅读(3421)  评论(0编辑  收藏  举报