写点什么

DTSE Tech Talk 丨第 2 期:1 小时深度解读 SaaS 应用系统设计

  • 2022 年 7 月 30 日
  • 本文字数:7902 字

    阅读完需:约 26 分钟

DTSE Tech Talk丨第2期:1小时深度解读SaaS应用系统设计

本文分享自华为云社区《DTSE Tech Talk丨第2期:1小时深度解读SaaS应用系统设计》,作者: 华为云社区精选。


DTSE Tech Talk 是华为云开发者联盟推出的技术公开课直播栏目,解读云上前沿技术,畅聊开发应用实践。由专家团队授课,答疑解惑,助力开发者使用华为云开放能力进行应用构建、技术创新。


围绕当下许多企业青睐的 SaaS 应用开发,华为云开发者技术服务工程师程泽在 DTT 首期带来主题为《SaaS 云原生应用典型架构》技术分享,点击回看。本次程泽带来了《SaaS 应用开发》系列课程第 2 期《SaaS应用技术架构设计》的分享,以下为精彩回顾。

1.SaaS 技术栈选型,如何取最优解


SaaS 技术栈作为 SaaS 应用的核心,可决定应用程序的可扩展性、功能性和可行性。因此,如何根据企业需求做出关于最佳技术堆栈的决策,是首要思考的问题。根据 SaaS 技术栈选择原则,企业在选择的过程中,需要注意以下四点:


  • 按需引入

    按照 SaaS 技术架构建设与演进需求节奏,引入技术栈。例如企业前期业务量不是很大,基础性运维能力就能支撑业务。随着业务规模的扩大,租户数量增加,底层资源消耗也逐渐增加,企业就需要建立运维监控系统数据分析平台支持海量的日志采集、存储、分析及查询等需求。

  • 可驾驭

    尽可能选择团队熟悉、成熟技术栈,企业在使用 CNCF 的开源软件过程中,若出现问题,借助有强有力社区支持,就能够得到及时反馈,帮助自己解决问题。

  • 适当超前

    技术栈容量选择能够满足未来 1~2 年业务需求,在选择的过程中要考量架构的可扩展性,方便后期的扩容操作。

  • 性价比

    技术栈考虑实施的性价比,如部署、维护成本等。


SaaS-管家项目为例,SaaS-管家项目是华为云开发者团队基于 SaaS 项目技术支持实践。从上图可以看出,在开发过程中,华为云选择基于 JAVA 开发的 Spring Boot 和 Spring-Cloud 技术栈进行微服务应用开发,引入微服务引擎 CSE 解决配置热更新的场景问题,应用部署方面选择具有弹性伸缩能力的 K8S 集群。在数据库方面,选择的是 RDS 服务解决后期扩容问题,以及 Redis 解决分布式缓存问题。在基础运维方面,通过 LTS 解决日志采集、查询分析服务。

2 CSE+CCE:简化企业微服务开发,加快应用容器化部署


随着云原生技术的不断完善和发展,云原生技术及架构在架构演进、技术选型、构建现代化应用等工作中产生了深刻的影响。在企业上云的趋势下,越来越多的企业和开发者开始把业务与技术向云原生演进。在技术栈选型上,企业也趋向于容器化、微服务化以及基于云化应用中间件、数据库构建应用。

2.1 微服务开发


云原生+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 华为的组件。


一步迁移弹簧云应用

2.2 容器化部署


华为云容器引擎 CCE 是基于业界主流的 Docker 和 Kubernetes 开源技术构建的容器服务,提供众多契合企业大规模容器集群场景的功能。而且,云容器引擎深度整合华为云高性能的计算(ECS/BMS)、网络(VPC/EIP/ELB)、存储(EVS/OBS/SFS)等服务,并支持 GPU、NPU、ARM、FPGA 等异构计算架构,支持多可用区(可用区,简称 AZ)、多区域(Region)容灾等技术构建高可用 Kubernetes 集群。


作为全球首批通过 CNCF 基金会 Kubernetes 一致性认证的容器服务,CCE 在系统可靠性、高性能、开源社区兼容性等多个方面具有独特的优势,满足企业在构建容器云方面的各种需求。CCE 具有如下价值:


  • 提高资源利用率(成本优化)

    榨干资源价值(虚机规格固定,容器规格灵活)

    秒级弹性伸缩(虚机分钟级弹性,容器秒级弹性)

    根据部分企业案例,在总体业务增长 20% 的情况下,综合节省成本可达到 30%

  • 提高运维效率(节省人力)

    自动化(CI/CD,自动弹性伸缩)

    上线/升级时间,从小时级变成分钟级

3 在多租户模式下,如何选择设计路由策略


实现多租户的设计原理,实际上是需要做到应用层和数据层的共享和隔离,然后通过一种路由机制,可以根据不同的策略,将用户的请求路由到指定的计算集群即可。

3.1 应用层多租模式


如果企业已经规划好了 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 调度到不同节点上,避免网络资源抢占。

3.2 数据层多租模式


在 SaaS 架构中,注重的就是数据的 “独立性”,也是隔离性。如何在共有的一套系统架构与服务,仍可以保障客户的数据相对独立的正常使用。一般地,以支持多租户的运行技术总体可分为三种:共享数据库,共享 Schema;共享数据库,基于 Schema 隔离;数据库隔离



共享数据库,共享 Schemas


  • 所有租户数据保存在共享数据库的共享表中,通过租户标识区分租户记录;每个租户访问相同的数据库和表,通过应用层数据访问控制保证数据的安全访问/隔离。

    优点:最低的资源成本、最大限度的使用 DB 资源;DB 的维护管理成本降低;

    缺点:共享数据库,共享 Schema 无法保证 Schema 扩展互相透明,增加了扩展的复杂度;数据隔离在应用层实现,隔离性较弱;租户的数据的备份恢复只能按记录操作;


共享数据库,基于 Schemas 隔离


  • 每个租户的数据保存在自己的表中,但共享同一个物理数据库,每个租户连接相同的数据库(相同的连接配置);但访问不同的表(Schema 设置)。

    优点:租户间有较弱的关系、影响较小(仍然可以轻松扩展、通过 DB User 保证数据安全和隔离;降低资源和数据库维护成本);

    缺点:不同租户访问相同的数据库,租户间的访问性能相互影响;数据备份恢复相互干扰;单 DB 租户数量受限;


基于数据库隔离


  • 每个租户的数据保存在一个物理上独立的数据库实例中(RDS),每个租户连接自己特定的数据库。

    优点:租户间完全没有影响(轻松扩展、数据备份恢复简单、数据安全的隔离);

    缺点:更高的资源成本和数据库的维护管理成本


当了解租户创建部分如何设计之后,我们需要考虑租户识别和租户路由问题。

3.3 前端路由策略


首先介绍前端路由策略。通常域名租户映射和 HTTP 请求参数支持实现租户 ID 向后端传递。在租户 ID 传递给后端之后,如存在共享资源池的情况,涉及租户上下文传递,需要配置路由策略,如使用租户路由插件,实现不同租户在逻辑隔离条件下,实现资源安全访问。


根据访问方式在应用入口可以使用路由策略方案有两种,分别如下:


  • 方案一:不同租户分配到不同的域名,用不同租户域名实现租户区分,通过域名映射,路由到不同租户应用。

  • 方案二:采用统一域名,通过不同请求参数,如增加租户字段,进行租户区分,将租户请求路由到后端应用。

3.4 后端租户路由策略


在共享资源池模式下,还要考虑后端租户路由情况。在多租户条件下,租户间是通过代码逻辑方式实现隔离,DNS 解析不同租户的域名是解析到同一个 IP 地址,即多个用户访问同一套资源环境,提供安全稳定的路由策略,将用户请求路由精确路由到租户所属的资源区,如下图所示:


  • 租户标识可以是域名、租户 id 或者 app id 等,租户标识进入网关时需要做安全认证,避免横向夺权;

  • 租户标识从访问入口起就要一路携带,直到租户隔离区域的终点,例如独享资源模式最简单,进入网关就可以去掉标识;如果是数据库隔离的,进入数据库就可以去掉标识;如果数据库共享根据字段隔离,标识就要放到 record 中;

  • 根据租户标识,服务把租户请求路由到不同的链路中,路由可以是云解析器,负载均衡或者服务本身,华为云开源的租户插件saas-tenant-router-starter可以帮助我们在服务中对租户请求进行路由。

4 SaaS-管家项目实践解读


服务内的租户路由,本质上是把租户标识放在每个请求的维度。以 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 访问等,需要采用合理的策略传递租户标识。如:

情形一:Open Feign 调用其他微服务时带上租户标识


/** * feign调用请求头添加租户标识 * * @since 2022-02-28 */public class FeignRequestInterceptor implements RequestInterceptor {    @Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header("tenantDomain", TenantContext.getDomain()); }}
复制代码


在这个场景中,Open Feign 启动一个拦截器,通过这个拦截器能够拦截用户请求,然后获取租户的唯一标识,通过 Template 字段,存储到请求头里。

情形二:使用 MQ 时,在消息头中传递租户标识


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;}
复制代码

第二步:获取 schema


@Overridepublic 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 模式。

方法一:K8s configmap 模式


server:  port: 8300spring:  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
复制代码

方法二:基于 spring



在数据库应用场景中,在增加新租户的时候,系统必须为新租户建立新的数据库,而当数据库版本更新,系统也必须为每个租户的数据库更新数据,必须借助一些工具来降低运维复杂度。在 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,我们将及时响应您的需求。也欢迎来自企业、个人开发者参与内容贡献。

相关资料


  • 应用开发文档:https://support.developer.huaweicloud.com/doc/zh-cn_topic_0000001321416345-0000001321416345

  • 参考示例代码:https://gitee.com/HuaweiCloudDeveloper

  • 问题咨询和专家服务预约(需注册华为云账号):https://support.developer.huaweicloud.com/feedback/?ticket=ST-5385866-mPu9vjwIeAGISrz1rXBAdwt7-sso

下期预告


本期课程,主要给大家介绍了在 saas 场景下,如何技术选型,saas 架构设计中关键的技术点等内容。

下节课,我们将给大家深入介绍多租路由开源插件能力,帮助企业快速实现 saas 化改造和技术架构升级。8 月 25 日,我们不见不散。


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
DTSE Tech Talk丨第2期:1小时深度解读SaaS应用系统设计_云计算_华为云开发者联盟_InfoQ写作社区