国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

淺談C#在網(wǎng)絡波動時防重復提交的方法

2020-01-24 00:05:57
字體:
供稿:網(wǎng)友

前幾天,公司數(shù)據(jù)庫出現(xiàn)了兩條相同的數(shù)據(jù),而且時間相同(毫秒也相同)。排查原因,發(fā)現(xiàn)是網(wǎng)絡波動造成了重復提交。

由于網(wǎng)絡波動而重復提交的例子也比較多:

 

網(wǎng)絡上,防重復提交的方法也很多,使用redis鎖,代碼層面使用lock。

但是,我沒有發(fā)現(xiàn)一個符合我心意的解決方案。因為網(wǎng)上的解決方案,第一次提交返回成功,第二次提交返回失敗。由于兩次返回信息不一致,一次成功一次失敗,我們不確定客戶端是以哪個返回信息為準,雖然我們希望客戶端以第一次返回成功的信息為準,但客戶端也可能以第二次失敗信息運行,這是一個不確定的結(jié)果。

在重復提交后,如果客戶端的接收到的信息都相同,都是成功,那客戶端就可以正常運行,就不會影響用戶體驗。

我想到一個緩存類,來源于PetaPoco。

Cache<TKey, TValue>代碼如下:

public class Cache<TKey, TValue>  {    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();    private readonly Dictionary<TKey, TValue> _map = new Dictionary<TKey, TValue>();    public int Count {      get { return _map.Count; }    }    public TValue Execute(TKey key, Func<TValue> factory)    {      // Check cache      _lock.EnterReadLock();      TValue val;      try {        if (_map.TryGetValue(key, out val))          return val;      } finally {        _lock.ExitReadLock();      }      // Cache it      _lock.EnterWriteLock();      try {        // Check again        if (_map.TryGetValue(key, out val))          return val;        // Create it        val = factory();        // Store it        _map.Add(key, val);        // Done        return val;      } finally {        _lock.ExitWriteLock();      }    }    public void Clear()    {      // Cache it      _lock.EnterWriteLock();      try {        _map.Clear();      } finally {        _lock.ExitWriteLock();      }    }  }

Cache<TKey, TValue>符合我的要求,第一次運行后,會將值緩存,第二次提交會返回第一次的值。

但是,細細分析Cache<TKey, TValue> 類,可以發(fā)現(xiàn)有以下幾個缺點

1、 不會自動清空緩存,適合一些key不多的數(shù)據(jù),不適合做為網(wǎng)絡接口。

2、 由于_lock.EnterWriteLock,多線程會變成并單線程,不適合做為網(wǎng)絡接口。

3、 沒有過期緩存判斷。

于是我對Cache<TKey, TValue>進行改造。

AntiDupCache代碼如下:

/// <summary>  /// 防重復緩存  /// </summary>  /// <typeparam name="TKey"></typeparam>  /// <typeparam name="TValue"></typeparam>  public class AntiDupCache<TKey, TValue>  {    private readonly int _maxCount;//緩存最高數(shù)量    private readonly long _expireTicks;//超時 Ticks    private long _lastTicks;//最后Ticks    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();    private readonly ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();    private readonly Dictionary<TKey, Tuple<long, TValue>> _map = new Dictionary<TKey, Tuple<long, TValue>>();    private readonly Dictionary<TKey, AntiDupLockSlim> _lockDict = new Dictionary<TKey, AntiDupLockSlim>();    private readonly Queue<TKey> _queue = new Queue<TKey>();    class AntiDupLockSlim : ReaderWriterLockSlim { public int UseCount; }    /// <summary>    /// 防重復緩存    /// </summary>    /// <param name="maxCount">緩存最高數(shù)量,0 不緩存,-1 緩存所有</param>    /// <param name="expireSecond">超時秒數(shù),0 不緩存,-1 永久緩存 </param>    public AntiDupCache(int maxCount = 100, int expireSecond = 1)    {      if (maxCount < 0) {        _maxCount = -1;      } else {        _maxCount = maxCount;      }      if (expireSecond < 0) {        _expireTicks = -1;      } else {        _expireTicks = expireSecond * TimeSpan.FromSeconds(1).Ticks;      }    }    /// <summary>    /// 個數(shù)    /// </summary>    public int Count {      get { return _map.Count; }    }    /// <summary>    /// 執(zhí)行    /// </summary>    /// <param name="key">值</param>    /// <param name="factory">執(zhí)行方法</param>    /// <returns></returns>    public TValue Execute(TKey key, Func<TValue> factory)    {      // 過期時間為0 則不緩存      if (object.Equals(null, key) || _expireTicks == 0L || _maxCount == 0) { return factory(); }      Tuple<long, TValue> tuple;      long lastTicks;      _lock.EnterReadLock();      try {        if (_map.TryGetValue(key, out tuple)) {          if (_expireTicks == -1) return tuple.Item2;          if (tuple.Item1 + _expireTicks > DateTime.Now.Ticks) return tuple.Item2;        }        lastTicks = _lastTicks;      } finally { _lock.ExitReadLock(); }      AntiDupLockSlim slim;      _slimLock.EnterUpgradeableReadLock();      try {        _lock.EnterReadLock();        try {          if (_lastTicks != lastTicks) {            if (_map.TryGetValue(key, out tuple)) {              if (_expireTicks == -1) return tuple.Item2;              if (tuple.Item1 + _expireTicks > DateTime.Now.Ticks) return tuple.Item2;            }            lastTicks = _lastTicks;          }        } finally { _lock.ExitReadLock(); }        _slimLock.EnterWriteLock();        try {          if (_lockDict.TryGetValue(key, out slim) == false) {            slim = new AntiDupLockSlim();            _lockDict[key] = slim;          }          slim.UseCount++;        } finally { _slimLock.ExitWriteLock(); }      } finally { _slimLock.ExitUpgradeableReadLock(); }      slim.EnterWriteLock();      try {        _lock.EnterReadLock();        try {          if (_lastTicks != lastTicks && _map.TryGetValue(key, out tuple)) {            if (_expireTicks == -1) return tuple.Item2;            if (tuple.Item1 + _expireTicks > DateTime.Now.Ticks) return tuple.Item2;          }        } finally { _lock.ExitReadLock(); }        var val = factory();        _lock.EnterWriteLock();        try {          _lastTicks = DateTime.Now.Ticks;          _map[key] = Tuple.Create(_lastTicks, val);          if (_maxCount > 0) {            if (_queue.Contains(key) == false) {              _queue.Enqueue(key);              if (_queue.Count > _maxCount) _map.Remove(_queue.Dequeue());            }          }        } finally { _lock.ExitWriteLock(); }        return val;      } finally {        slim.ExitWriteLock();        _slimLock.EnterWriteLock();        try {          slim.UseCount--;          if (slim.UseCount == 0) {            _lockDict.Remove(key);            slim.Dispose();          }        } finally { _slimLock.ExitWriteLock(); }      }    }    /// <summary>    /// 清空    /// </summary>    public void Clear()    {      _lock.EnterWriteLock();      try {        _map.Clear();        _queue.Clear();        _slimLock.EnterWriteLock();        try {          _lockDict.Clear();        } finally {          _slimLock.ExitWriteLock();        }      } finally {        _lock.ExitWriteLock();      }    }  }

代碼分析:

使用兩個ReaderWriterLockSlim鎖 + 一個AntiDupLockSlim鎖,實現(xiàn)并發(fā)功能。

Dictionary<TKey, Tuple<long, TValue>> _map實現(xiàn)緩存,long類型值記錄時間,實現(xiàn)緩存過期

int _maxCount + Queue<TKey> _queue,_queue 記錄key列隊,當數(shù)量大于_maxCount,清除多余緩存。

AntiDupLockSlim繼承ReaderWriterLockSlim,實現(xiàn)垃圾回收,

代碼使用 :

private readonly static AntiDupCache<int, int> antiDupCache = new AntiDupCache<int, int>(50, 1);  antiDupCache.Execute(key, () => {     ....     return val;  });

測試性能數(shù)據(jù):

----------------------- 開始  從1到100   重復次數(shù):1 單位: ms -----------------------

并發(fā)數(shù)量: 1    2    3    4    5    6    7    8    9    10   11   12

普通并發(fā): 188  93   65   46   38   36   28   31   22   20   18   19

AntiDupCache: 190  97   63   48   37   34   29   30   22   18   17   21

AntiDupQueue: 188  95   63   46   37   33   30   25   21   19   17   21

DictCache: 185  96   64   47   38   33   28   29   22   19   17   21

Cache: 185  186  186  188  188  188  184  179  180  184  184  176

第二次普通并發(fā): 180  92   63   47   38   36   26   28   20   17   16   20

----------------------- 開始  從1到100   重復次數(shù):2 單位: ms -----------------------

并發(fā)數(shù)量: 1    2    3    4    5    6    7    8    9    10   11   12

普通并發(fā): 368  191  124  93   73   61   55   47   44   37   34   44

AntiDupCache: 180  90   66   48   37   31   28   24   21   17   17   22

AntiDupQueue: 181  93   65   46   39   31   27   23   21   19   18   19

DictCache: 176  97   61   46   38   30   31   23   21   18   18   22

Cache: 183  187  186  182  186  185  184  177  181  177  176  177

第二次普通并發(fā): 366  185  127  95   71   62   56   48   43   38   34   43

----------------------- 開始  從1到100   重復次數(shù):4 單位: ms -----------------------

并發(fā)數(shù)量: 1    2    3    4    5    6    7    8    9    10   11   12

普通并發(fā): 726  371  253  190  152  132  106  91   86   74   71   69

AntiDupCache: 189  95   64   49   37   33   28   26   22   19   17   18

AntiDupQueue: 184  97   65   51   39   35   28   24   21   18   17   17

DictCache: 182  95   64   45   39   34   29   23   21   18   18   16

Cache: 170  181  180  184  182  183  181  181  176  179  179  178

第二次普通并發(fā): 723  375  250  186  150  129  107  94   87   74   71   67

----------------------- 開始  從1到100   重復次數(shù):12 單位: ms -----------------------

并發(fā)數(shù)量: 1    2    3    4    5    6    7    8    9    10   11   12

普通并發(fā): 2170 1108 762  569  450  389  325  283  253  228  206  186

AntiDupCache: 182  95   64   51   41   32   28   25   26   20   18   18

AntiDupQueue: 189  93   67   44   37   35   29   30   27   22   20   17

DictCache: 184  97   59   50   38   29   27   26   24   19   18   17

Cache: 174  189  181  184  184  177  182  180  176  176  180  179

第二次普通并發(fā): 2190 1116 753  560  456  377  324  286  249  227  202  189

仿線上環(huán)境,性能測試數(shù)據(jù):

----------------------- 仿線上環(huán)境  從1到1000  單位: ms -----------------------

并發(fā)數(shù)量: 1    2    3    4    5    6    7    8    9    10   11   12

普通并發(fā): 1852 950  636  480  388  331  280  241  213  198  181  168

AntiDupCache: 1844 949  633  481  382  320  267  239  210  195  174  170

AntiDupQueue: 1835 929  628  479  386  318  272  241  208  194  174  166

DictCache: 1841 935  629  480  378  324  269  241  207  199  176  168

Cache: 1832 1854 1851 1866 1858 1858 1832 1825 1801 1797 1788 1785

第二次普通并發(fā): 1854 943  640  468  389  321  273  237  209  198  177  172

項目:

Github: https://github.com/toolgood/ToolGood.AntiDuplication

Nuget: Install-Package ToolGood.AntiDuplication

后記:

嘗試添加 一個Queue<AntiDupLockSlim> 或Stack<AntiDupLockSlim> 用來緩存鎖,后發(fā)現(xiàn)性能效率相差不大,上下浮動。

使用 lock關鍵字加鎖,速度相差不大,代碼看似更簡單,但隱藏了一個地雷:一般人使用唯一鍵都是使用string,就意味著可能使用lock(string),鎖定字符串尤其危險,因為字符串被公共語言運行庫 (CLR)“暫留”。 這意味著整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內(nèi)容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 余干县| 河南省| 郓城县| 图们市| 屯留县| 上蔡县| 松潘县| 谢通门县| 前郭尔| 焦作市| 嫩江县| 大冶市| 浠水县| 巩义市| 昌黎县| 五台县| 庄河市| 普洱| 蓬溪县| 濮阳市| 大城县| 城步| 沧州市| 台南市| 上犹县| 嘉善县| 洱源县| 临猗县| 逊克县| 招远市| 六盘水市| 响水县| 上思县| 云安县| 潼南县| 修武县| 南宁市| 巴青县| 大兴区| 贵溪市| 孟津县|