当前位置: 首页 > article >正文

SpringBoot多租户系统的5种架构设计方案

多租户(Multi-tenancy)是一种软件架构模式,允许单个应用实例服务于多个客户(租户),同时保持租户数据的隔离性和安全性。

通过合理的多租户设计,企业可以显著降低运维成本、提升资源利用率,并实现更高效的服务交付。

本文将分享SpringBoot环境下实现多租户系统的5种架构设计方案

方案一:独立数据库模式

原理与特点

独立数据库模式为每个租户提供完全独立的数据库实例,是隔离级别最高的多租户方案。在这种模式下,租户数据完全分离,甚至可以部署在不同的服务器上。

实现步骤

  1. 创建多数据源配置:为每个租户配置独立的数据源
@Configuration
public class MultiTenantDatabaseConfig {@Autowiredprivate TenantDataSourceProperties properties;@Beanpublic DataSource dataSource() {AbstractRoutingDataSource multiTenantDataSource = new TenantAwareRoutingDataSource();Map<Object, Object> targetDataSources = new HashMap<>();// 为每个租户创建数据源for (TenantDataSourceProperties.TenantProperties tenant : properties.getTenants()) {DataSource tenantDataSource = createDataSource(tenant);targetDataSources.put(tenant.getTenantId(), tenantDataSource);}multiTenantDataSource.setTargetDataSources(targetDataSources);return multiTenantDataSource;}private DataSource createDataSource(TenantDataSourceProperties.TenantProperties tenant) {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(tenant.getUrl());dataSource.setUsername(tenant.getUsername());dataSource.setPassword(tenant.getPassword());dataSource.setDriverClassName(tenant.getDriverClassName());return dataSource;}
}
  1. 实现租户感知的数据源路由
public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return TenantContextHolder.getTenantId();}
}
  1. 租户上下文管理
public class TenantContextHolder {private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();public static void setTenantId(String tenantId) {CONTEXT.set(tenantId);}public static String getTenantId() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();}
}
  1. 添加租户识别拦截器
@Component
public class TenantIdentificationInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tenantId = extractTenantId(request);if (tenantId != null) {TenantContextHolder.setTenantId(tenantId);return true;}response.setStatus(HttpServletResponse.SC_BAD_REQUEST);return false;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {TenantContextHolder.clear();}private String extractTenantId(HttpServletRequest request) {// 从请求头中获取租户IDString tenantId = request.getHeader("X-TenantID");// 或者从子域名提取if (tenantId == null) {String host = request.getServerName();if (host.contains(".")) {tenantId = host.split("\.")[0];}}return tenantId;}
}
  1. 配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TenantIdentificationInterceptor tenantInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tenantInterceptor).addPathPatterns("/api/**");}
}
  1. 实现动态租户管理
@Entity
@Table(name = "tenant")
public class Tenant {@Idprivate String id;@Column(nullable = false)private String name;@Column(nullable = false)private String databaseUrl;@Column(nullable = false)private String username;@Column(nullable = false)private String password;@Column(nullable = false)private String driverClassName;@Columnprivate boolean active = true;// getters and setters
}@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {List<Tenant> findByActive(boolean active);
}@Service
public class TenantManagementService {@Autowiredprivate TenantRepository tenantRepository;@Autowiredprivate DataSource dataSource;@Autowiredprivate ApplicationContext applicationContext;// 用ConcurrentHashMap存储租户数据源private final Map<String, DataSource> tenantDataSources = new ConcurrentHashMap<>();@PostConstructpublic void initializeTenants() {List<Tenant> activeTenants = tenantRepository.findByActive(true);for (Tenant tenant : activeTenants) {addTenant(tenant);}}public void addTenant(Tenant tenant) {// 创建新的数据源HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(tenant.getDatabaseUrl());dataSource.setUsername(tenant.getUsername());dataSource.setPassword(tenant.getPassword());dataSource.setDriverClassName(tenant.getDriverClassName());// 存储数据源tenantDataSources.put(tenant.getId(), dataSource);// 更新路由数据源updateRoutingDataSource();// 保存租户信息到数据库tenantRepository.save(tenant);}public void removeTenant(String tenantId) {DataSource dataSource = tenantDataSources.remove(tenantId);if (dataSource != null && dataSource instanceof HikariDataSource) {((HikariDataSource) dataSource).close();}// 更新路由数据源updateRoutingDataSource();// 从数据库移除租户tenantRepository.deleteById(tenantId);}private void updateRoutingDataSource() {try {TenantAwareRoutingDataSource routingDataSource = (TenantAwareRoutingDataSource) dataSource;// 使用反射访问AbstractRoutingDataSource的targetDataSources字段Field targetDataSourcesField = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");targetDataSourcesField.setAccessible(true);Map<Object, Object> targetDataSources = new HashMap<>(tenantDataSources);targetDataSourcesField.set(routingDataSource, targetDataSources);// 调用afterPropertiesSet初始化数据源routingDataSource.afterPropertiesSet();} catch (Exception e) {throw new RuntimeException("Failed to update routing data source", e);}}
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantAdminController {@Autowiredprivate TenantManagementService tenantService;@GetMappingpublic List<Tenant> getAllTenants() {return tenantService.getAllTenants();}@PostMappingpublic ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {tenantService.addTenant(tenant);return ResponseEntity.status(HttpStatus.CREATED).body(tenant);}@DeleteMapping("/{tenantId}")public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {tenantService.removeTenant(tenantId);return ResponseEntity.noContent().build();}
}

优缺点分析

优点:

  • 数据隔离级别最高,安全性最佳
  • 租户可以使用不同的数据库版本或类型
  • 易于实现租户特定的数据库优化
  • 故障隔离,一个租户的数据库问题不影响其他租户
  • 便于独立备份、恢复和迁移

缺点:

  • 资源利用率较低,成本较高
  • 运维复杂度高,需要管理多个数据库实例
  • 跨租户查询困难
  • 每增加一个租户需要创建新的数据库实例
  • 数据库连接池管理复杂

适用场景

  • 高要求的企业级SaaS应用
  • 租户数量相对较少但数据量大的场景
  • 租户愿意支付更高费用获得更好隔离性的场景

方案二:共享数据库,独立Schema模式

原理与特点

在这种模式下,所有租户共享同一个数据库实例,但每个租户拥有自己独立的Schema(在PostgreSQL中)或数据库(在MySQL中)。这种方式在资源共享和数据隔离之间取得了平衡。

实现步骤

  1. 创建租户Schema配置
@Configuration
public class MultiTenantSchemaConfig {@Autowiredprivate DataSource dataSource;@Autowiredprivate TenantRepository tenantRepository;@PostConstructpublic void initializeSchemas() {for (Tenant tenant : tenantRepository.findByActive(true)) {createSchemaIfNotExists(tenant.getSchemaName());}}private void createSchemaIfNotExists(String schema) {try (Connection connection = dataSource.getConnection()) {// PostgreSQL语法,MySQL使用CREATE DATABASE IF NOT EXISTSString sql = "CREATE SCHEMA IF NOT EXISTS " + schema;try (Statement stmt = connection.createStatement()) {stmt.execute(sql);}} catch (SQLException e) {throw new RuntimeException("Failed to create schema: " + schema, e);}}
}
  1. 租户实体和存储
@Entity
@Table(name = "tenant")
public class Tenant {@Idprivate String id;@Column(nullable = false)private String name;@Column(nullable = false, unique = true)private String schemaName;@Columnprivate boolean active = true;// getters and setters
}@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {List<Tenant> findByActive(boolean active);Optional<Tenant> findBySchemaName(String schemaName);
}
  1. 配置Hibernate多租户支持
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {@Autowiredprivate DataSource dataSource;@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {Map<String, Object> properties = new HashMap<>();properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider());properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver());// 其他Hibernate配置...return builder.dataSource(dataSource).packages("com.example.entity").properties(properties).build();}@Beanpublic MultiTenantConnectionProvider multiTenantConnectionProvider() {return new SchemaBasedMultiTenantConnectionProvider();}@Beanpublic CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {return new TenantSchemaIdentifierResolver();}
}
  1. 实现多租户连接提供者
public class SchemaBasedMultiTenantConnectionProvider implements MultiTenantConnectionProvider {private static final long serialVersionUID = 1L;@Autowiredprivate DataSource dataSource;@Overridepublic Connection getAnyConnection() throws SQLException {return dataSource.getConnection();}@Overridepublic void releaseAnyConnection(Connection connection) throws SQLException {connection.close();}@Overridepublic Connection getConnection(String tenantIdentifier) throws SQLException {final Connection connection = getAnyConnection();try {// PostgreSQL语法,MySQL使用USE database_nameconnection.createStatement().execute(String.format("SET SCHEMA '%s'", tenantIdentifier));} catch (SQLException e) {throw new HibernateException("Could not alter JDBC connection to schema [" + tenantIdentifier + "]", e);}return connection;}@Overridepublic void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {try {// 恢复到默认Schemaconnection.createStatement().execute("SET SCHEMA 'public'");} catch (SQLException e) {// 忽略错误,确保连接关闭}connection.close();}@Overridepublic boolean supportsAggressiveRelease() {return false;}@Overridepublic boolean isUnwrappableAs(Class unwrapType) {return false;}@Overridepublic <T> T unwrap(Class<T> unwrapType) {return null;}
}
  1. 实现租户标识解析器
public class TenantSchemaIdentifierResolver implements CurrentTenantIdentifierResolver {private static final String DEFAULT_TENANT = "public";@Overridepublic String resolveCurrentTenantIdentifier() {String tenantId = TenantContextHolder.getTenantId();return tenantId != null ? tenantId : DEFAULT_TENANT;}@Overridepublic boolean validateExistingCurrentSessions() {return true;}
}
  1. 动态租户管理服务
@Service
public class TenantSchemaManagementService {@Autowiredprivate TenantRepository tenantRepository;@Autowiredprivate DataSource dataSource;@Autowiredprivate EntityManagerFactory entityManagerFactory;public void createTenant(Tenant tenant) {// 1. 创建SchemacreateSchemaIfNotExists(tenant.getSchemaName());// 2. 保存租户信息tenantRepository.save(tenant);// 3. 初始化Schema的表结构initializeSchema(tenant.getSchemaName());}public void deleteTenant(String tenantId) {Tenant tenant = tenantRepository.findById(tenantId).orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));// 1. 删除SchemadropSchema(tenant.getSchemaName());// 2. 删除租户信息tenantRepository.delete(tenant);}private void createSchemaIfNotExists(String schema) {try (Connection connection = dataSource.getConnection()) {String sql = "CREATE SCHEMA IF NOT EXISTS " + schema;try (Statement stmt = connection.createStatement()) {stmt.execute(sql);}} catch (SQLException e) {throw new RuntimeException("Failed to create schema: " + schema, e);}}private void dropSchema(String schema) {try (Connection connection = dataSource.getConnection()) {String sql = "DROP SCHEMA IF EXISTS " + schema + " CASCADE";try (Statement stmt = connection.createStatement()) {stmt.execute(sql);}} catch (SQLException e) {throw new RuntimeException("Failed to drop schema: " + schema, e);}}private void initializeSchema(String schemaName) {// 设置当前租户上下文String previousTenant = TenantContextHolder.getTenantId();try {TenantContextHolder.setTenantId(schemaName);// 使用JPA/Hibernate工具初始化Schema// 可以使用SchemaExport或更推荐使用Flyway/LiquibaseSession session = entityManagerFactory.createEntityManager().unwrap(Session.class);session.doWork(connection -> {// 执行DDL语句});} finally {// 恢复之前的租户上下文if (previousTenant != null) {TenantContextHolder.setTenantId(previousTenant);} else {TenantContextHolder.clear();}}}
}
  1. 租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantSchemaController {@Autowiredprivate TenantSchemaManagementService tenantService;@Autowiredprivate TenantRepository tenantRepository;@GetMappingpublic List<Tenant> getAllTenants() {return tenantRepository.findAll();}@PostMappingpublic ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {tenantService.createTenant(tenant);return ResponseEntity.status(HttpStatus.CREATED).body(tenant);}@DeleteMapping("/{tenantId}")public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {tenantService.deleteTenant(tenantId);return ResponseEntity.noContent().build();}
}

优缺点分析

优点:

  • 资源利用率高于独立数据库模式
  • 较好的数据隔离性
  • 运维复杂度低于独立数据库模式
  • 容易实现租户特定的表结构
  • 数据库级别的权限控制

缺点:

  • 数据库管理复杂度增加
  • 可能存在Schema数量限制
  • 跨租户查询仍然困难
  • 无法为不同租户使用不同的数据库类型
  • 所有租户共享数据库资源,可能出现资源争用

适用场景

  • 中型SaaS应用
  • 租户数量中等但增长较快的场景
  • 需要较好数据隔离但成本敏感的应用
  • PostgreSQL或MySQL等支持Schema/数据库隔离的数据库环境

方案三:共享数据库,共享Schema,独立表模式

原理与特点

在这种模式下,所有租户共享同一个数据库和Schema,但每个租户有自己的表集合,通常通过表名前缀或后缀区分不同租户的表。

实现步骤

  1. 实现多租户命名策略
@Component
public class TenantTableNameStrategy extends PhysicalNamingStrategyStandardImpl {private static final long serialVersionUID = 1L;@Overridepublic Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {String tenantId = TenantContextHolder.getTenantId();if (tenantId != null && !tenantId.isEmpty()) {String tablePrefix = tenantId + "_";return new Identifier(tablePrefix + name.getText(), name.isQuoted());}return super.toPhysicalTableName(name, context);}
}
  1. 配置Hibernate命名策略
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {@Autowiredprivate TenantTableNameStrategy tableNameStrategy;@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,DataSource dataSource) {Map<String, Object> properties = new HashMap<>();properties.put("hibernate.physical_naming_strategy", tableNameStrategy);// 其他Hibernate配置...return builder.dataSource(dataSource).packages("com.example.entity").properties(properties).build();}
}
  1. 租户实体和仓库
@Entity
@Table(name = "tenant_info") // 避免与租户表前缀冲突
public class Tenant {@Idprivate String id;@Column(nullable = false)private String name;@Columnprivate boolean active = true;// getters and setters
}@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {List<Tenant> findByActive(boolean active);
}
  1. 表初始化管理器
@Component
public class TenantTableManager {@Autowiredprivate EntityManagerFactory entityManagerFactory;@Autowiredprivate TenantRepository tenantRepository;@PersistenceContextprivate EntityManager entityManager;public void initializeTenantTables(String tenantId) {String previousTenant = TenantContextHolder.getTenantId();try {TenantContextHolder.setTenantId(tenantId);// 使用JPA/Hibernate初始化表结构// 在生产环境中,推荐使用Flyway或Liquibase进行更精细的控制Session session = entityManager.unwrap(Session.class);session.doWork(connection -> {// 执行建表语句// 这里可以使用Hibernate的SchemaExport,但为简化,直接使用SQL// 示例:创建用户表String createUserTable = "CREATE TABLE IF NOT EXISTS " + tenantId + "_users (" +"id BIGINT NOT NULL AUTO_INCREMENT, " +"username VARCHAR(255) NOT NULL, " +"email VARCHAR(255) NOT NULL, " +"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +"PRIMARY KEY (id)" +")";try (Statement stmt = connection.createStatement()) {stmt.execute(createUserTable);// 创建其他表...}});} finally {if (previousTenant != null) {TenantContextHolder.setTenantId(previousTenant);} else {TenantContextHolder.clear();}}}public void dropTenantTables(String tenantId) {// 获取数据库中所有表try (Connection connection = entityManager.unwrap(SessionImplementor.class).connection()) {DatabaseMetaData metaData = connection.getMetaData();String tablePrefix = tenantId + "_";try (ResultSet tables = metaData.getTables(connection.getCatalog(), connection.getSchema(), tablePrefix + "%", new String[]{"TABLE"})) {List<String> tablesToDrop = new ArrayList<>();while (tables.next()) {tablesToDrop.add(tables.getString("TABLE_NAME"));}// 删除所有表for (String tableName : tablesToDrop) {try (Statement stmt = connection.createStatement()) {stmt.execute("DROP TABLE " + tableName);}}}} catch (SQLException e) {throw new RuntimeException("Failed to drop tenant tables", e);}}
}
  1. 租户管理服务
@Service
public class TenantTableManagementService {@Autowiredprivate TenantRepository tenantRepository;@Autowiredprivate TenantTableManager tableManager;@PostConstructpublic void initializeAllTenants() {for (Tenant tenant : tenantRepository.findByActive(true)) {tableManager.initializeTenantTables(tenant.getId());}}@Transactionalpublic void createTenant(Tenant tenant) {// 1. 保存租户信息tenantRepository.save(tenant);// 2. 初始化租户表tableManager.initializeTenantTables(tenant.getId());}@Transactionalpublic void deleteTenant(String tenantId) {// 1. 删除租户表tableManager.dropTenantTables(tenantId);// 2. 删除租户信息tenantRepository.deleteById(tenantId);}
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantTableController {@Autowiredprivate TenantTableManagementService tenantService;@Autowiredprivate TenantRepository tenantRepository;@GetMappingpublic List<Tenant> getAllTenants() {return tenantRepository.findAll();}@PostMappingpublic ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {tenantService.createTenant(tenant);return ResponseEntity.status(HttpStatus.CREATED).body(tenant);}@DeleteMapping("/{tenantId}")public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {tenantService.deleteTenant(tenantId);return ResponseEntity.noContent().build();}
}

优缺点分析

优点:

  • 简单易实现,特别是对现有应用的改造
  • 资源利用率高
  • 跨租户查询相对容易实现
  • 维护成本低
  • 租户间表结构可以不同

缺点:

  • 数据隔离级别较低
  • 随着租户数量增加,表数量会急剧增长
  • 数据库对象(如表、索引)数量可能达到数据库限制
  • 备份和恢复单个租户数据较为复杂
  • 可能需要处理表名长度限制问题

适用场景

  • 租户数量适中且表结构相对简单的SaaS应用
  • 需要为不同租户提供不同表结构的场景
  • 快速原型开发或MVP(最小可行产品)
  • 从单租户向多租户过渡的系统

方案四:共享数据库,共享Schema,共享表模式

原理与特点

这是隔离级别最低但资源效率最高的方案。所有租户共享相同的数据库、Schema和表,通过在每个表中添加"租户ID"列来区分不同租户的数据。

实现步骤

  1. 创建租户感知的实体基类
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class TenantAwareEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "tenant_id", nullable = false)private String tenantId;@CreatedDate@Column(name = "created_at", updatable = false)private LocalDateTime createdAt;@LastModifiedDate@Column(name = "updated_at")private LocalDateTime updatedAt;@PrePersistpublic void onPrePersist() {tenantId = TenantContextHolder.getTenantId();}
}
  1. 租户实体和仓库
@Entity
@Table(name = "tenants")
public class Tenant {@Idprivate String id;@Column(nullable = false)private String name;@Columnprivate boolean active = true;// getters and setters
}@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {List<Tenant> findByActive(boolean active);
}
  1. 实现租户数据过滤器
@Component
public class TenantFilterInterceptor implements HandlerInterceptor {@Autowiredprivate EntityManager entityManager;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tenantId = TenantContextHolder.getTenantId();if (tenantId != null) {// 设置Hibernate过滤器Session session = entityManager.unwrap(Session.class);Filter filter = session.enableFilter("tenantFilter");filter.setParameter("tenantId", tenantId);return true;}response.setStatus(HttpServletResponse.SC_BAD_REQUEST);return false;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {Session session = entityManager.unwrap(Session.class);session.disableFilter("tenantFilter");}
}
  1. 为实体添加过滤器注解
@Entity
@Table(name = "users")
@FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")
})
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class User extends TenantAwareEntity {@Column(name = "username", nullable = false)private String username;@Column(name = "email", nullable = false)private String email;// 其他字段和方法...
}
  1. 租户管理服务
@Service
public class SharedTableTenantService {@Autowiredprivate TenantRepository tenantRepository;@Autowiredprivate EntityManager entityManager;@Transactionalpublic void createTenant(Tenant tenant) {// 直接保存租户信息tenantRepository.save(tenant);// 初始化租户默认数据initializeTenantData(tenant.getId());}@Transactionalpublic void deleteTenant(String tenantId) {// 删除该租户的所有数据deleteAllTenantData(tenantId);// 删除租户记录tenantRepository.deleteById(tenantId);}private void initializeTenantData(String tenantId) {String previousTenant = TenantContextHolder.getTenantId();try {TenantContextHolder.setTenantId(tenantId);// 创建默认用户、角色等// ...} finally {if (previousTenant != null) {TenantContextHolder.setTenantId(previousTenant);} else {TenantContextHolder.clear();}}}private void deleteAllTenantData(String tenantId) {// 获取所有带有tenant_id列的表List<String> tables = getTablesWithTenantIdColumn();// 从每个表中删除该租户的数据for (String table : tables) {entityManager.createNativeQuery("DELETE FROM " + table + " WHERE tenant_id = :tenantId").setParameter("tenantId", tenantId).executeUpdate();}}private List<String> getTablesWithTenantIdColumn() {List<String> tables = new ArrayList<>();try (Connection connection = entityManager.unwrap(SessionImplementor.class).connection()) {DatabaseMetaData metaData = connection.getMetaData();try (ResultSet rs = metaData.getTables(connection.getCatalog(), connection.getSchema(), "%", new String[]{"TABLE"})) {while (rs.next()) {String tableName = rs.getString("TABLE_NAME");// 检查表是否有tenant_id列try (ResultSet columns = metaData.getColumns(connection.getCatalog(), connection.getSchema(), tableName, "tenant_id")) {if (columns.next()) {tables.add(tableName);}}}}} catch (SQLException e) {throw new RuntimeException("Failed to get tables with tenant_id column", e);}return tables;}
}
  1. 租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class SharedTableTenantController {@Autowiredprivate SharedTableTenantService tenantService;@Autowiredprivate TenantRepository tenantRepository;@GetMappingpublic List<Tenant> getAllTenants() {return tenantRepository.findAll();}@PostMappingpublic ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {tenantService.createTenant(tenant);return ResponseEntity.status(HttpStatus.CREATED).body(tenant);}@DeleteMapping("/{tenantId}")public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {tenantService.deleteTenant(tenantId);return ResponseEntity.noContent().build();}
}

优缺点分析

优点:

  • 资源利用率最高
  • 维护成本最低
  • 实现简单,对现有单租户系统改造容易
  • 跨租户查询简单
  • 节省存储空间,特别是当数据量小时

缺点:

  • 数据隔离级别最低
  • 安全风险较高,一个错误可能导致跨租户数据泄露
  • 所有租户共享相同的表结构
  • 需要在所有数据访问层强制租户过滤

适用场景

  • 租户数量多但每个租户数据量小的场景
  • 成本敏感的应用
  • 原型验证或MVP阶段

方案五:混合租户模式

原理与特点

混合租户模式结合了多种隔离策略,根据租户等级、重要性或特定需求为不同租户提供不同级别的隔离。例如,免费用户可能使用共享表模式,而付费企业用户可能使用独立数据库模式。

实现步骤

  1. 租户类型和存储
@Entity
@Table(name = "tenants")
public class Tenant {@Idprivate String id;@Column(nullable = false)private String name;@Enumerated(EnumType.STRING)@Column(nullable = false)private TenantType type;@Columnprivate String databaseUrl;@Columnprivate String username;@Columnprivate String password;@Columnprivate String driverClassName;@Columnprivate String schemaName;@Columnprivate boolean active = true;public enum TenantType {DEDICATED_DATABASE,DEDICATED_SCHEMA,DEDICATED_TABLE,SHARED_TABLE}// getters and setters
}@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {List<Tenant> findByActive(boolean active);List<Tenant> findByType(Tenant.TenantType type);
}
  1. 创建租户分类策略
@Component
public class TenantIsolationStrategy {@Autowiredprivate TenantRepository tenantRepository;private final Map<String, Tenant> tenantCache = new ConcurrentHashMap<>();@PostConstructpublic void loadTenants() {tenantRepository.findByActive(true).forEach(tenant -> tenantCache.put(tenant.getId(), tenant));}public Tenant.TenantType getIsolationTypeForTenant(String tenantId) {Tenant tenant = tenantCache.get(tenantId);if (tenant == null) {tenant = tenantRepository.findById(tenantId).orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));tenantCache.put(tenantId, tenant);}return tenant.getType();}public Tenant getTenant(String tenantId) {Tenant tenant = tenantCache.get(tenantId);if (tenant == null) {tenant = tenantRepository.findById(tenantId).orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));tenantCache.put(tenantId, tenant);}return tenant;}public void evictFromCache(String tenantId) {tenantCache.remove(tenantId);}
}
  1. 实现混合数据源路由
@Component
public class HybridTenantRouter {@Autowiredprivate TenantIsolationStrategy isolationStrategy;private final Map<String, DataSource> dedicatedDataSources = new ConcurrentHashMap<>();@Autowiredprivate DataSource sharedDataSource;public DataSource getDataSourceForTenant(String tenantId) {Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {// 对于独立数据库的租户,查找或创建专用数据源return dedicatedDataSources.computeIfAbsent(tenantId, this::createDedicatedDataSource);}return sharedDataSource;}private DataSource createDedicatedDataSource(String tenantId) {Tenant tenant = isolationStrategy.getTenant(tenantId);HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(tenant.getDatabaseUrl());dataSource.setUsername(tenant.getUsername());dataSource.setPassword(tenant.getPassword());dataSource.setDriverClassName(tenant.getDriverClassName());return dataSource;}public void removeDedicatedDataSource(String tenantId) {DataSource dataSource = dedicatedDataSources.remove(tenantId);if (dataSource instanceof HikariDataSource) {((HikariDataSource) dataSource).close();}}
}
  1. 混合租户路由数据源
public class HybridRoutingDataSource extends AbstractRoutingDataSource {@Autowiredprivate HybridTenantRouter tenantRouter;@Autowiredprivate TenantIsolationStrategy isolationStrategy;@Overrideprotected Object determineCurrentLookupKey() {String tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return "default";}Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {return tenantId;}return "shared";}@Overrideprotected DataSource determineTargetDataSource() {String tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return super.determineTargetDataSource();}return tenantRouter.getDataSourceForTenant(tenantId);}
}
  1. 混合租户拦截器
@Component
public class HybridTenantInterceptor implements HandlerInterceptor {@Autowiredprivate TenantIsolationStrategy isolationStrategy;@Autowiredprivate EntityManager entityManager;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tenantId = extractTenantId(request);if (tenantId != null) {TenantContextHolder.setTenantId(tenantId);Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);// 根据隔离类型应用不同策略switch (isolationType) {case DEDICATED_DATABASE:// 已由数据源路由处理break;case DEDICATED_SCHEMA:setSchema(isolationStrategy.getTenant(tenantId).getSchemaName());break;case DEDICATED_TABLE:// 由命名策略处理break;case SHARED_TABLE:enableTenantFilter(tenantId);break;}return true;}response.setStatus(HttpServletResponse.SC_BAD_REQUEST);return false;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {String tenantId = TenantContextHolder.getTenantId();if (tenantId != null) {Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);if (isolationType == Tenant.TenantType.SHARED_TABLE) {disableTenantFilter();}}TenantContextHolder.clear();}private void setSchema(String schema) {try {entityManager.createNativeQuery("SET SCHEMA '" + schema + "'").executeUpdate();} catch (Exception e) {// 处理异常}}private void enableTenantFilter(String tenantId) {Session session = entityManager.unwrap(Session.class);Filter filter = session.enableFilter("tenantFilter");filter.setParameter("tenantId", tenantId);}private void disableTenantFilter() {Session session = entityManager.unwrap(Session.class);session.disableFilter("tenantFilter");}private String extractTenantId(HttpServletRequest request) {// 从请求中提取租户ID的逻辑return request.getHeader("X-TenantID");}
}
  1. 综合租户管理服务
@Service
public class HybridTenantManagementService {@Autowiredprivate TenantRepository tenantRepository;@Autowiredprivate TenantIsolationStrategy isolationStrategy;@Autowiredprivate HybridTenantRouter tenantRouter;@Autowiredprivate EntityManager entityManager;@Autowiredprivate DataSource dataSource;// 不同隔离类型的初始化策略private final Map<Tenant.TenantType, TenantInitializer> initializers = new HashMap<>();@PostConstructpublic void init() {initializers.put(Tenant.TenantType.DEDICATED_DATABASE, this::initializeDedicatedDatabase);initializers.put(Tenant.TenantType.DEDICATED_SCHEMA, this::initializeDedicatedSchema);initializers.put(Tenant.TenantType.DEDICATED_TABLE, this::initializeDedicatedTables);initializers.put(Tenant.TenantType.SHARED_TABLE, this::initializeSharedTables);}@Transactionalpublic void createTenant(Tenant tenant) {// 1. 保存租户基本信息tenantRepository.save(tenant);// 2. 根据隔离类型初始化TenantInitializer initializer = initializers.get(tenant.getType());if (initializer != null) {initializer.initialize(tenant);}// 3. 更新缓存isolationStrategy.evictFromCache(tenant.getId());}@Transactionalpublic void deleteTenant(String tenantId) {Tenant tenant = tenantRepository.findById(tenantId).orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));// 1. 根据隔离类型清理资源switch (tenant.getType()) {case DEDICATED_DATABASE:cleanupDedicatedDatabase(tenant);break;case DEDICATED_SCHEMA:cleanupDedicatedSchema(tenant);break;case DEDICATED_TABLE:cleanupDedicatedTables(tenant);break;case SHARED_TABLE:cleanupSharedTables(tenant);break;}// 2. 删除租户信息tenantRepository.delete(tenant);// 3. 更新缓存isolationStrategy.evictFromCache(tenantId);}// 独立数据库初始化private void initializeDedicatedDatabase(Tenant tenant) {// 创建数据源DataSource dedicatedDs = tenantRouter.getDataSourceForTenant(tenant.getId());// 初始化数据库结构try (Connection conn = dedicatedDs.getConnection()) {// 执行DDL脚本// ...} catch (SQLException e) {throw new RuntimeException("Failed to initialize database for tenant: " + tenant.getId(), e);}}// Schema初始化private void initializeDedicatedSchema(Tenant tenant) {try (Connection conn = dataSource.getConnection()) {// 创建Schematry (Statement stmt = conn.createStatement()) {stmt.execute("CREATE SCHEMA IF NOT EXISTS " + tenant.getSchemaName());}// 切换到该Schemaconn.setSchema(tenant.getSchemaName());// 创建表结构// ...} catch (SQLException e) {throw new RuntimeException("Failed to initialize schema for tenant: " + tenant.getId(), e);}}// 独立表初始化private void initializeDedicatedTables(Tenant tenant) {// 设置线程上下文中的租户ID以使用正确的表名前缀String previousTenant = TenantContextHolder.getTenantId();try {TenantContextHolder.setTenantId(tenant.getId());// 创建表// ...} finally {if (previousTenant != null) {TenantContextHolder.setTenantId(previousTenant);} else {TenantContextHolder.clear();}}}// 共享表初始化private void initializeSharedTables(Tenant tenant) {// 共享表模式下,只需插入租户特定的初始数据String previousTenant = TenantContextHolder.getTenantId();try {TenantContextHolder.setTenantId(tenant.getId());// 插入初始数据// ...} finally {if (previousTenant != null) {TenantContextHolder.setTenantId(previousTenant);} else {TenantContextHolder.clear();}}}// 清理方法private void cleanupDedicatedDatabase(Tenant tenant) {// 关闭并移除数据源tenantRouter.removeDedicatedDataSource(tenant.getId());// 注意:通常不会自动删除实际的数据库,这需要DBA手动操作}private void cleanupDedicatedSchema(Tenant tenant) {try (Connection conn = dataSource.getConnection()) {try (Statement stmt = conn.createStatement()) {stmt.execute("DROP SCHEMA IF EXISTS " + tenant.getSchemaName() + " CASCADE");}} catch (SQLException e) {throw new RuntimeException("Failed to drop schema for tenant: " + tenant.getId(), e);}}private void cleanupDedicatedTables(Tenant tenant) {// 查找并删除该租户的所有表try (Connection conn = dataSource.getConnection()) {DatabaseMetaData metaData = conn.getMetaData();String tablePrefix = tenant.getId() + "_";try (ResultSet tables = metaData.getTables(conn.getCatalog(), conn.getSchema(), tablePrefix + "%", new String[]{"TABLE"})) {while (tables.next()) {String tableName = tables.getString("TABLE_NAME");try (Statement stmt = conn.createStatement()) {stmt.execute("DROP TABLE " + tableName);}}}} catch (SQLException e) {throw new RuntimeException("Failed to drop tables for tenant: " + tenant.getId(), e);}}private void cleanupSharedTables(Tenant tenant) {// 从所有带有tenant_id列的表中删除该租户的数据entityManager.createNativeQuery("SELECT table_name FROM information_schema.columns " +"WHERE column_name = 'tenant_id'").getResultList().forEach(tableName -> entityManager.createNativeQuery("DELETE FROM " + tableName + " WHERE tenant_id = :tenantId").setParameter("tenantId", tenant.getId()).executeUpdate());}// 租户初始化策略接口@FunctionalInterfaceprivate interface TenantInitializer {void initialize(Tenant tenant);}
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class HybridTenantController {@Autowiredprivate HybridTenantManagementService tenantService;@Autowiredprivate TenantRepository tenantRepository;@GetMappingpublic List<Tenant> getAllTenants() {return tenantRepository.findAll();}@PostMappingpublic ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {tenantService.createTenant(tenant);return ResponseEntity.status(HttpStatus.CREATED).body(tenant);}@PutMapping("/{tenantId}")public ResponseEntity<Tenant> updateTenant(@PathVariable String tenantId, @RequestBody Tenant tenant) {tenant.setId(tenantId);tenantService.updateTenant(tenant);return ResponseEntity.ok(tenant);}@DeleteMapping("/{tenantId}")public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {tenantService.deleteTenant(tenantId);return ResponseEntity.noContent().build();}@GetMapping("/types")public ResponseEntity<List<Tenant.TenantType>> getTenantTypes() {return ResponseEntity.ok(Arrays.asList(Tenant.TenantType.values()));}
}

优缺点分析

优点:

  • 最大的灵活性,可根据租户需求提供不同隔离级别
  • 可以实现资源和成本的平衡
  • 可以根据业务价值分配资源
  • 适应不同客户的安全和性能需求

缺点:

  • 实现复杂度最高
  • 维护和测试成本高
  • 需要处理多种数据访问模式
  • 可能引入不一致的用户体验
  • 错误处理更加复杂

适用场景

  • 需要提供灵活定价模型的应用
  • 资源需求差异大的租户集合

方案对比

隔离模式数据隔离级别资源利用率成本复杂度适用场景
独立数据库最高企业级应用、金融/医疗行业
独立Schema中型SaaS、安全要求较高的场景
独立表中高中低中小型应用、原型验证
共享表最高大量小租户、成本敏感场景
混合模式可变可变中高多层级服务、复杂业务需求

总结

多租户架构是构建现代SaaS应用的关键技术,选择多租户模式需要平衡数据隔离、资源利用、成本和复杂度等多种因素。

通过深入理解这些架构模式及其权衡,可以根据实际情况选择适合的多租户架构,构建可扩展、安全且经济高效的企业级应用。

相关文章:

SpringBoot多租户系统的5种架构设计方案

多租户(Multi-tenancy)是一种软件架构模式&#xff0c;允许单个应用实例服务于多个客户(租户)&#xff0c;同时保持租户数据的隔离性和安全性。 通过合理的多租户设计&#xff0c;企业可以显著降低运维成本、提升资源利用率&#xff0c;并实现更高效的服务交付。 本文将分享S…...

数据分析实战1(Excel制作报表)

Excel数据链接&#xff1a;【课程4.0】第2章_Excel.zip - 飞书云文档 1、拿到数据第一步 备份数据 ctrlshiftL&#xff1a;筛选 相关快捷键&#xff1a;&#xff08;alt&#xff1a;自动求和、ctrlshift5&#xff1a;转换为%&#xff09; 2、环比、同比 环比&#xff08;本…...

本地部署大模型llm+RAG向量检索问答系统 deepseek chatgpt

项目视频讲解: 本地部署大模型llm+RAG向量检索问答系统 deepseek chatgpt_哔哩哔哩_bilibili 运行结果:...

设备健康管理的战略升维:用预测性维护重构企业竞争力

第一章 传统维护的沉默成本&#xff1a;被低估的利润黑洞 当轴承振动值突破安全阈值时&#xff0c;制造企业损失的远非维修费用。某重型装备制造厂的案例揭示了典型多米诺效应&#xff1a;传动系统突发故障导致36小时停产&#xff0c;触发订单违约金&#xff08;合约金额的9%&…...

Redis事务详解:原理、使用与注意事项

文章目录 Redis事务详解&#xff1a;原理、使用与注意事项什么是Redis事务Redis事务的基本使用基本事务示例事务执行过程 Redis事务的错误处理1. 入队错误2. 执行错误 WATCH命令&#xff1a;乐观锁实现Redis事务的局限性事务的最佳实践Lua脚本总结 Redis事务详解&#xff1a;原…...

提升 GitHub Stats 的 6 个关键策略

哈哈&#xff0c;GitHub 的 “B-” 评级 其实是个玄学问题&#xff0c;但确实有一些 快速提升的技巧&#xff01;你的数据看起来 提交数&#xff08;147&#xff09;和 PR&#xff08;9&#xff09;不算少&#xff0c;但 Stars&#xff08;21&#xff09;和贡献项目数&#xff…...

CSS Animation 详解

CSS Animation 允许元素平滑地从一个样式状态过渡到另一个样式状态。通过设置关键帧&#xff08;keyframes&#xff09;&#xff0c;可以控制动画序列中的中间步骤。 一、核心概念 1.关键帧&#xff08;Keyframes&#xff09; 使用 keyframes 规则定义动画序列通过百分比或 …...

LabVIEW 中内存释放相关问题

在LabVIEW 编程领域&#xff0c;内存管理是一个关键且复杂的议题。我们常常关注 LabVIEW 如何将内存释放回操作系统&#xff08;OS&#xff09;&#xff0c;以及是否有方法确保在特定数据结构&#xff08;如队列、变体属性、动态数据引用 DVR 等&#xff09;销毁、删除或清空后…...

【HarmonyOS 5】鸿蒙中的UIAbility详解(三)

【HarmonyOS 5】鸿蒙中的UIAbility详解&#xff08;三&#xff09; 一、前言 本文是鸿蒙中的UIAbility详解系列的最终章。主要针对UIAbility的冷启动和热启动&#xff0c;对于want数据的处理。UIAbility的备份恢复&#xff0c;UIAbility的接续等高级功能的概念和使用讲解。 …...

基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案

在使用 OpenAI、Claude、Gemini 等大语言模型 API 构建对话系统时&#xff0c;开发者普遍面临成本不断上升的挑战。无论是基于检索增强生成&#xff08;RAG&#xff09;的应用还是独立的对话系统&#xff0c;这些系统都需要维护对话历史以确保上下文的连贯性&#xff0c;类似于…...

vue-11(命名路由和命名视图)

命名路由和命名视图 命名路由和命名视图提供了组织和导航 Vue.js 应用程序的强大方法&#xff0c;尤其是在它们的复杂性增加时。它们提供了一种语义更合理、可维护的路由方法&#xff0c;使您的代码更易于理解和修改。命名路由允许您按名称引用路由&#xff0c;而不是依赖 URL…...

(附代码)自定义 LangChain 文档分割器,深入探索 LangChain 文档分割策略与应用

自定义文档分割器 在 LangChain 中&#xff0c;如果内置的文档分割器均没办法完成需求&#xff0c;还可以根据特定的需求实现自定义文档分割器&#xff08;一般极少&#xff09;&#xff0c;实现的方法也非常简单&#xff0c;继承文本分割器基类 TextSplitter&#xff0c;在构造…...

Python打卡训练营Day42

DAY 42 Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 import torch import torch.nn as nn import torch.nn.functional as F import torchvision import torchvision.transforms as tr…...

基于微信小程序的scratch学习系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…...

MATLAB实战:机器学习分类回归示例

以下是一个使用MATLAB的Statistics and Machine Learning Toolbox实现分类和回归任务的完整示例代码。代码包含鸢尾花分类、手写数字分类和汽车数据回归任务&#xff0c;并评估模型性能。 %% 加载内置数据集 % 鸢尾花数据集&#xff08;分类&#xff09; load fisheriris; X_i…...

动态库导出符号与extern “C“

1. windows下动态库导出符号 根据C/C语法规则&#xff0c;函数声明中的修饰符&#xff08;如__declspec(dllexport)&#xff09;可以放在返回类型之前或返回类型之后、函数名之前。这两种方式在功能上是等价的&#xff0c;编译器会以相同的方式处理。 __declspec(dllexport) …...

小知识:STM32 printf 重定向(串口输出)--让数据 “开口说话” 的关键技巧

引言 在 C 语言开发中&#xff0c;printf函数是我们调试程序、输出数据的得力助手&#xff0c;它能将格式化的数据输出到标准输出设备&#xff08;通常是屏幕&#xff09;。然而&#xff0c;在嵌入式领域&#xff0c;STM32 单片机并没有默认的显示设备&#xff0c;要让printf函…...

`docker commit` 和 `docker save`区别

理解 docker commit 和 docker save 之间的区别对于正确管理 Docker 镜像非常重要。让我们详细解释一下这两个命令的作用及其区别。 1. docker commit 作用&#xff1a; docker commit roop-builder roop:v1 命令的作用是基于一个正在运行的容器 roop-builder 创建一个新的镜…...

【C++ 多态】—— 礼器九鼎,釉下乾坤,多态中的 “风水寻龙诀“

欢迎来到一整颗红豆的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由一整颗红豆原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创…...

SCSAI平台面向对象建模技术的设计与实现

一、核心设计思想 SCSAI平台的核心目标是通过元建模&#xff08;Meta-Modeling&#xff09;技术实现面向对象建模的零编码化。其核心思想为&#xff1a; 自反性设计&#xff1a;定义ObjectClassInfo (OCI)为元类&#xff08;Meta-Class&#xff09;&#xff0c;所有对象类均为…...

pikachu通关教程-CSRF

CSRF(get) 用bp进行抓包 选择action value值的修改 点击test in browser copy然后放在bp代理的浏览器上&#xff0c;会出现一个提交按钮&#xff0c;这时候点击之后信息就被修改了。 CSRF(post) 请求的方式不同&#xff0c;其他都是一样 CSRF Token 存在cookie 首先要先下载一…...

智能体觉醒:AI开始自己“动手”了-自主进化开启任务革命时代

1. 智能体&#xff1a;AI从“工具”到“伙伴”的关键跃迁 1.1 什么是智能体&#xff1f; 智能体&#xff08;Agent&#xff09;是AI的“进化版”——它不再局限于生成文字或图像&#xff0c;而是能像人类一样“规划任务”“调用工具”甚至“协同合作”。例如&#xff0c;一个…...

Python爬虫实战:研究Aiohttp库相关技术

1. 引言 1.1 研究背景与意义 随着互联网的快速发展,网络上的数据量呈爆炸式增长。爬虫作为一种自动获取网络信息的工具,在数据挖掘、信息检索、舆情分析等领域有着广泛的应用。传统的同步爬虫在面对大量 URL 时效率低下,无法充分利用现代计算机的多核资源和网络带宽。而异…...

【C++指南】C++ list容器完全解读(二):list模拟实现,底层架构揭秘

. &#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 文章目录 引言一、链表节点设计&#xff1a;双向链表的基石1.1 节点类的实现 二、list框架与核心成员函…...

[神经网络]使用olivettiface数据集进行训练并优化,观察对比loss结果

结合归一化和正则化来优化网络模型结构&#xff0c;观察对比loss结果 搭建的神经网络&#xff0c;使用olivettiface数据集进行训练&#xff0c;结合归一化和正则化来优化网络模型结构&#xff0c;观察对比loss结果 from sklearn.datasets import fetch_olivetti_faces #倒入数…...

小明的Java面试奇遇之智能家装平台架构设计与JVM调优实战

一、文章标题 小明的Java面试奇遇之智能家装平台架构设计与JVM调优实战 二、文章标签 Java面试, 智能家装, 微服务架构, 高并发设计, JVM调优, SpringCloud, 消息队列, 分布式缓存, 架构设计, 面试技巧 三、文章概述 本文模拟了程序员小明应聘智能家装平台后端架构师的5轮…...

n8n:技术团队的智能工作流自动化助手

在当前数字化时代,自动化已经成为提高效率和减轻人工工作负担的一大推动力。今天,我们要为大家介绍一款极具潜力的开源项目——n8n,它不仅拥有广泛的应用场景,还具备内置AI功能,能够完全满足技术团队的高效工作需求。n8n的出现,为技术团队提供了自由编程与快速自动化构建…...

Flink 核心机制与源码剖析系列

Flink 核心机制与源码剖析系列 目录 第一篇&#xff1a;Flink 状态管理原理与源码深度剖析第二篇&#xff1a;水位线、事件时间与定时器源码全流程第三篇&#xff1a;Flink CEP 模式建模与高效事件匹配机制 第一篇&#xff1a;Flink 状态管理原理与源码深度剖析 1. 背景与意…...

华院计算出席信创论坛,分享AI教育创新实践并与燧原科技共同推出教育一体机

5月21日&#xff0c;信创论坛于上海漕河泾会议中心举办。本次论坛以“聚力融合&#xff0c;繁荣生态”为主题&#xff0c;话题聚焦工业制造、交通运输、金融、教育、医疗等领域。华院计算技术&#xff08;上海&#xff09;股份有限公司&#xff08;以下简称“华院计算”&#x…...

华为OD机试真题——会议接待 /代表团坐车(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《会议…...