SaaS多租户架构--应用程序

本文主要介绍SaaS架构中除 数据库 之外的开发过程。


租户标识

通常有2个:

  • TenantId
  • TenantCode

使用建议:

  • TenantId:用于数据持久化,对用户不可见
  • TenantCode:供用户输入或显示,它与 TenantId 一一对应



租户存储

Nebula的设计目标是一款通用的开发框架,因此对 租户存储 没有任何定义,应用程序可以根据实际业务需求来自由定义数据结构,这里只给出几条参考建议:

  • 租户总表存放在 master 数据库,其中不要包含【数据库连接字符串】
    • 数据库连接字符串 由配置服务来管理
  • 不要在内存中缓存所有租户数据,想像下未来可能会有100W个租户



用户身份

多租户架构中,最核心的就是用户身份的设计,因为一切数据来源都是用户的操作。

Nebula提供了一个接口来定义用户身份信息:

public interface IUserInfo
{
    string TenantId { get; }
    string UserId { get; }
    string UserName { get; }
    string UserRole { get; }

其中一个重要的成员 TenantId 就是租户ID,所以当用户执行操作时会知道TA对应哪个租户库。

Nebula提供2个实现类供开发者使用

  • WebUserInfo:表示一个普通用户(浏览器前端)
  • AppClientInfo:用于第三方的应用程序客户端

如果这些类型的数据成员不能满足你的需求,你可以自行实现这个接口。

当执行用户登录时,调用 AuthenticationManager.Login(...) 方法,第一个参数就是 IUserInfo 类型。
在已登录场景,调用 nhttpContext.GetUserInfo() 返回的就是 登录时 传入的 IUserInfo 对象



URL

核心原则:

  • URL地址能区分租户(URL中要包含TenantCode),例如使用场景:链接分享
  • 对于已登录用户,URL中的TenantCode必须和用户身份中的TenantId保持一致(注意校验)

建议实现方式:

  • 二级域名,例如:http://{TenantCode}.YourApp.com
  • URL片段,例如:http://www.YourApp.com/v20/api/xxapp/{TenantCode}/....



RabbitMQ

虽然RabbitMQ有内建的多租户架构支持,即:使用 vhost 来隔离,但是不建议使用这个特性,原因如下:

  • 当租户数量较多时,例如 1万个租户,那么 1万个 vhost 在界面中就没法看了!
  • 会导致大量的连接,极度浪费连接资源(线程和内存),流量负载也不均衡。

所以,建议的方式是:

  • 在消息体中增加一个数据成员 TenantId 来区分租户
  • 不同的业务数据,使用不同的队列
  • 如果业务的响应时间要求较高,可规划不同的优先级,并创建不同的队列



Redis

建议:

  • key 中包含 TenantId 信息
  • 根据业务用途,可以使用到不同的Redis实例



OSS存储

建议:

  • path格式: bucket/{TenantId}/{业务用途}/{日期-可选部分}/{filename}