本文转载自微信公众号《不只是dotNET》,作者不只是dotNET。转载本文请多多联系dotNET公众号。2020年突如其来的新冠疫情,让线上协同办公成为疫情期间的刚需。2020年2月3日至4月,我们还开始在家远程工作。协同办公软件一下子火了起来。钉钉、企业微信,尤其是腾讯会议在疫情期间都表现突出,呈现井喷式发展。目前企业信息化大部分是私有化部署,局限于企业内部网络,无法实现远程协同办公。因此,越来越多的ToB企业逐渐转向SaaS(Software-as-a-Service,软件即服务),SaaS最早是由美国Salesforce公司(成立于1999年)开创的一种新型软件服务模式。这家公司2019年市值已经超过1000亿美元,在国内还处于发展阶段,前景还是很广阔的。重构传统私有化软件以支持SaaS模式,多租户是一道无法逾越的坎。首先,系统需要转型为多租户模式,然后逐步实现计费、系统监控、用户行为分析等功能。.我觉得多租户的设计应该分三个层次来讨论,应用,数据库,中间件。目前应用的项目或产品开发几乎都是前后端分离的开发模式。应用层主要是指WebAPI。改造WebAPI有两种方式:1、每个租户部署一套WebAPI,上层使用域名或Url地址。解析路由,并在新租户注册时动态部署相应的WebAPI。这种方式改造成本低,但运维成本高。不推荐。如果时间紧迫,可以作为过渡阶段的临时解决方案。2.所有租户共享一套WebAPI。WebAPI中需要获取租户信息(域名、URL参数、请求头信息、cookies等),然后切换租户信息配置。创建新租户时,不需要新建WebAPI,只需要初始化租户的基本信息即可。这样,如果Cluster1的负载超过了限制,也可以将部分租户动态切换到其他Clusterer上,如上图所示。WebAPI的代码实现可以参考Abp框架中多租户的实现。这里有一个简化版本:TenantConfiguration:租户配置信息[Serializable]}publicstringCacheConfig{get;set;}publicstringMQConfig{get;set;}publicstringMongoConfig{get;set;}publicTenantConfiguration(){TenantStatus=TenantStatus.Enable;}publicTenantConfiguration(Guidid,stringname):this(){Id=id;Name=name;}}TenantStore:从缓存或数据库中获取租户配置信息//从缓存或数据库获取租户配置信息thrownewNotImplementedException();}}CurrentTenant:当前租户类,用于存储当前租户信息和切换租户///当前租户///publicclassCurrentTenant:ICurrentTenant{publicITenantStore_tenantStore;publicCurrentTenant(ITenantStoretenantStore){_tenantStore=tenantStore;}publicTenantConfiguration_config;publicTenantConfigurationConfig=>_config;//////切换租户/////////publicIDisposableChange(stringcode){TenantConfigurationtenantConfig=_tenantStore.Find(code);if(tenantConfig==null){thrownewException("Tenantnotfound");}if(tenantConfig.TenantStatus!=TenantStatus.Enable){thrownewException("Tenantisdisabledordeleted");}returnnewDisposeAction(()=>{_config=tenantConfig;});}}UrlTenantResolve:根据Url参数据进行租户解析publicinterfaceITenantResolve{stringResolve(HttpContexthttpContext);}/////////publicclassUrlTenantResolve:ITenantResolve{publicstringResolve(HttpContexthttpContext){returnhttpContext.Request.QueryString.HasValue?httpContext.Request.Query["__tenant"].ToString():null;}}MultiTenancyMiddleware:租户中间件,关于在dotNETCore中自定义中间件可以参考《dotNET Core 3.X 请求处理管道和中间件的理解》publicclassMultiTenancyMiddleware:IMiddleware{protectedreadonlyITenantResolve_tenantResolve;privatereadonlyICurrentTenant_currentTenant;publicMultiTenancyMiddleware(ITenantResolvetenantResolve,ICurrentTenantcurrentTenant){_tenantResolve=tenantResolve;_currentTenant=currentTenant;}publicasyncTaskInvokeAsync(HttpContextcontext,RequestDelegatenext){vartenantCode=_tenantResolve.Resolve(context);if(tenantCode!=_currentTenant.Config.Code){using(_currentTenant.Change(tenantCode)){awaitnext(context);}}else{awaitnext(context);}awaitnext(context);}}这里的数据库指的是关系型数据库,用于存储业务数据,实现多租户。有必要隔离数据。数据隔离通常有三种模式:1、完全隔离,每个租户使用独立的数据库;2、部分共享,租户共享一个数据库,以schema或table区分;3.完全共享,租户共享同一张数据库表,以tenant_id区分。推荐使用第一种或第二种,隔离度比较高。横向扩展也比较容易。如果是第三种,需要处理数据的隔离和单表大数据等问题,对技术要求比较高。中间件除了数据库,系统还需要依赖其他中间件,如缓存、消息队列、文件存储:缓存:Redi消息队列:RabbitMQ文件存储:MongoDB的GridFSRedis1,Redis利用数据库隔离租户;2,Redis可以通过修改配置文件来扩容数据库,默认为16个;3、通过redissharding集群部署,可以水平扩展;3、在Redis集群中,官方推荐的节点数不超过1000首先,这个对于多租户系统的前期应该够用了。如果租户数量激增,则扩展架构。例如,不同的租户被路由到不同的Redis集群。RabbitMQ在RabbitMQ中有一个vhost机制。一个租户可以创建一个虚拟主机,并通过虚拟主机隔离租户。目前还没有发现vhost是否有上限,需要进一步验证。MongoDB主要使用GridFS来存储非结构化数据,通过创建数据库来隔离租户。而且MongoDB支持分片集群部署,可以水平扩展。前期一个MongoDB集群应该够用了。最终,没有最好的技术方案和架构,只有最合适的,能满足当前业务场景和团队技术能力的,接下来需要做的就是做一个MVP(MinimumViableProduct),然后进行系统改造。希望本文对您有所帮助!