從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又恢復后,自增數怎么同步


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM