.NET:可扩展的单据编号生成器 之 基于缓冲区的顺序号
背景
我在上篇文章“.NET:可扩展的单据编号生成器 之 顺序号(防止重复)”中介绍了如何使用“种子表”和“悲观锁”解决顺序号的问题。昨天找朋友讨论,说这种速度不够高,今天就稍微改进一下,引入一个内存缓冲区,提高生成的速度。
思路
引入内存缓冲区后,顺序号的生产流程变为:在内存中维护一个顺序号区间,在这个区间内,就直接查内存,否则更新种子表并重新更新内存区间。还是直接看代码吧。
实现
代码下载:http://yunpan.cn/Q5jj5yedRAtk5。
SeedCodeRuleProvider.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using System.Transactions; 8 using System.Text.RegularExpressions; 9 using System.Data.Entity.Infrastructure; 10 11 namespace EntityCodeRuleDemo 12 { 13 public class SeedCodeRuleProvider : ICodeRuleProvider 14 { 15 private static readonly int _OnceBufferSize = 10000; 16 private static readonly Dictionary<string, BufferSeed> _Buffer = new Dictionary<string, BufferSeed>(); 17 18 private readonly int _width; 19 20 public SeedCodeRuleProvider(int width) 21 { 22 _width = width; 23 } 24 25 public string Generate(object entity) 26 { 27 return GetSeedValue(entity).ToString().PadLeft(_width, '0'); 28 } 29 30 protected virtual string GetKey(object entity) 31 { 32 return entity.GetType().FullName; 33 } 34 35 private int GetSeedValue(object entity) 36 { 37 var key = this.GetKey(entity); 38 39 lock (_Buffer) 40 { 41 if (!_Buffer.ContainsKey(key)) 42 { 43 this.SetBufferSeed(entity, key); 44 } 45 } 46 47 lock (_Buffer[key]) 48 { 49 if (_Buffer[key].IsOverflow()) 50 { 51 this.SetBufferSeed(entity, key); 52 } 53 54 _Buffer[key].CurrentValue++; 55 56 return _Buffer[key].CurrentValue; 57 } 58 } 59 60 private void SetBufferSeed(object entity, string key) 61 { 62 var value = this.GetOrSetSeedValueFormDatabase(entity); 63 64 _Buffer[key] = new BufferSeed 65 { 66 CurrentValue = value - _OnceBufferSize, 67 MaxValue = value 68 }; 69 } 70 71 private int GetOrSetSeedValueFormDatabase(object entity) 72 { 73 var key = this.GetKey(entity); 74 75 try 76 { 77 using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, 78 new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead })) 79 { 80 var value = 0; 81 82 using (var context = new TestContext()) 83 { 84 var seed = context.CodeSeeds.Where(x => x.Key == key).FirstOrDefault(); 85 if (seed == null) 86 { 87 seed = new CodeSeed { Id = Guid.NewGuid(), Key = key, Value = -1 }; 88 context.CodeSeeds.Add(seed); 89 } 90 91 seed.Value += _OnceBufferSize; 92 context.SaveChanges(); 93 94 value = seed.Value; 95 } 96 97 ts.Complete(); 98 99 return value; 100 } 101 } 102 catch (DbUpdateException) 103 { 104 return this.GetSeedValue(entity); 105 } 106 } 107 108 public static SeedCodeRuleProvider SeedCodeRuleProviderFactory(string literal) 109 { 110 var match = new Regex("^<种子(:(?<宽度>.*?))?>$").Match(literal); 111 112 var width = match.Groups["宽度"].Value; 113 114 return new SeedCodeRuleProvider(string.IsNullOrEmpty(width) ? 5 : int.Parse(width)); 115 } 116 117 private class BufferSeed 118 { 119 public int CurrentValue { get; set; } 120 121 public int MaxValue { get; set; } 122 123 public bool IsOverflow() 124 { 125 return this.CurrentValue >= this.MaxValue; 126 } 127 } 128 } 129 }
Program.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using System.Diagnostics; 8 using System.Text.RegularExpressions; 9 10 namespace EntityCodeRuleDemo 11 { 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 CodeRuleInterpreter.RegistProviderFactory(new Regex("^<种子(:(?<宽度>.*?))?>$"), SeedCodeRuleProvider.SeedCodeRuleProviderFactory); 17 18 var generator = CodeRuleInterpreter 19 .Interpret("前缀_<日期:yyyy_MM_dd>_<属性:NamePinYin>_<种子:6>"); 20 21 var watch = Stopwatch.StartNew(); 22 23 for (var i = 0; i < 10000; i++) 24 { 25 generator.Generate(new Employee { NamePinYin = "DUANGW" }); 26 } 27 28 watch.Stop(); 29 30 Console.WriteLine("1万条编号用时:" + watch.Elapsed); 31 } 32 } 33 }
执行结果
备注
优化前后,速度相差几百倍。