SaaS云进销存源码:多租户架构设计与实现指南

一、多租户架构的核心价值与挑战

在SaaS化转型中,多租户架构是降低运维成本、提升资源利用率的关键。通过单一系统实例服务多个租户,企业可避免为每个客户单独部署,显著降低硬件与人力成本。但实现多租户支持需解决三大核心问题:数据隔离(确保租户数据互不干扰)、性能隔离(防止单租户高负载影响其他租户)、定制化扩展(支持租户个性化需求)。

以某行业常见技术方案为例,传统单体架构通过数据库分表或字段标记实现多租户,但扩展性差,难以应对租户数量激增。而现代云原生架构采用分层设计,将租户上下文贯穿应用层、数据层与存储层,实现更灵活的资源分配。

二、多租户数据隔离的三种实现方案

1. 独立数据库模式(高隔离性)

每个租户拥有独立数据库实例,数据隔离性最强,但运维成本高。适用于金融、医疗等对数据安全要求极高的场景。

  1. -- 租户A数据库连接配置
  2. CREATE DATABASE tenant_a;
  3. USE tenant_a;
  4. CREATE TABLE inventory (id INT PRIMARY KEY, name VARCHAR(100));
  5. -- 租户B数据库连接配置
  6. CREATE DATABASE tenant_b;
  7. USE tenant_b;
  8. CREATE TABLE inventory (id INT PRIMARY KEY, name VARCHAR(100));

优点:隔离彻底,故障域独立;缺点:资源利用率低,跨租户查询需合并数据。

2. 共享数据库+独立Schema模式(平衡方案)

同一数据库内为每个租户创建独立Schema,通过Schema隔离数据。适用于中等规模SaaS应用。

  1. -- PostgreSQL示例:创建租户Schema
  2. CREATE SCHEMA tenant_a;
  3. CREATE TABLE tenant_a.inventory (id SERIAL PRIMARY KEY, name VARCHAR(100));
  4. CREATE SCHEMA tenant_b;
  5. CREATE TABLE tenant_b.inventory (id SERIAL PRIMARY KEY, name VARCHAR(100));

实现要点:需在应用层动态切换Schema,例如通过中间件拦截SQL并追加Schema前缀。

3. 共享数据库+共享表模式(高资源利用率)

所有租户数据存储在同一张表中,通过tenant_id字段区分。适用于租户数量多、数据量小的场景。

  1. CREATE TABLE inventory (
  2. id SERIAL PRIMARY KEY,
  3. tenant_id VARCHAR(36) NOT NULL,
  4. name VARCHAR(100),
  5. CONSTRAINT unique_per_tenant UNIQUE (tenant_id, id)
  6. );

优化技巧:为tenant_id字段建立索引,查询时强制过滤条件:

  1. -- 查询租户A的库存
  2. SELECT * FROM inventory WHERE tenant_id = 'tenant_a_id' AND name LIKE '%product%';

三、多租户架构的关键设计模式

1. 租户上下文传递

通过请求头(如X-Tenant-ID)或JWT Token携带租户标识,在中间件层解析并注入到线程上下文。

  1. // Spring Boot中间件示例
  2. @Component
  3. public class TenantContextFilter implements Filter {
  4. @Override
  5. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  6. throws IOException, ServletException {
  7. String tenantId = ((HttpServletRequest) request).getHeader("X-Tenant-ID");
  8. TenantContext.setCurrentTenant(tenantId); // 存储到ThreadLocal
  9. try {
  10. chain.doFilter(request, response);
  11. } finally {
  12. TenantContext.clear(); // 清理上下文
  13. }
  14. }
  15. }

2. 动态数据源路由

结合Spring的AbstractRoutingDataSource,根据租户ID动态选择数据源。

  1. public class TenantRoutingDataSource extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. return TenantContext.getCurrentTenant();
  5. }
  6. }
  7. // 配置多数据源
  8. @Configuration
  9. public class DataSourceConfig {
  10. @Bean
  11. public DataSource multiTenantDataSource() {
  12. Map<Object, Object> targetDataSources = new HashMap<>();
  13. targetDataSources.put("tenant_a", createDataSource("tenant_a_db"));
  14. targetDataSources.put("tenant_b", createDataSource("tenant_b_db"));
  15. TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
  16. routingDataSource.setTargetDataSources(targetDataSources);
  17. return routingDataSource;
  18. }
  19. }

3. 租户级缓存隔离

使用Redis时,可通过哈希标签(Hash Tag)或独立Key前缀实现缓存隔离。

  1. // 租户A的缓存Key
  2. String cacheKeyA = "tenant_a:inventory:" + productId;
  3. // 租户B的缓存Key
  4. String cacheKeyB = "tenant_b:inventory:" + productId;

四、性能优化与安全实践

1. 连接池管理

为每个租户分配独立连接池,避免单租户高并发耗尽连接。主流连接池(如HikariCP)支持按租户配置参数:

  1. # application.yml示例
  2. spring:
  3. datasource:
  4. hikari:
  5. maximum-pool-size: 20
  6. tenant-a:
  7. maximum-pool-size: 5 # 租户A专用连接池
  8. tenant-b:
  9. maximum-pool-size: 10

2. 跨租户查询防护

禁止直接执行无租户过滤的SQL,可通过以下方式实现:

  • SQL解析拦截:使用Druid等数据库防火墙解析SQL,检查是否包含tenant_id条件。
  • 存储过程封装:将跨表操作封装为存储过程,自动注入租户ID。

3. 租户配额管理

通过API网关或中间件限制租户资源使用,例如:

  1. // 伪代码:检查租户配额
  2. public void createOrder(Order order, String tenantId) {
  3. TenantQuota quota = quotaService.getQuota(tenantId);
  4. if (order.getAmount() > quota.getRemaining()) {
  5. throw new QuotaExceededException();
  6. }
  7. // 执行订单创建
  8. }

五、源码实现的关键模块

完整的SaaS云进销存源码需包含以下模块:

  1. 租户管理模块:租户注册、配额分配、状态监控。
  2. 元数据驱动模块:通过JSON或YAML定义租户个性化字段。
  3. 审计日志模块:记录所有租户操作,支持按租户追溯。
  4. 升级回滚模块:支持按租户分批发布新版本。

示例:元数据驱动的字段扩展

  1. // 租户A的字段配置
  2. {
  3. "tenantId": "tenant_a",
  4. "customFields": [
  5. {"name": "batch_no", "type": "string", "required": true},
  6. {"name": "expiry_date", "type": "date"}
  7. ]
  8. }

应用层动态生成表单与数据库表结构,避免硬编码。

六、部署与运维建议

  1. 容器化部署:使用Docker+Kubernetes实现租户资源隔离,通过Namespace划分资源配额。
  2. 监控告警:为每个租户配置独立的Prometheus指标,按租户设置阈值告警。
  3. 备份恢复:定期备份租户数据,支持按租户快速恢复。

结语

构建支持多租户的SaaS云进销存系统需兼顾隔离性、扩展性与成本。通过分层架构设计、动态数据源路由与元数据驱动开发,可实现高效的多租户支持。实际开发中,建议从共享表模式起步,逐步向独立Schema或数据库模式演进,以平衡性能与成本。