十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
這篇文章將為大家詳細講解有關怎么在C#中實現(xiàn)請求唯一性校驗支持高并發(fā),文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
蓬江網(wǎng)站建設公司成都創(chuàng)新互聯(lián)公司,蓬江網(wǎng)站設計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為蓬江上1000+提供企業(yè)網(wǎng)站建設服務。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務好的蓬江做網(wǎng)站的公司定做!
使用場景描述:
網(wǎng)絡請求中經(jīng)常會遇到發(fā)送的請求,服務端響應是成功的,但是返回的時候出現(xiàn)網(wǎng)絡故障,導致客戶端無法接收到請求結(jié)果,那么客戶端程序可能判斷為網(wǎng)絡故障,而重復發(fā)送同一個請求。當然如果接口中定義了請求結(jié)果查詢接口,那么這種重復會相對少一些。特別是交易類的數(shù)據(jù),這種操作更是需要避免重復發(fā)送請求。另外一種情況是用戶過于快速的點擊界面按鈕,產(chǎn)生連續(xù)的相同內(nèi)容請求,那么后端也需要進行過濾,這種一般出現(xiàn)在系統(tǒng)對接上,無法去控制第三方系統(tǒng)的業(yè)務邏輯,需要從自身業(yè)務邏輯里面去限定。
其他需求描述:
這類請求一般存在時間范圍和高并發(fā)的特點,就是短時間內(nèi)會出現(xiàn)重復的請求,因此對模塊需要支持高并發(fā)性。
技術實現(xiàn):
對請求的業(yè)務內(nèi)容進行MD5摘要,并且將MD5摘要存儲到緩存中,每個請求數(shù)據(jù)都通過這個一個公共的調(diào)用的方法進行判斷。
代碼實現(xiàn):
公共調(diào)用代碼 UniqueCheck 采用單例模式創(chuàng)建唯一對象,便于在多線程調(diào)用的時候,只訪問一個統(tǒng)一的緩存庫
/* * volatile就像大家更熟悉的const一樣,volatile是一個類型修飾符(type specifier)。 * 它是被設計用來修飾被不同線程訪問和修改的變量。 * 如果沒有volatile,基本上會導致這樣的結(jié)果:要么無法編寫多線程程序,要么編譯器失去大量優(yōu)化的機會。 */ private static readonly object lockHelper = new object(); private volatile static UniqueCheck _instance; ////// 獲取單一實例 /// ///public static UniqueCheck GetInstance() { if (_instance == null) { lock (lockHelper) { if (_instance == null) _instance = new UniqueCheck(); } } return _instance; }
這里需要注意volatile的修飾符,在實際測試過程中,如果沒有此修飾符,在高并發(fā)的情況下會出現(xiàn)報錯。
自定義一個可以進行并發(fā)處理隊列,代碼如下:ConcurrentLinkedQueue
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace PackgeUniqueCheck { ////// 非加鎖并發(fā)隊列,處理100個并發(fā)數(shù)以內(nèi) /// ///public class ConcurrentLinkedQueue { private class Node { internal K Item; internal Node Next; public Node(K item, Node next) { this.Item = item; this.Next = next; } } private Node _head; private Node _tail; public ConcurrentLinkedQueue() { _head = new Node (default(T), null); _tail = _head; } public bool IsEmpty { get { return (_head.Next == null); } } /// /// 進入隊列 /// /// public void Enqueue(T item) { NodenewNode = new Node (item, null); while (true) { Node curTail = _tail; Node residue = curTail.Next; //判斷_tail是否被其他process改變 if (curTail == _tail) { //A 有其他process執(zhí)行C成功,_tail應該指向新的節(jié)點 if (residue == null) { //C 其他process改變了tail節(jié)點,需要重新取tail節(jié)點 if (Interlocked.CompareExchange >( ref curTail.Next, newNode, residue) == residue) { //D 嘗試修改tail Interlocked.CompareExchange >(ref _tail, newNode, curTail); return; } } else { //B 幫助其他線程完成D操作 Interlocked.CompareExchange >(ref _tail, residue, curTail); } } } } /// /// 隊列取數(shù)據(jù) /// /// ///public bool TryDequeue(out T result) { Node curHead; Node curTail; Node next; while (true) { curHead = _head; curTail = _tail; next = curHead.Next; if (curHead == _head) { if (next == null) //Queue為空 { result = default(T); return false; } if (curHead == curTail) //Queue處于Enqueue第一個node的過程中 { //嘗試幫助其他Process完成操作 Interlocked.CompareExchange >(ref _tail, next, curTail); } else { //取next.Item必須放到CAS之前 result = next.Item; //如果_head沒有發(fā)生改變,則將_head指向next并退出 if (Interlocked.CompareExchange >(ref _head, next, curHead) == curHead) break; } } } return true; } /// /// 嘗試獲取最后一個對象 /// /// ///public bool TryGetTail(out T result) { result = default(T); if (_tail == null) { return false; } result = _tail.Item; return true; } } }
雖然是一個非常簡單的唯一性校驗邏輯,但是要做到高效率,高并發(fā)支持,高可靠性,以及低內(nèi)存占用,需要實現(xiàn)這樣的需求,需要做細致的模擬測試。
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Collections; namespace PackgeUniqueCheck { public class UniqueCheck { /* * volatile就像大家更熟悉的const一樣,volatile是一個類型修飾符(type specifier)。 * 它是被設計用來修飾被不同線程訪問和修改的變量。 * 如果沒有volatile,基本上會導致這樣的結(jié)果:要么無法編寫多線程程序,要么編譯器失去大量優(yōu)化的機會。 */ private static readonly object lockHelper = new object(); private volatile static UniqueCheck _instance; ////// 獲取單一實例 /// ///public static UniqueCheck GetInstance() { if (_instance == null) { lock (lockHelper) { if (_instance == null) _instance = new UniqueCheck(); } } return _instance; } private UniqueCheck() { //創(chuàng)建一個線程安全的哈希表,作為字典緩存 _DataKey = Hashtable.Synchronized(new Hashtable()); Queue myqueue = new Queue(); _DataQueue = Queue.Synchronized(myqueue); _Myqueue = new ConcurrentLinkedQueue (); _Timer = new Thread(DoTicket); _Timer.Start(); } #region 公共屬性設置 /// /// 設定定時線程的休眠時間長度:默認為1分鐘 /// 時間范圍:1-7200000,值為1毫秒到2小時 /// /// public void SetTimeSpan(int value) { if (value > 0&& value <=7200000) { _TimeSpan = value; } } ////// 設定緩存Cache中的最大記錄條數(shù) /// 值范圍:1-5000000,1到500萬 /// /// public void SetCacheMaxNum(int value) { if (value > 0 && value <= 5000000) { _CacheMaxNum = value; } } ////// 設置是否在控制臺中顯示日志 /// /// public void SetIsShowMsg(bool value) { Helper.IsShowMsg = value; } ////// 線程請求阻塞增量 /// 值范圍:1-CacheMaxNum,建議設置為緩存最大值的10%-20% /// /// public void SetBlockNumExt(int value) { if (value > 0 && value <= _CacheMaxNum) { _BlockNumExt = value; } } ////// 請求阻塞時間 /// 值范圍:1-max,根據(jù)阻塞增量設置請求阻塞時間 /// 阻塞時間越長,阻塞增量可以設置越大,但是請求實時響應就越差 /// /// public void SetBlockSpanTime(int value) { if (value > 0) { _BlockSpanTime = value; } } #endregion #region 私有變量 ////// 內(nèi)部運行線程 /// private Thread _runner = null; ////// 可處理高并發(fā)的隊列 /// private ConcurrentLinkedQueue_Myqueue = null; /// /// 唯一內(nèi)容的時間健值對 /// private Hashtable _DataKey = null; ////// 內(nèi)容時間隊列 /// private Queue _DataQueue = null; ////// 定時線程的休眠時間長度:默認為1分鐘 /// private int _TimeSpan = 3000; ////// 定時計時器線程 /// private Thread _Timer = null; ////// 緩存Cache中的最大記錄條數(shù) /// private int _CacheMaxNum = 500000; ////// 線程請求阻塞增量 /// private int _BlockNumExt = 10000; ////// 請求阻塞時間 /// private int _BlockSpanTime = 100; #endregion #region 私有方法 private void StartRun() { _runner = new Thread(DoAction); _runner.Start(); Helper.ShowMsg("內(nèi)部線程啟動成功!"); } private string GetItem() { string tp = string.Empty; bool result = _Myqueue.TryDequeue(out tp); return tp; } ////// 執(zhí)行循環(huán)操作 /// private void DoAction() { while (true) { while (!_Myqueue.IsEmpty) { string item = GetItem(); _DataQueue.Enqueue(item); if (!_DataKey.ContainsKey(item)) { _DataKey.Add(item, DateTime.Now); } } //Helper.ShowMsg("當前數(shù)組已經(jīng)為空,處理線程進入休眠狀態(tài)..."); Thread.Sleep(2); } } ////// 執(zhí)行定時器的動作 /// private void DoTicket() { while (true) { Helper.ShowMsg("當前數(shù)據(jù)隊列個數(shù):" + _DataQueue.Count.ToString()); if (_DataQueue.Count > _CacheMaxNum) { while (true) { Helper.ShowMsg(string.Format("當前隊列數(shù):{0},已經(jīng)超出最大長度:{1},開始進行清理操作...", _DataQueue.Count, _CacheMaxNum.ToString())); string item = _DataQueue.Dequeue().ToString(); if (!string.IsNullOrEmpty(item)) { if (_DataKey.ContainsKey(item)) { _DataKey.Remove(item); } if (_DataQueue.Count <= _CacheMaxNum) { Helper.ShowMsg("清理完成,開始休眠清理線程..."); break; } } } } Thread.Sleep(_TimeSpan); } } ////// 線程進行睡眠等待 /// 如果當前負載壓力大大超出了線程的處理能力 /// 那么需要進行延時調(diào)用 /// private void BlockThread() { if (_DataQueue.Count > _CacheMaxNum + _BlockNumExt) { Thread.Sleep(_BlockSpanTime); } } #endregion #region 公共方法 ////// 開啟服務線程 /// public void Start() { if (_runner == null) { StartRun(); } else { if (_runner.IsAlive == false) { StartRun(); } } } ////// 關閉服務線程 /// public void Stop() { if (_runner != null) { _runner.Abort(); _runner = null; } } ////// 添加內(nèi)容信息 /// /// 內(nèi)容信息 ///true:緩存中不包含此值,隊列添加成功,false:緩存中包含此值,隊列添加失敗 public bool AddItem(string item) { BlockThread(); item = Helper.MakeMd5(item); if (_DataKey.ContainsKey(item)) { return false; } else { _Myqueue.Enqueue(item); return true; } } ////// 判斷內(nèi)容信息是否已經(jīng)存在 /// /// 內(nèi)容信息 ///true:信息已經(jīng)存在于緩存中,false:信息不存在于緩存中 public bool CheckItem(string item) { item = Helper.MakeMd5(item); return _DataKey.ContainsKey(item); } #endregion } }
模擬測試代碼:
private static string _example = Guid.NewGuid().ToString(); private static UniqueCheck _uck = null; static void Main(string[] args) { _uck = UniqueCheck.GetInstance(); _uck.Start(); _uck.SetIsShowMsg(false); _uck.SetCacheMaxNum(20000000); _uck.SetBlockNumExt(1000000); _uck.SetTimeSpan(6000); _uck.AddItem(_example); Thread[] threads = new Thread[20]; for (int i = 0; i < 20; i++) { threads[i] = new Thread(AddInfo); threads[i].Start(); } Thread checkthread = new Thread(CheckInfo); checkthread.Start(); string value = Console.ReadLine(); checkthread.Abort(); for (int i = 0; i < 50; i++) { threads[i].Abort(); } _uck.Stop(); } static void AddInfo() { while (true) { _uck.AddItem(Guid.NewGuid().ToString()); } } static void CheckInfo() { while (true) { Console.WriteLine("開始時間:{0}...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); Console.WriteLine("插入結(jié)果:{0}", _uck.AddItem(_example)); Console.WriteLine("結(jié)束時間:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); //調(diào)整進程休眠時間,可以測試高并發(fā)的情況 //Thread.Sleep(1000); } }
測試截圖:
關于怎么在C#中實現(xiàn)請求唯一性校驗支持高并發(fā)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。