微服务拆分治理最佳实践

目录

一 背景

二 数据库拆分

数据库改造

代码改造方案

方案一

方案二

多数据源组件

自定义事务实现

数据安全性

三 应用拆分

拆分方案

拆分实践

系统瘦身

拆分带来的好处

四 数据访问权限收口

问题介绍

改造过程

收益

五 总结


本文作者:徐强,张均杰,黄威

专业在线打字练习平台-巧手打字通,只输出有价值的知识。

一 背景

部门中维护了一个老系统,功能都耦合在一个单体应用中(300+接口),表也放在同一个库中(200+表),导致系统存在很多风险和缺陷。经常出现问题:如数据库的单点、性能问题,应用的扩展受限,复杂性高等问题。

从下图可见。各业务相互耦合无明确边界,调用关系错综复杂。

图片

随着业务快速发展,各种问题越来越明显,急需对系统进行微服务改造优化。经过思考,整体改造将分为三个阶段进行:

  • 数据库拆分:数据库按照业务垂直拆分。

  • 应用拆分:应用按照业务垂直拆分。

  • 数据访问权限收口:数据权限按照各自业务领域,归属到各自的应用,应用与数据库一对一,禁止交叉访问。

图片

二 数据库拆分

单体数据库的痛点:未进行业务隔离,一个慢SQL易导致系统整体出现问题;读写压力大,性能下降;

数据库改造

图片

根据业务划分,我们计划将数据库拆分为9个业务库。数据同步方式采用主从复制的方式,我们提前整理好表和新数据库的对应关系交给运维同学,运维同学通过binlog过滤将对应的表和数据同步到对应的新数据库中,每个新数据库中只包含自己业务的表。

代码改造方案

如果一个接口中操作了多张表,之前这些表属于同一个库,数据库拆分后可能会分属于不同的库。所以需要针对代码进行相应的改造。

目前存在问题的位置:

  • 数据源选择:系统之前是支持多数据源切换的,在service上添加注解来选择数据源。数据库拆分后出现的情况是同一个service中操作的多个mapper从属于不同的库。

  • 事务:事务注解目前是存在于service上的,并且事务会缓存数据库链接,一个事务内不支持同时操作多个数据库。

改造点梳理:

  • 同时写入多个库,且是同一事务的接口6个:需改造数据源,需改造事务,需要关注分布式事务;

  • 同时写入多个库,且不是同一事务的接口50+:需改造数据源,需改造事务,无需关注分布式事务;

  • 同时读取多个库 或 读取一个库写入另一个库的接口200+:需改造数据源,但无需关注事务;

  • 涉及多个库的表的联合查询8个:需进行代码逻辑改造

梳理方式:

采用部门中的切面工具,抓取入口和表的调用关系(可识别表的读/写操作),找到一个接口中操作了多个表,并且多个表分属于不同业务库的情况;

分布式事务:

进行应用拆分和数据访问权限收口之后,是不存在分布式事务的问题的,因为操作第二个库会调用对应系统的RPC接口进行操作。所以本次不会正式支持分布式事务,而是采用代码逻辑保证一致性的方式来解决;

方案一

图片

将service中分别操作多个库的mapper,抽取成多个Service。分别添加切换数据源注解和事务注解。

问题:改动位置多,涉及改动的每个方法都需要梳理历史业务;service存在很多嵌套调用的情况,有时难以理清逻辑;修改200+位置改动工作量大,风险高;

方案二

图片

如图所示,方案二将数据源注解移动到Mapper上,并使用自定义的事务实现来处理事务。

将多数据源注解放到Mapper上的好处是,不需要梳理代码逻辑,只需要在Mapper上添加对应数据源名称即可。但是这样又有新的问题出现,

  • 问题1:如上图,事务的是配置在Service层,当事务开启时,数据源的连接并没有获取到,因为真正的数据源配置在Mapper上。所以会报错,这个错误可以通过多数据源组件的默认数据源功能解决。

  • 问题2:mybatis的事务实现会缓存数据库链接。当第一次缓存了数据库链接后,后续配置在mapper上的数据源注解并不会重新获取数据库链接,而是直接使用缓存起来的数据库链接。如果后续的mapper要操作其余数据库,会出现找不到表的情况。鉴于以上问题,我们开发了一个自定义的事务实现类,用来解决这个问题。

下面将对方案中出现的两个组件进行简要说明原理。

多数据源组件

多数据源组件是单个应用连接多个数据源时使用的工具,其核心原理是通过配置文件将数据库链接在程序启动时初始化好,在执行到存在注解的方法时,通过切面获取当前的数据源名称来切换数据源,当一次调用涉及多个数据源时,会利用栈的特性解决数据源嵌套的问题。

/**
 * 切面方法
 */
public Object switchDataSourceAroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        //获取数据源的名字
        String dsName = getDataSourceName(pjp);
        boolean dataSourceSwitched = false;
        if (StringUtils.isNotEmpty(dsName)
                && !StringUtils.equals(dsName, StackRoutingDataSource.getCurrentTargetKey())) {
            // 见下一段代码
            StackRoutingDataSource.setTargetDs(dsName);
            dataSourceSwitched = true;
        }
        try {
            // 执行切面方法
            return pjp.proceed();
        } catch (Throwable e) {
            throw e;
        } finally {
            if (dataSourceSwitched) {
                StackRoutingDataSource.clear();
            }
        }
    }

public static void setTargetDs(String dbName) {
    if (dbName == null) {
 throw new NullPointerException();
    }
    if (contextHolder.get() == null) {
        contextHolder.set(new Stack<String>());
    }
    contextHolder.get().push(dbName);
    log.debug("set current datasource is " + dbName);
}

StackRoutingDataSource继承 AbstractRoutingDataSource类,AbstractRoutingDataSource是spring-jdbc包提供的一个了AbstractDataSource的抽象类,它实现了DataSource接口的用于获取数据库链接的方法。

自定义事务实现

从方案二的图中可以看到默认的事务实现使用的是mybatis的SpringManagedTransaction。

图片

如上图,Transaction和SpringManagedTransaction都是mybatis提供的类,他提供了接口供SqlSession使用,处理事务操作。通过下边的一段代码可以看到,事务对象中存在connection变量,首次获得数据库链接后,后续当前事务内的所有数据库操作都不会重新获取数据库链接,而是会使用现有的数据库链接,从而无法支持跨库操作。

public class SpringManagedTransaction implements Transaction {

  private static final&nb
<think>好的,我现在需要回答用户关于微服务架构拆分过程和最佳实践的问题。首先,用户提到了几个引用内容,我需要仔细看看这些引用里有什么有用的信息。 引用[1]介绍了微服务架构的基本概念,提到了拆分单体应用为独立的小型服务,解决复杂性等问题。这可能涉及到拆分的基本原则,比如单一职责原则。引用[2]提到最佳实践中的单一职责原则,这可能和拆分过程中的服务边界划分有关。引用[3]详细描述了如何按业务功能拆分,比如电商系统中的商品、订单、库存等模块,以及使用领域驱动设计(DDD)的理念。这里的关键点可能是如何界定微服务的边界,以及使用DDD的方法论。 用户的问题具体是微服务拆分的过程和方法,以及最佳实践。我需要结合这些引用内容,整理出一个结构化的回答。可能需要包括拆分的步骤,比如业务分析、界定边界、数据拆分、基础设施搭建等。同时,最佳实践可能涉及持续集成、监控、团队协作等方面。 另外,用户要求回答结构清晰,分步骤说明,还要生成相关问题。需要确保每个部分都引用正确的来源,比如引用[3]提到的领域驱动设计,或者引用[2]中的单一职责原则。 需要注意的是,用户要求使用中文回答,行内公式用$...$,独立公式用$$...$$,但这个问题可能不涉及数学公式,所以可能不需要用到。不过如果有涉及性能指标或其他需要公式的地方,可能需要考虑,但根据引用内容,似乎不需要。 现在,我需要组织回答的结构: 1. 拆分过程: a. 业务领域分析(DDD) b. 界定服务边界(引用[3]的例子) c. 数据拆分(独立数据库) d. 基础设施搭建(如API网关、服务发现) 2. 最佳实践: a. 单一职责原则(引用[2]) b. 渐进式拆分(避免一次性拆分) c. 自动化与监控(引用[1]提到的云环境) d. 团队协作(引用[1]的团队协作开发) 可能还需要提到通信机制、容错设计,但引用中没有直接提到,可能需要依赖通用知识,但用户要求尽量引用站内内容,所以可能不需要。 然后生成相关问题,比如如何应对拆分后的数据一致性,或者如何选择拆分粒度,或者监控工具的选择等。 需要检查每个步骤是否都有引用支持,比如引用[3]中的电商系统例子,引用[2]的单一职责,引用[1]的团队协作和云环境。确保每个引用标识正确,例如在提到领域驱动设计时标注[^3],在单一职责时标注[^2],在团队协作时标注[^1]。 现在,需要将这些内容整合成连贯的段落,分步骤说明,并自然地加入引用标识。确保回答符合用户要求的格式,没有使用markdown,用中文,结构清晰。</think>微服务架构拆分过程与方法可分为以下关键步骤,结合最佳实践实现高效拆分: ### 一、拆分过程 1. **业务领域分析(领域驱动设计)** 采用领域驱动设计(DDD)划分业务边界,聚合核心功能。例如电商系统拆分为商品、订单、库存等子域,每个子域对应独立的微服务[^3]。通过事件风暴(Event Storming)识别业务实体、操作流程和上下文边界。 2. **界定服务边界** - **单一职责原则**:每个微服务仅承担明确的功能,如“商品服务”仅管理商品信息与上下架逻辑,“订单服务”处理订单生命周期[^2][^3]。 - **高内聚低耦合**:通过接口定义服务间的依赖关系,避免直接数据库耦合。例如库存服务通过API与订单服务交互,而非共享数据库表[^3]。 3. **数据拆分与隔离** 每个微服务拥有独立数据库(如MySQL分库或MongoDB分集合),通过事件溯源(Event Sourcing)或异步消息(如Kafka)实现数据最终一致性[^3]。 4. **基础设施搭建** - **通信机制**:采用轻量级协议(REST/gRPC)和API网关统一路由请求。 - **服务治理**:引入服务注册中心(如Consul)和配置中心(如Spring Cloud Config)[^1]。 - **监控与日志**:集成Prometheus监控指标与ELK日志系统。 --- ### 二、最佳实践 1. **渐进式拆分** 从单体中逐步剥离高价值模块,优先拆分频繁变更或性能瓶颈的组件(如促销系统),降低风险[^3]。 2. **自动化与持续交付** 每个微服务独立构建、测试和部署,通过CI/CD流水线(如Jenkins+GitLab)实现快速迭代[^1]。 3. **团队协作模式** 按“康威定律”组织团队结构,每个小组负责1-2个微服务,提升开发效率[^1]。 4. **容错设计** 使用熔断器(Hystrix)和重试机制(Retry Policy)保障服务可用性,避免级联故障。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值