摘要:介绍在SaaS场景下如何技术选型,SaaS架构设计中关键的技术点等内容。
本文分享自华为云社区《DTSE Tech Talk丨第2期:1小时深度解读SaaS应用系统设计》,作者: 华为云社区精选。
DTSE Tech Talk是华为云开发者联盟推出的技术公开课直播栏目,解读云上前沿技术,畅聊开发应用实践。由专家团队授课,答疑解惑,助力开发者使用华为云开放能力进行应用构建、技术创新。
围绕当下许多企业青睐的SaaS应用开发,华为云开发者技术服务工程师在DTT首期带来主题为《SaaS云原生应用典型架构》技术分享,点击回看。本期直播为 “SaaS应用开发系列”课程第2期 《SaaS应用技术架构设计》,以下为精彩内容回顾。
SaaS技术栈作为SaaS应用的核心,可决定应用程序的可扩展性、功能性和可行性。 因此,如何根据企业需求做出关于最佳技术堆栈的决策,是首要思考的问题。根据SaaS技术栈选择原则,企业在选择的过程中,需要注意以下四点:
以 SaaS-Housekeeper项目为例,SaaS-Housekeeper项目是华为云开发者团队基于SaaS项目技术支持实践。从上图可以看出,在开发过程中,华为云选择基于JAVA开发的Spring Boot和Spring-Cloud技术栈进行微服务应用开发,引入微服务引擎CSE解决配置热更新的场景问题,应用部署方面选择具有弹性伸缩能力的K8S集群。在数据库方面,选择的是RDS服务解决后期扩容问题,以及Redis解决分布式缓存问题。在基础运维方面,通过LTS解决日志采集、查询分析服务。
随着云原生技术的不断完善和发展,云原生技术及架构在架构演进、技术选型、构建现代化应用等工作中产生了深刻的影响。在企业上云的趋势下,越来越多的企业和开发者开始把业务与技术向云原生演进。在技术栈选型上,企业也趋向于容器化、微服务化以及基于云化应用中间件、数据库构建应用。
云原生+AI+大数据时代,微服务化和容器化是应用现代化的基本特点。开源Spring Cloud为开发人员构建微服务架构提供了完整的解决方案,成为很多开发者的选择。但基于Spring Cloud组件构建微服务平台,需要集成验证Hystrix、Ribbon、Zipkin、Prometheus等大量三方组件,门槛高,学习周期长。
华为云微服务引擎CSE是用于微服务应用的云中间件,为用户提供注册发现、服务治理、配置管理等高性能和高韧性的企业级云服务能力。而且,CSE可无缝兼容SpringCloud、ServiceComb等开源生态。用户也可结合其他云服务,快速构建云原生微服务体系,实现微服务应用的快速开发和高可用运维。CSE具有SpringCloud应用零门槛上云;治理能力开箱即用;实现配置分发同步及版本管理三大特点。
在《DTSE Tech Talk技术公开课丨第1期:要想不踩SaaS那些坑,得先了解“SaaS架构”》中提到,用户在微服务开发阶段与华为云CSE微服务引擎对接,只需导入华为云微服务SDK即可享受何种服务治理和管控能力,相比较开源Spring Cloud既简单又方便。基于Spring Cloud微服务开发的项目,对接CSE只需在Pom里把Spring Cloud换成Spring Cloud华为的组件。
一步迁移SpringCloud应用
华为云容器引擎CCE是基于业界主流的Docker和Kubernetes开源技术构建的容器服务,提供众多契合企业大规模容器集群场景的功能。而且,云容器引擎深度整合华为云高性能的计算(ECS/BMS)、网络(VPC/EIP/ELB)、存储(EVS/OBS/SFS)等服务,并支持GPU、NPU、ARM、FPGA等异构计算架构,支持多可用区(Available Zone,简称AZ)、多区域(Region)容灾等技术构建高可用Kubernetes集群。
作为全球首批通过CNCF基金会Kubernetes一致性认证的容器服务,CCE在系统可靠性、高性能、开源社区兼容性等多个方面具有独特的优势,满足企业在构建容器云方面的各种需求。CCE具有如下价值:
实现多租户的设计原理,实际上是需要做到应用层和数据层的共享和隔离,然后通过一种路由机制,可以根据不同的策略,将用户的请求路由到指定的计算集群即可。
如果企业已经规划好了SaaS应用多租和开发内容,那是时候考虑SaaS应用部署环境的问题。
在SaaS应用场景,线下IDC部署显然不是一个很好的选择,相较于基于云上虚拟机部署模式,采用Kubernetes集群模式部署SaaS应用应该是首选。云容器引擎CCE提供高度可扩展的、高性能的企业级Kubernetes集群,充分利用云上弹性能力、丰富存储类型,支持实现SaaS业务降成本、动态扩容、高可靠性等需求。云容器引擎CCE可以为云上构建SaaS应用提供不同多租隔离模式:
基于CCE Pod模式,租户间通过Pod进行隔离,每个Pod包含多个Container。
基于CCE NameSpace模式,不同租户的业务微服务部署在同一个CCE集群中的不同的NameSpace中,支持资源的逻辑隔离;不同租户的路由、升级等策略部署到同一个配置中心的不同的配置组中,支持策略逻辑隔离;
基于环境模式,不同租户的业务微服务在不同的环境中
在共享资源池模式下,Kubernetes集群为SaaS应用开发者提供的namespace隔离方式能够带来很大的帮助。华为云容器引擎CCE在namespace粒度提供了网络隔离、资源配额限制以及RBAC权限管理策略等租户管控策略。
网络隔离方面,CCE基于Kubernetes的网络策略功能进行了加强,通过配置网络策略,允许在同个集群内实现网络的隔离,也就是可以在某些实例(Pod)之间架起防火墙。在华为云上提供了vpc网络和容器隧道网络,仅“容器隧道网络”模式的集群支持网络隔离。
资源配额限制方面,通过设置命名空间级别的资源配额,实现多租户在共享集群资源的情况下限制团队、租户可以使用的资源总量,包括限制命名空间下创建某一类型对象的数量以及对象消耗计算资源(CPU、内存)的总量。
在业务比较平稳的SaaS系统中,可以用于租户业务资源的隔离,CCE集群支持CPU/内存配额限制、网络隔离、QoS限速等策略。以QoS限速策略为例,由于不同租户资源可能部署在同一节点上,导致不同业务容器之间存在带宽抢占的情况,容易造成业务抖动。为了解决这个问题,您可以通过对Pod间互访进行QoS限速来解决这个问题当然,也可以通过亲和性策略,将不同租户的pod调度到不同节点上,避免网络资源抢占。
在SaaS架构中,注重的就是数据的 “独立性”,也是隔离性。如何在共有的一套系统架构与服务,仍可以保障客户的数据相对独立的正常使用。 一般地,以支持多租户的运行技术总体可分为三种:共享数据库,共享Schema;共享数据库,基于Schema隔离;数据库隔离。
当了解租户创建部分如何设计之后,我们需要考虑租户识别和租户路由问题。
首先介绍前端路由策略。通常域名租户映射和HTTP请求参数支持实现租户ID向后端传递。在租户ID传递给后端之后,如存在共享资源池的情况,涉及租户上下文传递,需要配置路由策略,如使用租户路由插件,实现不同租户在逻辑隔离条件下,实现资源安全访问。
根据访问方式在应用入口可以使用路由策略方案有两种,分别如下:
在共享资源池模式下,还要考虑后端租户路由情况。在多租户条件下,租户间是通过代码逻辑方式实现隔离,DNS解析不同租户的域名是解析到同一个IP地址,即多个用户访问同一套资源环境,提供安全稳定的路由策略,将用户请求路由精确路由到租户所属的资源区,如下图所示:
服务内的租户路由,本质上是把租户标识放在每个请求的维度。以Java为例,传统的Java web项目,可直接通过Request Context Holder获取租户标识,这个类使用了Thread Local作为线程隔离。在使用多线程的项目中,例如reactor或项目中使用了hystrix线程池隔离模式,threadlocal变量会在线程传中丢失,此类情况可选择与作用范围对应的容器,例如可使用Hystrix Request Valuable Default作为标识的存储容器,自主代码也可依赖其线程池创建的方式来传递租户标识以及租户相关信息。
- public class TenantContext {
- private static final HystrixRequestVariableDefault<Map<String, String>> TENANT_KEY =
- new HystrixRequestVariableDefault<>();
- public static String getDomain() {
- return Optional.ofNullable(TENANT_KEY.get()).orElse(new HashMap<>()).get(Constants.TENANT_DOMAIN);
- }
- /**
- * 初始化时设置domain,直到会话结束时才会销毁
- *
- * @param domain 标识
- */
- public static void setDomain(String domain) {
- HystrixRequestContext.initializeContext();
- Map<String, String> variableMap = new HashMap<>(2);
- variableMap.put(Constants.TENANT_DOMAIN, domain);
- // 默认主库
- variableMap.put(Constants.DB_STRATEGY, Constants.DB_SLAVE);
- TENANT_KEY.set(variableMap);
- }
SaaSHousekeeper项目以域名作为租户标识, TENENT_DOMAIN是请求的域名,每个请求到达服务时,过滤器会拦截请求,然后把租户标识存放到HystrixRequestValuableDefault中,如果非用户请求,也需要把租户标识初始化。
解决租户存储的问题之后,接下来需要解决租户路由标识是如何在服务中如何传递的。在实现租户标识传递时,可能会遇到不同场景,如跨微服务调用以及MQ、ES访问等,需要采用合理的策略传递租户标识。如:
- /**
- * feign调用请求头添加租户标识
- *
- * @since 2022-02-28
- */
- public class FeignRequestInterceptor implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- requestTemplate.header("tenantDomain", TenantContext.getDomain());
- }
- }
在这个场景中,Open Feign启动一个拦截器,通过这个拦截器能够拦截用户请求,然后获取租户的唯一标识,通过Template字段,存储到请求头里。
- rabbitTemplate.setBeforePublishPostProcessors(new MessagePostProcessor() {
- @Override
- public Message postProcessMessage(Message message) throws AmqpException {
- String tenantDomain = TenantContext.getDomain();
- log.info("发送消息前的tenantDomain : " + tenantDomain);
- // 消息发送前保存租户标识
- Optional.ofNullable(tenantDomain)
- .ifPresent(domain -> message.getMessageProperties().setHeader("tenantDomain", tenantDomain));
- return message;
- }
- });
在获取标识之后,如何映射到后端的数据源中,让请求与数据源建立链接。面对这个情况,租户路由在进行数据请求时,通过拦截器拦截数据请求,从请求上下文中获取租户标识,根据标识从mapping表中获取该租户绑定的数据源以及schema,然后把这些连接设置到数据请求连接中,再继续下一步。具体操作步骤如下:
- public DataSourceGroup getDataSourceGroup(String key) {
- String groupName = defaultSource;
- if (bindingMap != null && !bindingMap.isEmpty() && bindingMap.containsKey(key)) {
- // 配置指定的数据源有效时
- groupName = bindingMap.get(key).getGroupName();
- }
- groupName = StringUtils.isBlank(groupName) ? defaultSource : groupName;
- // 配置显示绑定的数据源不存在,直接抛出异常
- Optional.ofNullable(groupName).orElseThrow(() -> new RoutingException(key + " No Binding Data Source!"));
- DataSourceGroup dataSourceGroup = groupMap.get(groupName);
- // 指定数据源无效时, 从扩展的数据源适配器中获取数据源组
- Optional.ofNullable(dataSourceGroup)
- .orElseThrow(() -> new RuntimeException(key + " Binding Data Source group not exists"));
- log.warn("{} select DataSource {} success!", key, groupName);
- return dataSourceGroup;
- }
- @Override
- public Object intercept(Invocation invocation) throws Exception {
- Connection coon = (Connection) invocation.getArgs()[0];
- // 获取传递的租户标识
- String domain = TenantContext.getDomain();
- // 租户标识获取配置的对应schema以及数据源绑定信息
- DataSourceBindingProperty bindingProperty = dynamicRoutingDataSource.getBidingProperty(domain);
- String catalog = null;
- if (bindingProperty != null && StringUtils.isNotBlank(bindingProperty.getSchema())) {
- // 配置指定的schema, 优先级最高
- catalog = bindingProperty.getSchema();
- } else {
- // 未指定schema, 则使用当前生效的适配器逻辑获取schema, 默认适配器规则为使用
- catalog = schemaAdapter.getSchema(domain);
- }
- if (catalog != null) {
- log.warn("{} select schema {}", domain, catalog);
- coon.setCatalog(catalog);
- }
- return invocation.proceed();
- }
示例代码来源: saas-tenant-router-starter
在SaaS应用场景中,经常遇到更新一些参数或者新增一些信息连接。在租户参数配置更新实际操作方面会遇到一些问题,当遇到增加新租户或租户的资源信息改变时,需要动态改变租户路由的映射表(variableMap)。
如果Mapping表使用数据库或redis存储,io消耗会比较大,建议使用本地存储。还可以用事件机制来刷新Mapping表,例如spring cloud bus 事件和k8s configmap热更新的event模式。
- server:
- port: 8300
- spring:
- profiles:
- active: local
- application:
- name: saas-housekeeper-order
- cloud:
- kubernetes:
- reload:
- enabled: true #配置更新时重新启动开关打开
- mode: polling #主动拉取模式
- strategy: event
- config:
- enabled: true
- namespace: housekeeper
- name: saas-housekeeper-config
在数据库应用场景中,在增加新租户的时候,系统必须为新租户建立新的数据库,而当数据库版本更新,系统也必须为每个租户的数据库更新数据,必须借助一些工具来降低运维复杂度。在SaaSHousekeeper项目中,采用了flyway作为数据版本的管理工具,为租户创建新数据库、插入基础数据,维护数据库版本的更新。
Flyway.configure().dataSource(url, username, password).schemas(schema).load().migrate();
虽然SaaS-housekeeper项目是一个标准化方案,但是它能满足用户在应用的过程中的定制化调整。
在SaaS-housekeeper项目的家政业务场景中,A租户想做清洁服务,B租户想做月子服务,服务的内容和规格和收费计量都不一样,怎么把这些内容让租户自己定义呢?这些表的设计就是把服务的定义,规格的定义,选项的定义,各种组合的价格都变成客户可自配置的内容。
SaaSHousekeeper项目在服务发布的设计上,就是把家政业务场景的元数据定义交给租户,根据租户需求配置。
SaaSHousekeeper项目不仅在后端拥有定制化功能,在前端同样也可以进行定制化调整。以前端页面为例,很多用户希望自己的应用展示企业或个人自己独特的风格,SaaSHousekeeper项目除了app定制,小程序定制或web模板定制外,也可以在同一个web前端配置不同的展示风格,SaaS-housekeeper项目的SaaS应用前端可在用户根据域名登陆时获取对应租户的设定来配置风格主题
为了更好的推动云原生SaaS应用开发,华为云开发者团队基于SaaS项目技术支持实践,沉淀了SaaS应用开发相关套件中,包括微服务架构、多租隔离设计、多租户路由、数据存储多租设计、数据源管理等,希望能够为企业级开发者提供SaaS应用改造和技术构建升级提供技术参考。如果您在云原生应用开发或者技术选型等过程中有任何技术问题,都可以给华为云开发者技术团队提issues,我们将及时响应您的需求。也欢迎来自企业、个人开发者参与内容贡献。
应用开发文档:文档中心
参考示例代码:HuaweiCloudDeveloper: 为开发者提供SaaS、云容器、微服务、serverless、AI等应用构建的技术参考文档、samples代码,如果您在云原生应用开发或者技术选型等过程中有任何技术问题,都可以给华为云开发者技术团队提issues,我们将及时响应您的需求。也欢迎来自企业、个人开发者参与内容贡献。
问题咨询和专家服务预约(需注册华为云账号):https://support.developer.huaweicloud.com/feedback/?ticket=ST-5385866-mPu9vjwIeAGISrz1rXBAdwt7-sso
本期课程,主要给大家介绍了在saas场景下,如何技术选型,saas架构设计中关键的技术点等内容。
下节课,我们将给大家深入介绍多租路由开源插件能力,帮助企业快速实现saas化改造和技术架构升级。8月25日,我们不见不散。