使用.NET Core搭建分散式音頻效果處理服務(四)選擇垂直擴展還是水平擴展?

来源:https://www.cnblogs.com/SteveLee/archive/2018/08/14/9477780.html
-Advertisement-
Play Games

眾所周知垂直擴展是提升單機的性能的方式,比如提升雙路、四路的CPU運算能力,加大記憶體,更換速度更快的SSD,或者從代碼根本上進行優化和性能提升。水平擴展是提供多台多種伺服器分離單機性能的方式,比如集群,主從,隊列,負載平衡等等。 白話的垂直擴展 現在伺服器都是雲伺服器,單純從單機的硬體性能提升整體性 ...


眾所周知垂直擴展是提升單機的性能的方式,比如提升雙路、四路的CPU運算能力,加大記憶體,更換速度更快的SSD,或者從代碼根本上進行優化和性能提升。水平擴展是提供多台多種伺服器分離單機性能的方式,比如集群,主從,隊列,負載平衡等等。

 

 白話的垂直擴展

現在伺服器都是雲伺服器,單純從單機的硬體性能提升整體性能,可能已經不太適用,而從代碼上,其實還有些功課可以做,即使不多:

  1. 優化多線程協調模式,優化多線程下資源共用問題,以免出現奇怪的運行時錯誤。
  2. 改同步為非同步:此方法提升的是吞吐率,性能並不能提升,不過對於客戶端響應也算是件好事吧。
  3. 使用磁碟預讀模式,極小幅度提升IO性能。
  4. 使用單機任務隊列,強制任務有序進行:此方法在單機上不會提升性能,甚至會減少原本的單機吞吐率,但是卻能保證任務在同一時刻的完整性。

我們先從垂直擴展中壓榨單機的性能,同時還要保證穩定性,甚至穩定性比單機極限性能更加重要,為何?因為多線程(web伺服器都是多線程模型)資源互斥問題,會讓你查找問題的時候抓狂(當然,如果你要訪問的資源只是單個,就另當別論了)。因此,很多時候我們通常會加鎖來避免這類事情發生(鎖的問題和功能我們這裡不討論),雖然犧牲了性能,但卻換來了每次高請求所帶來的穩定性。

其次,我們知道,web伺服器都屬於多線程模型,這樣設計的目的是為了提高該伺服器的整體吞吐量(不同伺服器語言採用不同的線程開闢模式,例如java使用的是系統級的線程),當一個線程正在接近滿負荷的處理當前的任務,緊接著馬上又來一個請求(系統不會因為當前正在運行任務而終止新的請求),那麼將是雪上加霜的,多個任務同時長時間在搶占同一個CPU資源,無疑是對整體影響甚大的。

言歸正傳,我們在單機上面針對這類問題,既要儘可能的減少處理時間,又要絕對保證整體運行期間的穩定性(後期會介紹如何使用熔斷機制提升多台伺服器系統的整體穩定性)。

上一節,我們已經創建了一個同步的介面,下麵我們將這個介面稍作改動,使其成為包含非同步任務方法的介面,整體代碼就不貼上來了,以免影響篇幅

  1. 如果你喜歡手動創建與管理任務,那麼你可以new一個Task<TResult>實例。
  2. 如果你喜歡讓系統為你管理該任務狀態,那麼你可以Task.Factary.StartNew來新建一個實例。

 

 白話的水平擴展

當一條街道上的小區越來越多,用水越來越大,而住戶反應水壓卻越來越小,你是考慮增加主管道通水量大小、還是考慮增加每個小區的增壓泵的功率、還是考慮增加主管道的數量(目前筆者小區就遇到水壓不夠的情況)。

在軟體工程項目中,其實伺服器TPS跟水壓是同一個概念。

  1. 加大主水管道(如同提升CPU、記憶體)始終會有一個極限;
  2. 增加每個小區增壓泵的功率(如同客戶端使用大量的輪詢,可主管道出口就只有那麼點點量)始終要求比得到的多得多;

因此換句話說:

  1. 增加伺服器數量(畢竟伺服器比自來水廠容易建設:-)),提升管道入口的處理能力;
  2. 增加不同伺服器類型,例如隊列伺服器,負載伺服器,緩存伺服器等等中間伺服器,分攤和分離不同功能分到而行,如同主管道的分流閥,節流閥,增壓泵等等;
  3. 增加帶寬(這個是肯定的,提升TPS帶寬肯定也是主要的);

 

一:點對點——原裝

當然,如果介面已經成為了非同步模式(本質其實是提前返回請求,但並沒有返回請求所處理的結果),那麼還需要一個介面來告訴客戶端處理的結果,客戶端通過該介面的輪詢獲得實時的結果。

在筆者介紹的這個服務中,流程架構如下:

  非常簡單的點對點模式,用戶請求一次,等待伺服器處理響應完成後釋放,所有內容均採用同步方式進行,得到結果是:

 非常簡單的點對點模式,用戶請求一次,等待伺服器處理響應完成後釋放,所有內容均採用同步方式進行,得到結果是:

用戶等待時間 = 伺服器處理時間

如果用戶上傳10秒,而伺服器處理需要4秒,那麼這個等待對於用戶來說,是極為煎熬的。

也許聰明的你會說,在客戶端給個友好的提示,比如讓一個“風火輪”不停的轉動,當處理完成後隱藏掉。的確,這從另一個角度上看確實也行得通(比如成本因素),但我們不討論用戶的視覺和等待等感覺上的東西,只討論從技術上如何讓這個響應時間更快,能快到幾乎讓用戶察覺不到。

 

二:使用並行任務——小幅提升

單機並行模式大家應該都明白,畢竟現在CPU都是多核的了,幹嘛要讓其他CPU閑著呢,不管是JAVA還是C#,目前主流語言都可以完美的執行並行任務(python開多進程其實也算),各種語法請自行Google,既然文章標題是Net,那麼筆者就少量的複製一下C#的代碼。

Parallel.Invoke(() => { },() => { });

 

哇撒,真的很少,就是C#中的一個並行執行的語句而已,自己需要並行執行的代碼放入花括弧中就行,換成流程結構圖如下:

畫的很搓,歡迎拍磚。

筆者採用的CPU是I7-2700K,並行任務狀態確實使用了起來,但減少時間卻只有1秒,很不可思議,或許是筆者的代碼優化不夠好吧(並行原理和理想結果為何有出入請自行Google),所以就不毛遂自薦的貼上來了:-),但是這個3秒時間我會跟他死磕到底——用戶不能等。

 

三:分離用戶請求和耗時處理——非同步

 當朋友們看到這裡的時候,或許心裡早就想到用非同步的方式來實現C/S的介面請求了,對,但我們還是需要走一下流程,梳理一下思路。請繼續接著看。

非同步其實就是多線程,只是目前由於高級語言的發展,已經將線程的難點給隱藏掉了,在一個請求主線程中,新建一個非同步線程(或任務),分離主線程的長時間處理耗時,將這塊難啃的骨頭交給子線程去做,自己只管輕鬆的執行到return,是的確很舒服哦(筆者也夢想擁有這樣的碼磚方式,o(∩_∩)o 哈哈),new一個線程我們不做介紹,畢竟他的管理模式是純手動的、並且是複雜的,我們只介紹new一個任務來分離主線程之間的關聯。

正如之前提到,微軟巴巴已經將這種模型給封裝好了,只需要在介面處理函數內、將處理模塊塞進Task中即可,不用再去new一個線程、管理這個線程的狀態、什麼時候調度、什麼時候阻塞等等一些較底層的操作。萬事有好必有壞,多線程模型創建是很簡單了,相應的實現細節對於很多入門的朋友就看不懂了。

不過,當一個對外介面(或者內部函數)採用非同步模式,那麼調用端也需要進行輪詢(非同步同步無所謂,看調用如何實現)處理結果,這個模式相比原來常規同步複雜許多,需要建立任務、執行任務、存儲任務狀態和結果等等,不廢話,上圖:

通過將“伺服器處理耗時”進行分離,請求主線程只需要將相應的參數傳遞給子線程(或任務),主線程就直接返回到客戶端,如果忽略子任務之前的邏輯時間複雜度,完全可以達到瞬間返回到客戶端,具體時長根據不同的平臺和架構不同而不同,正如之前國外有人對NET CORE和GO進行過空業務響應對比(具體鏈接得找找),在請求數高於100W(包括併發)和沒有任何邏輯代碼的前提下,直接請求某個介面,NET CORE只比GO慢了近40ms(相同單機)。這樣的性能還是非常看好的。

另外,如果處理時間過長,而且子任務不能及時返回,那將產生越來越多的任務阻塞,畢竟一個CPU是有極限的,並且伴隨著或多或少的運行時錯誤,而這種錯誤是最讓我們程式員頭疼的,因此,這時我們需要加入單機隊列,來限制和防止處理和請求達到瞬時波峰,文章結尾提供一份單機隊列的代碼供大家參考:

實際證明,這樣對於用戶來說,是瞬間的,不用等待的,極大的提升了用戶體驗。不過呢,如果請求數越多,那麼越後進來的請求,等待的時間將越長,對於客戶端輪詢的時間也將變得更長。

輪詢時間 = 請求數(單機隊列數) * 單個處理耗時時間

好像比原來的點對點更糟糕了,實際我們根據這個架構進行擴展,將得到更好的體驗,請繼續接著看。

 

四:讓多台機器一起工作吧——集群

先看張圖:

哇塞,一下子變得這麼複雜,好捉急啊。其實並不難理解,我們來看一看做了哪些變化:

  1. 單機的隊列擴展為了使用伺服器做隊列集群;
  2. 增加調度任務;
  3. 將多個處理服務分配到多台機器上運行;
  4. 單機緩存增加到緩存集群;

其他也就沒什麼花頭了。當請求任務過高,放入隊列中,分離前級請求和後級處理,後級處理伺服器的數量將直接影響整個平臺的非同步處理時間。如果非要對比單機模式,性能是隨處理伺服器的數量增加而提高的。下一節我們將詳細討論這套架構方案。

 

感謝閱讀!

 

(附上單機隊列的實現,僅供參考)

  1 /// <summary>
  2 /// 非同步任務隊列
  3 /// </summary>
  4 public class AsyncTaskQueue : IDisposable
  5 {
  6     private bool _isDisposed;
  7     private readonly ConcurrentQueue<AwaitableTask> _queue = new ConcurrentQueue<AwaitableTask>();
  8     private Thread _thread;
  9     private AutoResetEvent _autoResetEvent;
 10 
 11     /// <summary>
 12     /// 非同步任務隊列
 13     /// </summary>
 14     public AsyncTaskQueue()
 15     {
 16         _autoResetEvent = new AutoResetEvent(false);
 17         _thread = new Thread(InternalRuning) {IsBackground = true};
 18         _thread.Start();
 19     }
 20 
 21     private bool TryGetNextTask(out AwaitableTask task)
 22     {
 23         task = null;
 24         while (_queue.Count > 0)
 25         {
 26             if (_queue.TryDequeue(out task) && (!AutoCancelPreviousTask || _queue.Count == 0)) return true;
 27             task.Cancel();
 28         }
 29 
 30         return false;
 31     }
 32 
 33     private AwaitableTask PenddingTask(AwaitableTask task, int maxQueueCount = 1000)
 34     {
 35         lock (_queue)
 36         {
 37             if (_queue.Count >= maxQueueCount)
 38             {
 39                 throw new Exception($"超出最大隊列數量,maxQueueCount={maxQueueCount}");
 40             }
 41 
 42             Debug.Assert(task != null);
 43             _queue.Enqueue(task);
 44             _autoResetEvent.Set();
 45         }
 46 
 47         return task;
 48     }
 49 
 50     private void InternalRuning()
 51     {
 52         while (!_isDisposed)
 53         {
 54             if (_queue.Count == 0)
 55             {
 56                 _autoResetEvent.WaitOne();
 57             }
 58 
 59             while (TryGetNextTask(out var task))
 60             {
 61                 if (task.IsCancel) continue;
 62 
 63                 if (UseSingleThread)
 64                 {
 65                     task.RunSynchronously();
 66                 }
 67                 else
 68                 {
 69                     task.Start();
 70                 }
 71             }
 72         }
 73     }
 74 
 75     /// <summary>
 76     /// 是否使用單線程完成任務.
 77     /// </summary>
 78     public bool UseSingleThread { get; set; } = true;
 79 
 80     /// <summary>
 81     /// 自動取消以前的任務。
 82     /// </summary>
 83     public bool AutoCancelPreviousTask { get; set; } = false;
 84 
 85     /// <summary>
 86     /// 執行任務
 87     /// </summary>
 88     /// <param name="action"></param>
 89     /// <param name="maxQueueCount"></param>
 90     /// <returns></returns>
 91     public AwaitableTask Run(Action action, int maxQueueCount = 1000)
 92         => PenddingTask(new AwaitableTask(new Task(action, new CancellationToken(false))), maxQueueCount);
 93 
 94     /// <summary>
 95     /// 執行任務
 96     /// </summary>
 97     /// <typeparam name="TResult"></typeparam>
 98     /// <param name="function"></param>
 99     /// <param name="maxQueueCount"></param>
100     /// <returns></returns>
101     public AwaitableTask<TResult> Run<TResult>(Func<TResult> function, int maxQueueCount = 1000)
102         => (AwaitableTask<TResult>) PenddingTask(new AwaitableTask<TResult>(new Task<TResult>(function)),
103             maxQueueCount);
104 
105 
106     /// <inheritdoc />
107     public void Dispose()
108     {
109         Dispose(true);
110         GC.SuppressFinalize(this);
111     }
112 
113     /// <summary>
114     /// 析構任務隊列
115     /// </summary>
116     ~AsyncTaskQueue() => Dispose(false);
117 
118     private void Dispose(bool disposing)
119     {
120         if (_isDisposed) return;
121         if (disposing)
122         {
123             _autoResetEvent.Dispose();
124         }
125 
126         _thread = null;
127         _autoResetEvent = null;
128         _isDisposed = true;
129     }
130 
131     /// <summary>
132     /// 可等待的任務
133     /// </summary>
134     public class AwaitableTask
135     {
136         private readonly Task _task;
137 
138         /// <summary>
139         /// 初始化可等待的任務。
140         /// </summary>
141         /// <param name="task"></param>
142         public AwaitableTask(Task task) => _task = task;
143 
144         /// <summary>
145         /// 任務的Id
146         /// </summary>
147         public int TaskId => _task.Id;
148 
149         /// <summary>
150         /// 任務是否取消
151         /// </summary>
152         public bool IsCancel { get; private set; }
153 
154         /// <summary>
155         /// 開始任務
156         /// </summary>
157         public void Start() => _task.Start();
158 
159         /// <summary>
160         /// 同步執行開始任務
161         /// </summary>
162         public void RunSynchronously() => _task.RunSynchronously();
163 
164         /// <summary>
165         /// 取消任務
166         /// </summary>
167         public void Cancel() => IsCancel = true;
168 
169         /// <summary>
170         /// 獲取任務等待器
171         /// </summary>
172         /// <returns></returns>
173         public TaskAwaiter GetAwaiter() => new TaskAwaiter(this);
174 
175         /// <summary>Provides an object that waits for the completion of an asynchronous task. </summary>
176         [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true, Synchronization = true)]
177         public struct TaskAwaiter : INotifyCompletion
178         {
179             private readonly AwaitableTask _task;
180 
181             /// <summary>
182             /// 任務等待器
183             /// </summary>
184             /// <param name="awaitableTask"></param>
185             public TaskAwaiter(AwaitableTask awaitableTask) => _task = awaitableTask;
186 
187             /// <summary>
188             /// 任務是否完成.
189             /// </summary>
190             public bool IsCompleted => _task._task.IsCompleted;
191 
192             /// <inheritdoc />
193             public void OnCompleted(Action continuation)
194             {
195                 var This = this;
196                 _task._task.ContinueWith(t =>
197                 {
198                     if (!This._task.IsCancel) continuation?.Invoke();
199                 });
200             }
201 
202             /// <summary>
203             /// 獲取任務結果
204             /// </summary>
205             public void GetResult() => _task._task.Wait();
206         }
207     }
208 
209     /// <summary>
210     /// 可等待的任務
211     /// </summary>
212     /// <typeparam name="TResult"></typeparam>
213     public class AwaitableTask<TResult> : AwaitableTask
214     {
215         /// <summary>
216         /// 初始化可等待的任務
217         /// </summary>
218         /// <param name="task">需要執行的任務</param>
219         public AwaitableTask(Task<TResult> task) : base(task) => _task = task;
220 
221 
222         private readonly Task<TResult> _task;
223 
224         /// <summary>
225         /// 獲取任務等待器
226         /// </summary>
227         /// <returns></returns>
228         public new TaskAwaiter GetAwaiter() => new TaskAwaiter(this);
229 
230         /// <summary>
231         /// 任務等待器
232         /// </summary>
233         [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true, Synchronization = true)]
234         public new struct TaskAwaiter : INotifyCompletion
235         {
236             private readonly AwaitableTask<TResult> _task;
237 
238             /// <summary>
239             /// 初始化任務等待器
240             /// </summary>
241             /// <param name="awaitableTask"></param>
242             public TaskAwaiter(AwaitableTask<TResult> awaitableTask) => _task = awaitableTask;
243 
244             /// <summary>
245             /// 任務是否已完成
246             /// </summary>
247             public bool IsCompleted => _task._task.IsCompleted;
248 
249             /// <inheritdoc />
250             public void OnCompleted(Action continuation)
251             {
252                 var This = this;
253                 _task._task.ContinueWith(t =>
254                 {
255                     if (!This._task.IsCancel) continuation?.Invoke();
256                 });
257             }
258 
259             /// <summary>
260             /// 獲取任務結果
261             /// </summary>
262             /// <returns></returns>
263             public TResult GetResult() => _task._task.Result;
264         }
265     }
266 }
View Code

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 教材:《彙編語言》 王爽 第三版 Charpter 1. 基礎知識 1.1 : 機器語言 1.2 : 彙編語言的產生 1.3 : 彙編語言的組成 1.4 : 存儲器 1.5 : 指令和數據 1.6 : 存儲單元 1.7 : CPU對於存儲器的讀寫 1.8 : 地址匯流排 1.9 : 數據匯流排 1.10 ...
  • JMM簡介 Java Memory Model簡稱JMM, 是一系列的Java虛擬機平臺對開發者提供的多線程環境下的記憶體可見性、是否可以重排序等問題的無關具體平臺的統一的保證。(可能在術語上與Java運行時記憶體分佈有歧義,後者指堆、方法區、線程棧等記憶體區域)。併發編程有多種風格,除了CSP(通信順序 ...
  • 一.標識符 標識符:在java程式中,有些名字是我們自己定義的,那麼這些我們自己定義的名字就叫做自定義的標識符 標識符的命名規則: 1.標識符是由字母(a-z A-Z)、數字、下劃線(_)、美元符號($)組成的 2.標識符不能以數字開頭 3.標識符是嚴格區分大小寫的 4.標識符是沒有長度限制的 5. ...
  • 在C#中消息有兩個指向,一個指向Message,一個指向INotify。這裡主要講INotify。 INotify也有人稱之為[通知],不管叫消息還是通知,都是一個意思,就是傳遞信息。 消息的定義 INotify消息其實是一個介面,介面名叫INotifyPropertyChanged。介面定義如下: ...
  • 持續集成配置之Nuget Intro 本文是基於微軟的 VSTS(Visual Studio Team Service) 做實現公眾類庫的自動打包及發佈。 之前自己的項目有通過 Github 上的 Travis 和 Appveyor,這次主要是用 VSTS 來做的,對比 appveyor 和 vst ...
  • 概要 相信很多朋友在程式生涯中,或多或少都會遇到處理媒體流的需求,而且是採用S端處理,排除代碼上課優化的極限,仍然還是需要很長的時間時,比如: 1:百度網盤在播放視頻的時候,如非VIP會員還需要更長甚至直接斷開流; 2:任何直播視頻在轉碼的時候,不論是否VIP,都會有段緩衝時間,已至於觀看者無法達到 ...
  • 什麼是NoSql NoSQL(Not Only SQL),泛指非關係型的資料庫,是對不同於傳統的關係型資料庫的資料庫管理系統的統稱,強調Key-Value Stores和文檔資料庫的優點。為瞭解決大規模數據集合多重數據種類帶來的挑戰而興起的資料庫。有著模式自由,逆規範化,多分區存儲,彈性可擴展,多副 ...
  • 使用ILMerge工具,將C#項目debug目錄下的exe及其依賴的dll文件打包成一個exe文件,直接雙擊就可運行。 使用工具: ILMerge :http://www.microsoft.com/en-us/download/details.aspx?id=17630 ILMerge-GUI:h ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...