宁睿 发表于 2024-6-13 21:11:55

财务管理系统之saas多租户架构是什么以及分库分表以及如何选择分布式事件方

saas是什么

SaaS(Software as a Service,软件即服务)是一种软件分发模子,在这种模子中,软件应用由云服务提供商托管并通过互联网向终端用户提供。用户通常通过订阅的方式获得软件服务,而不是购买和安装在当地计算机上。SaaS模子的典范特点包括:

[*]访问方式
用户通过网络浏览器、API或轻量级客户端访问软件,无需在当地安装特定的软件或应用步调。
[*]成本效益
用户按利用量或通过订阅模式支付费用,避免了传统软件的高昂前期购买成本和维护费用。
[*]可扩展性
SaaS应用通常提供灵活的订阅选项,用户可以根据需要轻松增长或淘汰服务。
[*]多租户模子
一个实例的应用服务可以同时服务于多个客户,每个客户的数据和配置信息在逻辑上是隔离的,但物理上大概存储在同一服务器上。
财务系统saas架构怎么设计


[*]多租户架构


[*]数据隔离:采取物理隔离(每个租户一个数据库)或逻辑隔离(共享数据库,但数据行级别隔离)确保数据安全。
[*]配置隔离:答应每个租户根据需要自界说应用的某些方面,如UI主题、功能模块开关等。

[*]微服务架构


[*]服务拆分:将财务系统拆分为独立的微服务,如账户管理、支付处置惩罚、报表生成等,每个服务负责一块独立的业务逻辑。
[*]服务通信:采取REST API或消息队列(如Kafka、RabbitMQ)实现服务间的异步通信。

[*]安全性


[*]认证与授权:集成OAuth2和JWT进行安全的用户认证和授权。
[*]数据加密:对敏感数据进行加密存储,并利用HTTPS掩护数据传输过程。
[*]审计日志:记录全部用户操纵和系统变乱,以便于事后审计和监控。
具体相识多租户


[*]数据隔离
数据隔离是多租户系统的核心,确保一个租户的数据不会被其他租户访问。常见的数据隔离方法有:


[*]物理隔离:为每个租户提供独立的数据库实例。
[*]逻辑隔离:全部租户共享同一个数据库,但数据表中包罗租户标识符(Tenant ID)来区分差别租户的数据。
@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   
    @Column(name = "tenant_id")
    private String tenantId;
   
    // 其他属性和方法
}
在查询时,总是包罗租户ID作为查询条件:
public List<Customer> findByTenantId(String tenantId) {
    // 使用JPA、MyBatis或其他ORM工具根据tenantId查询数据
}
2.认证和授权
多租户系统需要强大的认证和授权机制,确保用户只能访问授权的数据和功能。


[*]认证:可以利用OAuth2、OpenID Connect等标准协议进行用户认证。
[*]授权:在应用层面查抄用户是否有权访问特定的数据或实验操纵。
[*]码示例(Spring Security):
配置Spring Security利用JWT进行认证,并根据租户ID和用户角色授权:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
      // 其他配置...
      .authorizeRequests()
      .antMatchers("/api/**").access("hasRole('USER') and @tenantSecurity.hasTenantId(authentication)")
      .anyRequest().authenticated()
      .and()
      .addFilter(new JWTAuthenticationFilter(authenticationManager()));
}
3 配置和定制
多租户系统通常需要支持按租户定制配置,如UI主题、功能开关等。


[*]数据库存储配置:在数据库中为每个租户存储配置信息。
[*]应用级别缓存:缓存租户的配置信息,淘汰数据库访问。
代码示例(租户配置获取):
public class TenantConfigService {
    @Autowired
    private TenantConfigRepository configRepository;
   
    public TenantConfig getConfig(String tenantId) {
      // 从数据库或缓存中获取租户配置
      return configRepository.findByTenantId(tenantId);
    }
}
4.4. 多租户上下文
在处置惩罚请求时,识别当前的租户ID并在整个请求处置惩罚过程中保持租户上下文是非常重要的。


[*]租户识别:可以通过请求的子域名、URL路径、HTTP头或其他方式识别租户。
[*]上下文传递:利用ThreadLocal或Spring的RequestContextHolder传递租户上下文。
代码示例(租户上下文):
public class TenantContext {
    private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
   
    public static void setTenantId(String tenantId) {
      currentTenant.set(tenantId);
    }
   
    public static String getTenantId() {
      return currentTenant.get();
    }
   
    public static void clear() {
      currentTenant.remove();
    }
}


在请求拦截器中设置租户ID:
public class TenantIdentificationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      String tenantId = request
管理复杂数据源 分库分表

在面临大数据量和高并发场景时,数据库的分库分表是常见的解决方案,它可以有用地进步数据库的查询服从和数据写入能力。多租户架构中实现分库分表,需要考虑租户隔离、数据一致性、查询服从等因素。以下是一些设计思路和代码示例,帮助实现多租户环境下的数据库分库分表策略。
设计思路

[*] 租户识别:首先,需要一种机制来识别差别的租户请求,这可以通过域名、请求头、大概是API的路径参数来实现。
[*] 动态数据源:根据租户识别的效果,动态选择对应的数据库(分库)。
[*] 分表策略:在单个数据库内部,根据数据的访问模式和数据量,设计合理的分表策略,如按时间分表、按业务范例分表等。
[*] 数据路由:在应用层实现数据路由逻辑,确保数据写入到正确的表中。
[*] 查询聚合:对于跨表或跨库的查询,需要在应用层进行数据的聚合和处置惩罚。
[*] 实现示例
假设我们利用Spring Boot作为应用框架,以下是一些简化的代码示例,展示如何实现多租户下的分库分表。
1.租户识别
public class TenantInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      // 示例:通过请求头识别租户ID
      String tenantId = request.getHeader("X-Tenant-ID");
      TenantContext.setCurrentTenant(tenantId);
      return true;
    }
}
2.动态数据源
public class TenantRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
      // 根据租户上下文决定使用哪个数据源
      return TenantContext.getCurrentTenant();
    }
}

[*]分表策略
分表策略可以根据具体业务需求设计,这里不睁开具体代码。通常,可以在插入和查询数据时,根据某些字段(如日期、ID范围)动态选择表名。
[*]数据路由
在数据访问层(如Repository或Mapper),根据分表策略动态构建SQL语句,指向正确的表。
public class OrderRepository {

    public void save(Order order) {
      // 根据订单日期计算表名
      String tableName = "orders_" + order.getDate().format(DateTimeFormatter.ofPattern("yyyyMM"));
      String sql = "INSERT INTO " + tableName + " (...) VALUES (...)";
      // 执行SQL
    }
}

[*]查询聚合
跨表或跨库的查询需要在应用层手动聚合数据。
public class OrderService {

    public List<Order> findOrdersByDateRange(LocalDate start, LocalDate end) {
      List<Order> result = new ArrayList<>();
      // 计算时间范围内的所有表名
      List<String> tableNames = calculateTableNames(start, end);
      for (String tableName : tableNames) {
            String sql = "SELECT * FROM " + tableName + " WHERE date BETWEEN ? AND ?";
            // 执行查询并聚合结果
            result.addAll(executeQuery(sql, start, end));
      }
      return result;
    }
}
注意事项


[*] 事件管理:在分库分表的环境下,跨库事件管理变得复杂,大概需要引入分布式事件解决方案。
[*] 性能优化:分库分表后,需要注意索引、查询优化等,以包管系统性能。
分布式情况下事件

ERP(企业资源规划)系统的事件管理选择,无论是采取Seata还是最终一致性模子,主要取决于系统的业务需求、数据一致性要求以及系统架构的复杂度。下面是两种方案的一些考虑因素:
Seata(分布式事件框架)
Seata 提供了较为严酷的数据一致性包管,适用于对数据一致性要求较高的场景。它通过AT、TCC、SAGA等模式支持分布式事件,可以或许确保跨服务、跨数据库的操纵要么全部成功,要么全部回滚。


[*]优点:提供强一致性包管,适用于金融、库存等对数据一致性要求极高的场景。
[*]缺点:增长了系统的复杂度,大概会对性能产生肯定影响。
最终一致性(基于消息队列等)
最终一致性模子通过异步消息、变乱驱动等方式,答应系统在一段时间内处于不一致状态,但最终达到一致状态。这种模子适用于可以容忍短暂数据不一致的业务场景,如订单处置惩罚、用户注册等。


[*] 优点:系统架构灵活,性能较好,适用于高并发场景。
[*] 缺点:数据一致性不是立刻得到包管,需要业务上可以或许担当最终一致性。
ERP系统的选择
[*] 对于ERP系统而言,由于涉及到财务、库存、采购等多个关键业务模块,这些模块对数据一致性的要求通常较高。因此,如果ERP系统采取微服务架构,跨服务调用频仍,且业务流程中存在大量需要包管原子性的操纵,那么采取Seata等分布式事件框架大概更为合适,以确保业务操纵的原子性和一致性。
[*] 然而,如果ERP系统的某些模块或业务流程可以容忍短暂的数据不一致,大概业务流程可以通过异步处置惩罚和补偿机制来达到最终一致性,那么采取基于消息队列的最终一致性模子大概更加高效,尤其是在需要处置惩罚高并发请求的场景下。
实践建议
在实际应用中,ERP系统大概同时采取这两种模子,根据差别业务模块和场景的具体需求,选择最合适的事件管理策略。比方,对于库存和财务等核心模块,采取Seata等分布式事件框架来包管数据的强一致性;而对于订单处置惩罚、通知发送等可以容忍短暂不一致的业务流程,则采取基于消息队列的最终一致性模子来进步系统的吞吐量和响应速度。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 财务管理系统之saas多租户架构是什么以及分库分表以及如何选择分布式事件方