<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實現(xiàn)多租戶系統(tǒng)的一點思考

          共 8579字,需瀏覽 18分鐘

           ·

          2021-05-20 06:18

          2020年突發(fā)的新冠疫情,讓在線協(xié)同辦公在疫情期間成為了剛需。我們也從 2020 年的 2月3 日開始在家遠程辦公,直到四月份。協(xié)同辦公軟件一下子火爆了起來,釘釘、企業(yè)微信、特別是騰訊會議等都在疫情期間表現(xiàn)突出,呈現(xiàn)出井噴式的發(fā)展。

          目前大部分的企業(yè)信息化都是私有化部署,局限于企業(yè)的內(nèi)部網(wǎng)絡,無法實現(xiàn)遠程協(xié)同辦公,所以越來越多的 To B 企業(yè)逐步轉向 SaaS(Software-as-a-Service,軟件即服務),SaaS 最早是美國Salesforce公司(1999年創(chuàng)立)創(chuàng)造的新軟件服務模式。這家公司的市值在 2019 年已經(jīng)超過1000億美元,國內(nèi)現(xiàn)在還處在發(fā)展中階段,前景還是十分廣闊的。

          要將傳統(tǒng)的私有化部署的軟件重構成支持 SaaS 模式,多租戶是一個邁不過去的坎,首先需要將系統(tǒng)改造成多租戶模式,然后再逐步實現(xiàn)計費、系統(tǒng)監(jiān)控、用戶行為分析等功能。

          我覺得多租戶的設計應該分為三個層面來進行討論,應用、數(shù)據(jù)庫和中間件。

          應用

          現(xiàn)在的項目或產(chǎn)品開發(fā)幾乎都是前后端分離的開發(fā)模式,應用層主要指的是 WebAPI ,WebAPI 的改造有兩種方式:

          1、每個租戶部署一套 WebAPI、上層通過域名或 Url 地址的解析進行路由,當有新租戶注冊的時候就動態(tài)進行對應的 WebAPI 的部署,這種方式改造成本低,但運維成本高,不建議使用,如果時間緊,可以當過度階段的臨時方案。

          2、所有的租戶共用一套 WebAPI ,在 WebAPI 中需要獲取到租戶信息(域名、Url參數(shù)、請求頭信息、Cookie 等),然后進行租戶信息配置的切換。有新租戶創(chuàng)建的時候無需進行新的 WebAPI 的創(chuàng)建,只需要初始化租戶基本信息即可。

          在這種方式下,如果 Cluster1 的負載超過限度了,也要能夠進行動態(tài)切換,將其中的某些租戶切換到其他的 Cluester 中,如上圖。

          在 WebAPI 的代碼實現(xiàn)上,可以參考 Abp 框架中多租戶的實現(xiàn),這里給出一個簡化版本:

          TenantConfiguration:租戶配置信息

          [Serializable]
          public class TenantConfiguration
          {
              public Guid Id { getset; }

              public string Code { getset; }

              public string Name { getset; }

              public TenantStatus TenantStatus { getset; }

              public string DBConfig { getset; }

              public string CacheConfig { getset; }

              public string MQConfig { getset; }

              public string MongoConfig { getset; }

              public TenantConfiguration()
              {
                  TenantStatus = TenantStatus.Enable;
              }

              public TenantConfiguration(Guid id, string name)
                  : this()

              {
                 
                  Id = id;
                  Name = name;
              }
          }

          TenantStore:從緩存或數(shù)據(jù)庫中獲取租戶配置信息

          public interface ITenantStore
          {
              TenantConfiguration Find(string code);
          }
          public class TenantStore : ITenantStore
          {
              public TenantConfiguration Find(string code)
              {
                  //從緩存或數(shù)據(jù)庫進行租戶配置信息獲取
                  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:根據(jù) Url 參數(shù)進行租戶解析

          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 MultiTenancyMiddlewareIMiddleware
          {
              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);
              }
          }

          數(shù)據(jù)庫

          數(shù)據(jù)庫在這里指的是關系型數(shù)據(jù)庫,用來存儲業(yè)務數(shù)據(jù),實現(xiàn)多租戶,就要對數(shù)據(jù)進行隔離,通常的數(shù)據(jù)隔離方式有三種模式:

          1、完全隔離,每個租戶使用獨立數(shù)據(jù)庫;

          2、部分共享,租戶共享一個數(shù)據(jù)庫,以 schema 或者 table 區(qū)分;

          3、完全共享,租戶共享相同的數(shù)據(jù)庫表,以 tenant_id 進行區(qū)分

          推薦使用第一種或第二種,隔離程度比較高,也比較容易做橫向擴展,如果是第三種,需要處理數(shù)據(jù)的隔離問題,需要處理單表大數(shù)據(jù)的問題等,對技術要求比較高。

          中間件

          除了數(shù)據(jù)庫,一個系統(tǒng)還需要依賴其他的一些中間件,比如緩存、消息隊列、文件存儲:

          • 緩存:Redi
          • 消息隊列:RabbitMQ
          • 文件存儲:MongoDB 的 GridFS

          Redis

          1、Redis 中使用數(shù)據(jù)庫的方式進行租戶隔離;

          2、Redis 可以通過修改配置文件的方式進行數(shù)據(jù)庫的擴展,默認為 16 個;3、通過 Redis 分片集群的方式進行部署,可以進行橫向擴展;3、在 Redis 集群中,官方推薦節(jié)點數(shù)量不超過 1000 個,這個對于多租戶系統(tǒng)的前期來說應該是夠用了,如果到了租戶數(shù)量的爆發(fā)期,再進行架構的擴展,比如,不同的租戶路由到不同的 Redis 集群中。

          RabbitMQ

          在 Rabbitmq 有 vhost 機制,可以一個租戶創(chuàng)建一個vhost,通過 vhost 來進行租戶的隔離,目前還沒查詢到 vhost 是否有上限,需要做進一步驗證。

          MongoDB

          MongoDB 中主要使用 GridFS 來進行非結構化數(shù)據(jù)的存儲,通過創(chuàng)建數(shù)據(jù)庫的方式來進行租戶的隔離,而且 MongoDB 支持分片的集群部署方式,可以進行擴展橫擴展,在前期,一個 MongoDB 集群應該就夠用了。

          最后

          技術方案和架構沒有最好的,只有最適合的,符合當下的業(yè)務場景、團隊的技術能力就可以,然后要做的就是做 MVP (最小可行性產(chǎn)品),進而進行系統(tǒng)的改造。

          希望本文對您有所幫助!


          瀏覽 201
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂在线免费视屏 | 三级片在线网站 | 肏屄免费视频 | 国产色情免费 | 欧美日比视频 |