背景
我在上篇文章“.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 }
執行結果
備注
優化前后,速度相差幾百倍。