企业级SaaS市场近几年在每一个细分领域都涌现出了一批玩家。从技术角度看,不一样的领域、不一样的SaaS产品,一定有着一样的架构内核,其中最关键的即是对于多租户(Multi-Tenancy)的支持。对广大企业来讲,引入SaaS产品本质上就是对互联网服务的租赁,于是多租户便必然是SaaS的自然属性之一,也是其与传统互联网应用架构设计的重要差别之一。在SaaS架构的成熟度演进过程当中,其核心路线即是如何实现多租户,也就是说,SaaS成熟度的高低,很大程度上取决于如何实现多租户的支持。数据库
多租户在技术实现层面目前并无既定的规范,不只细节多,每处细节的实现方式也多种多样。如何落地,一方面取决于当前研发团队现有的技术储备、技术选型、团队资本实力、所处行业或客户特色(好比金融行业对数据安全会有更高要求),另外一方面也与当前的技术发展息息相关,云厂商的崛起和云原生时代的到来,也深入影响着包括SaaS在内的软件构建的方法。安全
但常规来讲,真正的SaaS应用每每须要知足如下两点:网络
· 单实例架构
· 多租户并发
单实例意味着系统资源层面的共享,多租户意味着应用逻辑层面的隔离。因此如何平衡好这两点,才是SaaS应用多租户设计的核心关注点。框架
经典的分布式服务架构自然解决了互联网应用的三高问题(高并发、高性能、高可用),这也是企业SaaS发展中后期即将面临的问题,下面咱们来分析下如何在该架构下去设计与实现多租户SaaS应用。分布式
从资源共享的层面看,从share nothing到share everything,在天平的任何一个点上均可以支撑多租户。但正如咱们前文所说,SaaS架构首要考虑的目标即是单实例,只有单实例才能将成本尽量下降,产品才会有规模效应。因此所谓共享和隔离,在经典架构下又会聚焦为一点,即如何对不一样租户进行资源层面的隔离。高并发
谈到资源,咱们可能会想到CPU、内存、磁盘、网络带宽等,但如此多类型的资源,从其特征上又能够归为两类,即存储资源和计算资源。组件化
换句话说,SaaS系统在技术本质上也能够认为就是分布式存储和分布式计算的融合。性能
在多租户的实现中,每每更关键的是对于存储资源的处理,计算资源通常只在必要状况下才会考虑,我认为这主要是和存储的“有状态性”有关。下面咱们以一些典型场景为例,具体分析一下多租户的设计该如何着手。
隔离存储资源归纳来讲能够用一个词来解决:命名空间。以数据库为例,咱们只须要在每条租户的记录上,记下对应租户的标识便可。
通常来讲,不考虑分库分表的状况下,咱们逻辑上会在同一个Schema中,存储全部租户的数据。这就要求每张表都会有一个tenant_id字段,也即每条记录都携带了它的“命名空间”——租户标识。
再以经常使用的NoSQL方案Redis为例,通常来讲也是在同一个分布式集群中存储全部租户数据,那么很明显在key上携带租户标识便可。
因此不管何种存储,思路都是相通的,并且处理起来相对简单粗暴。但这里我想着重强调的是,在工程层面咱们应当将这种约定在底层框架里作统一处理。
好比在租户上下文中的全部SQL语句,应当都要携带where tenant_id=?这个条件,才能保证逻辑正确,咱们很难想象在代码从零到十万、百万行的过程当中,全部人都自始至终都牢记这个规则。
那么相似场景下,咱们就能够经过AOP技术将多租户相关的逻辑切出来进行统一处理,好比在Java中,咱们能够定义@TenantContextAware注解,以声明而非编码的方式在须要的地方作对应的租户信息获取及传递处理。
那么又如何保证开发者也牢记这个规则呢,因为多租户是SaaS的自然属性,咱们能够反其道而行之,默认支持多租户逻辑,同时定义@TenantContextUnaware注解,在不须要多租户的地方进行例外声明,这就大大下降了开发团队的负担。
同理,相似Redis Key的维护,也建议定义统一的KeyGeneratePolicy来维护。
隔离计算资源的方法也能够用一个词来归纳,那就是亲和性,简单来讲就是租户与集群计算资源的亲和性设计。
计算与存储除了“状态”方面的差别外,还有一个很是重要的区别,计算的财务成本每每远高于存储,好比咱们一台虚拟主机上可能只容许数百个线程同时处理请求。
正由于如此,宝贵的计算资源在非必要的状况下通常不会再进行细粒度的隔离,例如咱们通常不会在运行时只容许某租户的请求只提交给指定工做线程处理。
另一方面,计算资源发生倾斜的后果,每每比存储要严重的多,如同木桶效应般,直接且显著地影响整个集群的服务能力。
但特定场景下较粗粒度的隔离,有时候仍是很是必要的。好比为了减小系统故障时租户的影响范围,咱们可能会将租户的请求哈希后提交给不一样的线程池处理,由于这种状况下,反压将会产生全局的影响。
另外咱们也可能在特定场景下进行进程、集群层面的隔离。总的来讲,对计算资源进行隔离,没有既定的模式与套路,并且每每须要高超的资源操做水平,通常不到万不得已不建议实施。
一样地,若是必定要实施,那么也应当以组件化的方式进行,保证业务逻辑的纯粹性。
经过上述对存储和计算资源的隔离处理,咱们的SaaS架构总体看起来将会是下图这个结构。
在这里用一个表格就一些要点对两种手段作个简单的对比,便于你们更直观地理解。
面向企业的SaaS服务每每还有一些特色可能会引出一些高阶需求,而独立的单实例架构有时候并不能彻底知足这些高阶需求。此时就须要对原有架构进行扩展,以实例级别的总体隔离,配合租户级的请求分流手段,为SaaS带来资源、软件版本等多方面的隔离。
但须要注意的是,对单实例架构的扩展,并无下降其架构成熟度,与咱们文中一直在强调的单实例架构理念并不冲突。
好比咱们每每会根据企业客户的规模和特色对其保障等级进行分级,那如何进一步合理地隔离资源,保障不一样级别客户的使用体验,也是一个没法逃避的问题。
这种状况下,咱们就能够考虑将这类客户的某些资源实施特殊的保护性隔离,或者干脆将单实例架构扩展成为多实例架构,将客户分流到不一样保障级别的资源池。
若是有个别客户体量远超其余客户,那么在成本容许的状况下,咱们甚至能够考虑为其建设专属资源池,对其进行重点保障,这种级别的保护并不意味着牺牲了小体量客户的体验,相反,每每大致量客户才更容易发生一些影响稳定性的突发事件,因此能够认为是一种多赢的操做。
另外,SaaS每每能给客户带来更快的特性交付,但这种快速交付极可能带来不佳的使用体验,好比严重BUG的存在。
那么这个时候,若是咱们的系统是多实例架构,那么就能够很轻易地实现灰度发布,从而使得特性交付的过程更加稳健,也是对品牌形象的一种保护。
在实际开发中,咱们每每容易忽视早期对相似多租户等基础层面的系统性规划与设计,致使后期研发、维护成本持续增长,甚至在面临一些新的商业机会的时候,没法灵活应对。
好的架构则能将这些本质的特征透明化,作到业务层无感,从而提升研发效率。在企业SaaS的多租户架构设计环节,咱们没法罗列或预判全部可能,在不一样的技术选型下的多租户实现也有很大差别,咱们应当着重去发掘其技术本质,从计算与存储资源的隔离层面,系统地规划与架构,作好基础组件的建设与沉淀。
只有抛开现象去概括总结相关本质方法,才能以不变应万变。
关于做者
张晋。网易智慧企业架构师,负责旗下多款SaaS产品的架构、基础设施建设等相关工做,有丰富的C端、B端产品研发经验。目前主要关注企业级产品的技术架构、研发管理等方面。