实现多租户系统的一点思考
2020年突發的新冠疫情,讓在線協同辦公在疫情期間成為了剛需。我們也從 2020 年的 2月3 日開始在家遠程辦公,直到四月份。協同辦公軟件一下子火爆了起來,釘釘、企業微信、特別是騰訊會議等都在疫情期間表現突出,呈現出井噴式的發展。
目前大部分的企業信息化都是私有化部署,局限于企業的內部網絡,無法實現遠程協同辦公,所以越來越多的 To B 企業逐步轉向 SaaS(Software-as-a-Service,軟件即服務),SaaS 最早是美國Salesforce公司(1999年創立)創造的新軟件服務模式。這家公司的市值在 2019 年已經超過1000億美元,國內現在還處在發展中階段,前景還是十分廣闊的。
要將傳統的私有化部署的軟件重構成支持 SaaS 模式,多租戶是一個邁不過去的坎,首先需要將系統改造成多租戶模式,然后再逐步實現計費、系統監控、用戶行為分析等功能。
我覺得多租戶的設計應該分為三個層面來進行討論,應用、數據庫和中間件。
應用
現在的項目或產品開發幾乎都是前后端分離的開發模式,應用層主要指的是 WebAPI ,WebAPI 的改造有兩種方式:
1、每個租戶部署一套 WebAPI、上層通過域名或 Url 地址的解析進行路由,當有新租戶注冊的時候就動態進行對應的 WebAPI 的部署,這種方式改造成本低,但運維成本高,不建議使用,如果時間緊,可以當過度階段的臨時方案。
2、所有的租戶共用一套 WebAPI ,在 WebAPI 中需要獲取到租戶信息(域名、Url參數、請求頭信息、Cookie 等),然后進行租戶信息配置的切換。有新租戶創建的時候無需進行新的 WebAPI 的創建,只需要初始化租戶基本信息即可。
在這種方式下,如果 Cluster1 的負載超過限度了,也要能夠進行動態切換,將其中的某些租戶切換到其他的 Cluester 中,如上圖。
在 WebAPI 的代碼實現上,可以參考 Abp 框架中多租戶的實現,這里給出一個簡化版本:
TenantConfiguration:租戶配置信息
[Serializable] public?class?TenantConfiguration {public?Guid?Id?{?get;?set;?}public?string?Code?{?get;?set;?}public?string?Name?{?get;?set;?}public?TenantStatus?TenantStatus?{?get;?set;?}public?string?DBConfig?{?get;?set;?}public?string?CacheConfig?{?get;?set;?}public?string?MQConfig?{?get;?set;?}public?string?MongoConfig?{?get;?set;?}public?TenantConfiguration(){TenantStatus?=?TenantStatus.Enable;}public?TenantConfiguration(Guid?id,?string?name):?this(){Id?=?id;Name?=?name;} }TenantStore:從緩存或數據庫中獲取租戶配置信息
public?interface?ITenantStore {TenantConfiguration?Find(string?code); } public?class?TenantStore?:?ITenantStore {public?TenantConfiguration?Find(string?code){//從緩存或數據庫進行租戶配置信息獲取throw?new?NotImplementedException();} }CurrentTenant:當前租戶類,用來存儲當前租戶信息,以及切換租戶
public?interface?ICurrentTenant {TenantConfiguration?Config?{?get;}IDisposable?Change(string?code); } ///?<summary> ///?當前租戶 ///?</summary> public?class?CurrentTenant:ICurrentTenant {public?ITenantStore?_tenantStore;public?CurrentTenant(ITenantStore?tenantStore){_tenantStore?=?tenantStore;}public?TenantConfiguration?_config;public?TenantConfiguration?Config?=>?_config;///?<summary>///?切換租戶///?</summary>///?<param?name="code"></param>///?<returns></returns>public?IDisposable?Change(string?code){TenantConfiguration?tenantConfig=?_tenantStore.Find(code);if?(tenantConfig?==?null){throw?new?Exception("Tenant?not?found");}if?(tenantConfig.TenantStatus?!=?TenantStatus.Enable){throw?new?Exception("Tenant?is?disabled?or?deleted");}return?new?DisposeAction(()?=>{_config?=?tenantConfig;});} }UrlTenantResolve:根據 Url 參數進行租戶解析
public?interface?ITenantResolve {string?Resolve(HttpContext?httpContext); } ///?<summary> ///? ///?</summary> public?class?UrlTenantResolve:ITenantResolve {public?string?Resolve(HttpContext?httpContext){return?httpContext.Request.QueryString.HasValue??httpContext.Request.Query["__tenant"].ToString():?null;} }MultiTenancyMiddleware:租戶中間件,關于在 dotNET Core 中自定義中間件可以參考《dotNET Core 3.X 請求處理管道和中間件的理解》
public?class?MultiTenancyMiddleware:?IMiddleware {protected?readonly?ITenantResolve?_tenantResolve;private?readonly?ICurrentTenant?_currentTenant;public?MultiTenancyMiddleware(ITenantResolve?tenantResolve,ICurrentTenant?currentTenant){_tenantResolve?=?tenantResolve;_currentTenant?=?currentTenant;}public?async?Task?InvokeAsync(HttpContext?context,?RequestDelegate?next){var?tenantCode?=?_tenantResolve.Resolve(context);if?(tenantCode?!=?_currentTenant.Config.Code){using?(_currentTenant.Change(tenantCode)){await?next(context);}}else{await?next(context);}await?next(context);} }數據庫
數據庫在這里指的是關系型數據庫,用來存儲業務數據,實現多租戶,就要對數據進行隔離,通常的數據隔離方式有三種模式:
1、完全隔離,每個租戶使用獨立數據庫;
2、部分共享,租戶共享一個數據庫,以 schema 或者 table 區分;
3、完全共享,租戶共享相同的數據庫表,以 tenant_id 進行區分
推薦使用第一種或第二種,隔離程度比較高,也比較容易做橫向擴展,如果是第三種,需要處理數據的隔離問題,需要處理單表大數據的問題等,對技術要求比較高。
中間件
除了數據庫,一個系統還需要依賴其他的一些中間件,比如緩存、消息隊列、文件存儲:
- 緩存:Redi 
- 消息隊列:RabbitMQ 
- 文件存儲:MongoDB 的 GridFS 
Redis
1、Redis 中使用數據庫的方式進行租戶隔離;
2、Redis 可以通過修改配置文件的方式進行數據庫的擴展,默認為 16 個;3、通過 Redis 分片集群的方式進行部署,可以進行橫向擴展;3、在 Redis 集群中,官方推薦節點數量不超過 1000 個,這個對于多租戶系統的前期來說應該是夠用了,如果到了租戶數量的爆發期,再進行架構的擴展,比如,不同的租戶路由到不同的 Redis 集群中。
RabbitMQ
在 Rabbitmq 有 vhost 機制,可以一個租戶創建一個vhost,通過 vhost 來進行租戶的隔離,目前還沒查詢到 vhost 是否有上限,需要做進一步驗證。
MongoDB
MongoDB 中主要使用 GridFS 來進行非結構化數據的存儲,通過創建數據庫的方式來進行租戶的隔離,而且 MongoDB 支持分片的集群部署方式,可以進行擴展橫擴展,在前期,一個 MongoDB 集群應該就夠用了。
最后
技術方案和架構沒有最好的,只有最適合的,符合當下的業務場景、團隊的技術能力就可以,然后要做的就是做 MVP (最小可行性產品),進而進行系統的改造。
希望本文對您有所幫助!
總結
以上是生活随笔為你收集整理的实现多租户系统的一点思考的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: WPF 右下角弹窗的简单实现
- 下一篇: C# 8.0 默认接口实现
