SaaS的可擴展性:如何使你的軟件不論初創企業還是跨國公司都能支援得到?

企業級軟件訂閲服務 (SaaS) 的開發 (3)

Photo by Alex Kotliarskyi on Unsplash


這篇文章繼續上一篇有關企業用軟件訂閲服務開發的探討,而這次談到可擴展性 (Scalability)。可擴展性就是當軟件服務的用量增加時,可以通過系統架構的調整去應付增加的用量。而對於企業軟件來說,用量增加也代表客戶增加,這裡會一同探討軟件服務會面對不同規模的客戶。

你負責的產品會賣給什麽樣的客戶,要看產品本身的市場定位,客戶可以大如跨國企業,也可小如初創公司。但是無論如何,其終究會遇到客戶規模差別很大的問題。

例如說你的產品是針對會計師設計的軟件,一個客戶可以是十多人的本地小公司,亦可能是過千人的跨國會計師樓。即使軟件本來只是針對小型公司的市場,但在產品發展的路途上,無論如何也有機會遇到大公司。大型公司能提出的豐厚條件下,你的企業/上司自然不會輕易放過這些機會。而如何讓你的軟件既能符合中小型公司的需要,也能滿足大公司的要求,就是問題所在。


根據常見的企業規模去區分,軟件的服務模式大致可以歸類為三大類別:

  • 雲端共享服務 (Sharing Service)
  • 雲端隔離服務 (Isolated Service)
  • On-Premise

雲端共享服務 (Sharing Service)

Photo by Samuel Zeller on Unsplash

共享服務正是多租戶模式 (Multi-tenancy)的部分所題到的資源共享設計,利用大量客戶去攤分服務的成本,令到單一客戶的成本可以降低。使用共享服務的客戶類型主要是初創/小型公司,他們不會投入大量資金去購買企業用軟件。在有選擇的情況下,他們多數會用較低價格或者免費的應用程式(例如他們傾向使用免費的 Google Docs,而不會購買 Microsoft Office)。共享服務能為客戶提供最實惠的軟件價格,但也因爲收費低廉甚至免費提供,共享服務的收益也會比較低

另外,即使是大型客戶,在決定購買軟件前,都會希望有試用版,來測試軟件否適合他們。雲端共享就是一個很適合提供試用版的地方。當客戶滿意你的軟件時,就有機會花真金白銀購買,並要求轉至下文所題及到的隔離服務 (Isolated Service),或者On-Premise 模式的服務。

共享服務如何應付流量與擴展

雲端共享服務的用量是根據客戶的數量而作出相應的增減,正常情況下 (產品的銷售理想時),客戶不斷增加,服務器便會達到滿載的水平。所以系統的可拓展性對於雲端共享服務是必要的。不過可幸的是,現今各大雲端服務供應商都發展成熟,雲端服務的擴展其實非常容易。服務擴展有兩個方向:垂直方向 (Veritical)水平方向 (Horizontal)

垂直方向 (Veritical):常稱爲Scale-up,增加服務器的處理性能以應付更多的用量,很簡單來說就是電腦太慢的話就升級硬件。現在的雲端服務 Scale-up 相當容易使用,基本上就是選擇你需要的硬件配置就可以,配置的變更不需要一分鐘就完成。

Azure Cloud Service Vertial Scaling

水平方向 (Horizontal):常稱爲Scale out,增加電腦數量去分擔處理流量。Scale-out 有一個必須的基本配備,就是網絡負載均衡器(NLB — Network Load Balancer),它可以依照服務的狀態,把請求派發到不同的服務器進行處理。

和 Scale-up 一樣,Scale-out 也很容易使用,用戶可以簡單到拉動 Scrollbar 就能控制服務器的數量。

Azure Cloud Service Horizontal Scaling

順便一提,Scale-out 的架構和之前所説到的多租戶模式 (Multi-tenancy) 並無衝突。只要能認清楚客戶的數據庫是哪一個的話,經由哪一臺服務器處理也沒有問題。

Scale-out with Multi-tenancy

自動調整 (Auto-scale)

其實現在的大型雲端服務供應商都有提供自動調整的服務,亦易於設定。根據用戶的設定,再按運行中的服務器使用量,雲端服務就會自動去增加或者減少服務器的數目。如下面的設定,當CPU的用量多於75%的時候就只都增加一臺服務,低於25%的話就自動減少一臺,最少一臺,最大十臺。

Azure VM Autoscale 

自動調整除了在客戶量增加的時候大派用場外,還可以在流量降低時,相對地減少服務器數量。企業軟件通常都是在工作時間内的用量比較大,工作時間以外的流量會變得非常小。假設工作時間大約是 09:00 — 18:00,餘下的時間就可以把服務器數量大減。服務器的價格是按照運行時間(分鐘)來算的,所以這樣可以進一步縮減服務器的成本。

數據庫

使用共享雲端服務的用戶大都是中小型公司,所以不太可能會發生數據庫應付不了的用戶。比較容易發生的倒是數據庫服務器應付不了太多客戶的問題。這裏的處理方法不難。例如我們訂明一個數據庫服務器最多只會運行100個客戶的數據庫。當該數據庫超過100個客戶時,我們就另外他添加一個數據庫服務器去處理。每一個數據庫服務器再根據他自身的用量來進行 Scale-up / down,以作調整服務器的性能和成本。

儲存空間 / 其他雲端服務

根據實質情況,你還可能使用很多不同的雲端服務,如檔案上載儲存服務 (e.g. Blob Storage,Amazon S3) ,或者處理媒體檔案的服務 (e.g. Azure Media Service, Amzon Media Service,etc) 。這些雲端服務等同於微服務 (MicroService) ,基本上不會出現遠超負荷的問題,因為它們的設計是要服務所有雲端的用戶。所以在考慮服務器負荷的時候,就毋須煩惱這類雲端服務。不過,這些服務的用量和價錢都成正比,所以在控制成本方面,就要多花時間留意。


隔離服務 (Isolated Service)

隔離服務是上一篇提到的 單一住戶模式(Single-Tenacy),使用隔離服務的公司主要是中型至大型公司,這些公司會按自己的需要,投放資源來購買企業軟件。這些公司爲求確保他們的服務質素,往往願意付出相對高的價錢,要求軟件供應者為他們在雲端設立專用服務器,並分割一定量的資源在專門服務這些公司

而在隔離服務的範圍内,可擴展性的重要性反而不大,因爲客戶的用量基本上都是固定的,亦比較容易估算。通常不會出現突然用量大增,而導致系統超出負荷的情況發生。即使需要做到自動擴展,做法也和上文提到的共享服務相差不多。而隔離服務的優劣就在講單一租戶模式的時候就提到過,這裡就不重複了。


On-Premise

當公司規模去到大公司/跨國企業時,他們會非常重視資料保密,也不會隨意把企業的資料放上公共的雲端服務。他們會要求把軟件發佈到他們公司的私人雲端 (Private Cloud),或者他們的數據中心 (Data Center) 。其實這就是回歸到傳統的服務器架構,確實有點脫離了現代雲端服務其攤分成本的概念。但從另外一個方向來看,這些大公司往往已在使用大量不同的系統,所以他們實際上是和自己公司的其他軟件去攤分,而不是和別的公司去攤分成本。

On-Premise 已經不是軟件訂閱服務所應討論的範圍,在此不贅。不過如果你的軟件服務是針對企業用戶的話,On-Premise 是必然會遇到的課題。關於 On-Premise,還待日後再談。


總結

可擴展性對於產品的發展起到很關鍵的作用。基於軟件訂閲服務攤分成本的概念下,你的服務越吸納更多客戶,平均服務器成本也會越來越低,價格有下調的空間,服務也就會更有吸引力。

接下來大家討論和可擴展性不可分離的 高可用性 (High availbility)

更多資訊可以留意 ONES Publication 定期發佈的文章,亦可以聯絡我們,我們的網址是: https://ones.software

]]>

SaaS的多租戶模式(Multi-tenancy)實踐:.NET Core + Entity Framework Core

企業級軟件訂閲服務 (SaaS) 的開發 (2)
多租戶模式 (Multi tenancy)

我們於上一篇提過關於多租戶模式 (Multi tenancy) 的各種好處,今日我們就來談談如何去實際架構出一個多用戶模式的服務器架構,並能實現客戶獨立穩健、容易維護的特點。


多用戶模式的實現方法可以分爲幾種:

  1. 服務器使用同一個域名 (Domain Name),在每一個請求 (Request) 裏面都加上一個 TenantId 的參數作分辨。
  2. 服務器使用同一個域名,然後以不同的用戶ID 來作區分。
  3. 服務器接受不同域名,每個客戶都給予他一個專屬域名,並以域名去區分

這裏會示範實現第三個方案,其優點有:

  • 可以用不同子域名 (Sub-domain) 去分給不同用戶,例如 tenantA.example.com,tenantB.example.com。如果請求裏面加有用戶的名字的話,能夠讓用戶感覺方案是他們專有的服務。
  • 如果客戶自己公司有想用的域名的話,也可以使用。只要在他們的域名服務器 (Domain Name Server)上,把域名指到我們服務器的地址便可。
  • 對服務器來説,只要小心處理,用哪個方案差別都不大,但是在瀏覽器 (Browser)上差別就很大。方案 1 和 2 都是使用同一個域名進入,即使所有客戶不同,他們在瀏覽器上的 Cookie 或者 Local Storage 的空間都是共通。雖然説在實際環境中,同一臺電腦登入不同客戶的情況非常罕見。但一旦登入不同客戶,就很容易發生資料有衝突。這樣的話,就不容易達到客戶獨立這個條件。相反方案 3的話,每一個客戶都使用不同的域名,在瀏覽器上的的存取空間也會是獨立的,就不會發生以上的問題。

閒話休提,要開始實際示範了。這個示範架構的概念是「靠頂端判斷租戶資料,再於末端去進行分類處理」。這裏我們會利用 .Net Core + Entity Framework Core 來實現一個多租戶模式的例子。這個例子要符合三個要求:

  1. 根據不同請求域名 (Request Host Name),返回對應的客戶資料,如名字;
  2. 根據不同域名,存取不同的數據庫 (Database)
  3. 根據不同域名,上載和下載檔案(File) 進去不同的文件夾 (Folder)

實際操作

1.首先打開 Visual Studio,新增一個 .Net Core 的 Project,這裏我們用 2.2版本。

2.定義 Tenant (租客),裏面存放了該客戶的資料,如客戶名字數據庫的地址 (ConnectionString),存取檔案的文件夾,和這個客戶接受的所有域名。

3.定義TenantContext,用來緩存當前的Scope現在是哪一個租戶。

4.定義TenantResolver,來根據不同的域名去搜尋自己屬於哪一個租戶。這裏爲了方便示範,所有直接加入了兩個客戶的資料在這裏,但正式應該要讀取一個額外儲存所有租戶資料的檔案,或者是連接到一個遠端的租戶管理服務器,來讀取租戶資料。

這裏的例子,如果是經過 localhost:5000 進來的就是客戶A,localhost:5001進來的就是客戶B。

5. 到這裏爲止,就有了最基本的構成了。然後就是如何去判斷請求是屬於哪一個租戶,這裏使用中間件 (Middleware),來定義一個HttpTenantResolveMiddleware 去判斷。

在 HttpContext 裏面能夠找回當前 Request 的 HostName,並通過TenantResolver 裏是屬於哪一個 Tenant,最後放進去 TenantContext 裏面便可。

6.在 startup.cs,把之前定義的 Class 都加進去依賴注入,TenantResolver 用 Singleton,其他的用 Scope。

這裏有一個巧妙的地方,就是把 TenantContext 裏面的 Tenant 再放進Scope裏面,這樣就可以更直接的在拿到當前 Scope 的租戶。

7.加入 HttpTenantResolveMiddleware 進流水綫 (Pipeline)。判斷 Tenant這個流程,越早階段做越好,所以這裏直接放到第一位。

8. 這楊就可以開始簡單的測試一下。首先在 launchSetting 裏面加上兩個Tenant 的 Url。

定義一個測試的 Controller,裏面直接 注入 (inject) Tenant 進去就可以了。

然後就可以看到測試結果。同一條路徑,同一個服務器,但經過不同域名的請求,返回不同租戶的資料。

到這裏,基本的租戶分類就完成了。


數據庫存取  — Entity Framework Core

接著來示範一些對 Db 的實際操作,這裏用 SqlServer 來做示範。

1.要加入三個 Entity Framework Core 需要的 Nuget Project。

2.我們就定義一個簡單的 TenantDbContext 的數據庫,裏面有一個 Key value pair 的表。

3.在 startup.cs 裏面注入 TenantDbContext。平時的 Entity Framework 教學裏,都會在這裏加入數據庫的地址 (connectionString) 加進去,但我們就不在這裏加。

4. 再來一個小竅門,在 TenantDbContext 也注入 Teannt。覆蓋掉原來的 OnConfiguring,在這裏用 Tenant 裏面的 ConnectionString 去指定數據庫路徑。

5.然而,如果現在直接運行 add-migration 去添加 MIgration 的話,就會發生錯誤。因爲在沒有請求的時候,是不會知道現在是屬於哪個租戶,和應該連去哪個 DB。

但其實這個問題很容易解決。只要加一個 Class 去Implement IDesignTimeDbContextFactory<TenantDbContext> 就可以。在運行 add-migration的時候,就會根據這裏去創建 TenantDbContext,只要在這裏給一個假的住戶資料進去,add-migraiton 的時候就可以靠這個Tenant的數據庫路徑,去計算 Migration。

Migration 成功做出來了!

6.然後就到建立的數據庫的時候。在startup.cs configure 的最後階段,對每一個 Tenant 都創造一個新的 Scope,將該 Tenant 放進 TenantContext,再運行 Migration 就可。

運行完後看一看 SSMS,兩個客戶的數據庫都應能成功創造出來。

7.開始實際測試,做一個測試數據庫的 Controller。如果熟悉 Entity Framework的人就會發現,在這裏對 Entity Framework 的用法和平時的用法沒有任何分別

測試開。預先將設了客戶A的 hello=I’m Tenant A, 然後就可以看到不同的域名拿到的結果是不一樣,兩個數據庫是完全分開的。

檔案存取

1.這個做法也很簡單,做一個 TenantFileService,也是一開始就注入Tenant。這裡都用 Tenant 裏面的 FileDirectory 作爲路徑的基礎。

2. 在startup.cs的加到 Dependency Injection 裏面。

3.實際測試,和數據庫的測試一樣,也是 Controller 注入TenantFileService,直接使用便可。

大家可以看到經過不同的域名上載的檔案,會分別放進不同的文件夾裏面。


總結

上一篇裏面提到過,儘管是在多租戶模式(Multi Tenancy)的環境下, 用戶資料的安全和保密還是最重要的考慮。而這個例子的示範的系統架構,就正正是爲了達到這個目的。

裏面提到過,儘管是在多租戶模式(Multi Tenancy)的環境下, 用戶資料的安全和保密還是最重要的考慮。而這個例子的示範的系統架構,就正正是爲了達到這個目的。

當一個系統不斷開發期間,開發者不可能寫每一個功能的時候,都要顧慮到如何去區分現在是屬於哪個一租戶,因爲這樣很容易會發生人爲錯誤,也會令到代碼混亂,難以維護。

而這個結構的好處是,在最頂端的階段已經確立好這個請求是屬於那個一租戶,然後到了最末端的 IO 的部分,根據最頂端的判斷好的租戶資料去決定如何存取,而中間的邏輯部分就完全不用顧慮到租戶的問題,就當普通的服務器去開發功能便可。

當然實際的多租戶模式的不止這麽簡單,還有很多範圍要顧及,如背景工作 (Background Job)緩存 (Cache)WebSocket日誌文件 (Logging) 等等。但只要牢記這這個例子的概念 「靠頂端判斷租戶資料,再於末端去進行分類處理」,那無論功能如何增加擴大,也能做好維護工作,而不會發生用戶資料混亂的事情發生。

大家可以上我們的Github,去下載這個 project 實際來試試看。 https://github.com/ones-software/dotnet-core-multi-tenancy-sample


ONEs software: https://ones.software/hk/bookings-one/

]]>