java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot垂直分片

SpringBoot实现垂直分片的六种策略

作者:风象南

随着业务规模的不断扩大,单一数据库架构往往难以满足日益增长的数据量和访问压力,作为解决方案之一,垂直分片通过将不同业务模块的数据分散到不同的数据库或实例中,本文将介绍在SpringBoot环境下实现垂直分片的六种策略,需要的朋友可以参考下

一、垂直分片概述

1.1 什么是垂直分片

垂直分片是数据库分库分表的一种方式,它按照业务功能或数据表将原本在同一个数据库的数据拆分到不同的数据库实例中。

与水平分片(将同一张表的数据按照某种规则分散到不同库或表中)不同,垂直分片主要解决的是业务模块的解耦和单库的资源瓶颈问题。

1.2 垂直分片的优势

1.3 垂直分片的挑战

二、多数据源配置策略

2.1 基本原理

多数据源配置是实现垂直分片最直接的方式,通过在SpringBoot中配置多个DataSource并为不同的业务模块指定不同的数据源,实现业务数据的物理隔离。

2.2 实现步骤

2.2.1 配置多个数据源

首先在application.yml中配置多个数据源:

spring:
  datasource:
    # 用户服务数据源
    user:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://user-db:3306/user_db
      username: user_app
      password: password
      
    # 订单服务数据源
    order:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://order-db:3306/order_db
      username: order_app
      password: password
      
    # 产品服务数据源
    product:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://product-db:3306/product_db
      username: product_app
      password: password

2.2.2 创建数据源配置类

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.user")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource orderDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.product")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }
}

2.2.3 为不同模块配置独立的事务管理器和JdbcTemplate

@Configuration
public class UserDbConfig {
    
    @Autowired
    @Qualifier("userDataSource")
    private DataSource userDataSource;
    
    @Bean
    public JdbcTemplate userJdbcTemplate() {
        return new JdbcTemplate(userDataSource);
    }
    
    @Bean
    public PlatformTransactionManager userTransactionManager() {
        return new DataSourceTransactionManager(userDataSource);
    }
}

@Configuration
public class OrderDbConfig {
    
    @Autowired
    @Qualifier("orderDataSource")
    private DataSource orderDataSource;
    
    @Bean
    public JdbcTemplate orderJdbcTemplate() {
        return new JdbcTemplate(orderDataSource);
    }
    
    @Bean
    public PlatformTransactionManager orderTransactionManager() {
        return new DataSourceTransactionManager(orderDataSource);
    }
}

@Configuration
public class ProductDbConfig {
    
    @Autowired
    @Qualifier("productDataSource")
    private DataSource productDataSource;
    
    @Bean
    public JdbcTemplate productJdbcTemplate() {
        return new JdbcTemplate(productDataSource);
    }
    
    @Bean
    public PlatformTransactionManager productTransactionManager() {
        return new DataSourceTransactionManager(productDataSource);
    }
}

2.2.4 在Service层使用不同的数据源

@Service
public class UserService {
    
    @Autowired
    @Qualifier("userJdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    
    @Transactional("userTransactionManager")
    public User createUser(User user) {
        // 用户数据库操作
        // ...
    }
}

@Service
public class OrderService {
    
    @Autowired
    @Qualifier("orderJdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    
    @Transactional("orderTransactionManager")
    public Order createOrder(Order order) {
        // 订单数据库操作
        // ...
    }
}

2.3 优缺点分析

优点:

缺点:

2.4 适用场景

三、动态数据源路由策略

3.1 基本原理

动态数据源路由利用Spring提供的AbstractRoutingDataSource类,根据当前执行的上下文(如当前线程)动态决定使用哪个数据源。

这种方式实现了数据源选择的透明化,使业务代码无需关心具体使用哪个数据源。

3.2 实现步骤

3.2.1 创建数据源路由上下文

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

3.2.2 实现动态数据源路由

public class DynamicDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

3.2.3 配置动态数据源

@Configuration
public class DynamicDataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.user")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource orderDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.product")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        
        Map<Object, Object> dataSourceMap = new HashMap<>(3);
        dataSourceMap.put("user", userDataSource());
        dataSourceMap.put("order", orderDataSource());
        dataSourceMap.put("product", productDataSource());
        
        // 设置数据源映射
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        // 设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(userDataSource());
        
        return dynamicDataSource;
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

3.2.4 创建数据源切换注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSelector {
    String value();
}

3.2.5 实现数据源切换的AOP切面

@Aspect
@Component
public class DataSourceAspect {
    
    @Pointcut("@annotation(com.example.config.DataSourceSelector)")
    public void dataSourcePointcut() {}
    
    @Before("dataSourcePointcut() && @annotation(dataSource)")
    public void switchDataSource(JoinPoint point, DataSourceSelector dataSource) {
        String dataSourceName = dataSource.value();
        DataSourceContextHolder.setDataSourceType(dataSourceName);
    }
    
    @After("dataSourcePointcut()")
    public void restoreDataSource(JoinPoint point) {
        DataSourceContextHolder.clearDataSourceType();
    }
}

3.2.6 在Service层使用注解切换数据源

@Service
public class UserService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @DataSourceSelector("user")
    @Transactional
    public User createUser(User user) {
        // 用户数据库操作
        // ...
    }
}

@Service
public class OrderService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @DataSourceSelector("order")
    @Transactional
    public Order createOrder(Order order) {
        // 订单数据库操作
        // ...
    }
}

3.3 优缺点分析

优点:

缺点:

3.4 适用场景

四、ORM框架多数据源配置策略

4.1 基本原理

利用ORM框架(如JPA、MyBatis)的多数据源支持,为不同的业务模块配置独立的ORM组件,实现对不同数据库的透明访问。

这种方式结合了ORM的便利性和垂直分片的优势。

4.2 实现步骤(以JPA为例)

4.2.1 配置多个数据源

spring:
  # 用户服务数据源
  datasource:
    user:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://user-db:3306/user_db
      username: user_app
      password: password
      
    # 订单服务数据源
    order:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://order-db:3306/order_db
      username: order_app
      password: password
      
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect

4.2.2 配置用户模块的JPA配置

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.user.repository",
    entityManagerFactoryRef = "userEntityManagerFactory",
    transactionManagerRef = "userTransactionManager"
)
public class UserJpaConfig {
    
    @Autowired
    @Qualifier("userDataSource")
    private DataSource userDataSource;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(userDataSource)
            .packages("com.example.user.entity")
            .persistenceUnit("userPU")
            .properties(getJpaProperties())
            .build();
    }
    
    @Bean
    public PlatformTransactionManager userTransactionManager(
            @Qualifier("userEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
    
    private Map<String, Object> getJpaProperties() {
        Map<String, Object> props = new HashMap<>();
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
        props.put("hibernate.hbm2ddl.auto", "update");
        return props;
    }
}

4.2.3 配置订单模块的JPA配置

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.order.repository",
    entityManagerFactoryRef = "orderEntityManagerFactory",
    transactionManagerRef = "orderTransactionManager"
)
public class OrderJpaConfig {
    
    @Autowired
    @Qualifier("orderDataSource")
    private DataSource orderDataSource;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(orderDataSource)
            .packages("com.example.order.entity")
            .persistenceUnit("orderPU")
            .properties(getJpaProperties())
            .build();
    }
    
    @Bean
    public PlatformTransactionManager orderTransactionManager(
            @Qualifier("orderEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
    
    private Map<String, Object> getJpaProperties() {
        Map<String, Object> props = new HashMap<>();
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
        props.put("hibernate.hbm2ddl.auto", "update");
        return props;
    }
}

4.2.4 创建实体类和Repository

用户模块:

// 实体类
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    // getters and setters
}

// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByEmail(String email);
}

订单模块:

// 实体类
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    private BigDecimal amount;
    private Date orderDate;
    // getters and setters
}

// Repository
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
}

4.2.5 在Service层使用

@Service
@Transactional("userTransactionManager")
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

@Service
@Transactional("orderTransactionManager")
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserService userService;
    
    public Order createOrder(Order order) {
        // 验证用户是否存在
        User user = userService.getUserById(order.getUserId());
        if (user == null) {
            throw new RuntimeException("User not found");
        }
        return orderRepository.save(order);
    }
    
    public List<Order> getUserOrders(Long userId) {
        return orderRepository.findByUserId(userId);
    }
}

4.3 优缺点分析

优点:

缺点:

4.4 适用场景

五、分库中间件策略

5.1 基本原理

利用专业的分库分表中间件(如ShardingSphere、MyCat等)进行垂直分片,通过中间件提供的路由和代理功能,实现对多个数据库的统一管理和访问。

这种方式将分片逻辑从应用层下沉到中间件层,简化了应用开发。

5.2 实现步骤(以ShardingSphere-JDBC为例)

5.2.1 添加依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.0</version>
</dependency>

5.2.2 配置垂直分片规则

spring:
  shardingsphere:
    datasource:
      names: user-db,order-db,product-db
      user-db:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://user-db:3306/user_db
        username: user_app
        password: password
      order-db:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://order-db:3306/order_db
        username: order_app
        password: password
      product-db:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://product-db:3306/product_db
        username: product_app
        password: password
    
    rules:
      sharding:
        tables:
          # 用户表配置
          users:
            actual-data-nodes: user-db.users
          user_address:
            actual-data-nodes: user-db.user_address
          
          # 订单表配置
          orders:
            actual-data-nodes: order-db.orders
          order_items:
            actual-data-nodes: order-db.order_items
          
          # 产品表配置
          products:
            actual-data-nodes: product-db.products
          product_categories:
            actual-data-nodes: product-db.product_categories
    
    props:
      sql-show: true

5.2.3 创建实体类和Repository

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    // getters and setters
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 查询方法
}

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    private BigDecimal amount;
    // getters and setters
}

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
}

5.2.4 配置数据库访问

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EnableTransactionManagement
public class JpaConfig {
    
    @Bean
    public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.example.entity");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        
        return factory.getObject();
    }
    
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

5.2.5 在Service层使用

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

@Service
@Transactional
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(Order order) {
        return orderRepository.save(order);
    }
    
    public List<Order> getUserOrders(Long userId) {
        return orderRepository.findByUserId(userId);
    }
}

5.3 优缺点分析

优点:

缺点:

5.4 适用场景

六、微服务架构下的垂直分片策略

6.1 基本原理

在微服务架构中,每个微服务拥有自己独立的数据库,通过业务功能的拆分自然实现了垂直分片。

不同微服务之间通过API调用而非直接数据库访问进行交互,实现了数据的物理隔离和访问控制。

6.2 实现步骤

6.2.1 拆分微服务和数据库

根据业务领域划分微服务:

每个微服务拥有独立的数据库。

6.2.2 用户服务实现

// 用户服务数据库配置
@Configuration
@EnableJpaRepositories(basePackages = "com.example.userservice.repository")
public class UserServiceDbConfig {
    // 数据源、事务管理器等配置
}

// 用户实体类
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    // getters and setters
}

// 用户Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 查询方法
}

// 用户服务接口
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUserById(id));
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        return ResponseEntity.ok(userService.createUser(user));
    }
}

6.2.3 订单服务实现

// 订单服务数据库配置
@Configuration
@EnableJpaRepositories(basePackages = "com.example.orderservice.repository")
public class OrderServiceDbConfig {
    // 数据源、事务管理器等配置
}

// 订单实体类
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    private BigDecimal amount;
    // getters and setters
}

// 订单Repository
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
}

// 用户客户端(Feign)
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    
    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable Long id);
}

// 订单服务
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserClient userClient;
    
    public Order createOrder(Order order) {
        // 通过Feign调用用户服务验证用户
        User user = userClient.getUserById(order.getUserId());
        if (user == null) {
            throw new RuntimeException("User not found");
        }
        return orderRepository.save(order);
    }
}

// 订单API
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        return ResponseEntity.ok(orderService.createOrder(order));
    }
    
    @GetMapping("/user/{userId}")
    public ResponseEntity<List<Order>> getUserOrders(@PathVariable Long userId) {
        return ResponseEntity.ok(orderService.getUserOrders(userId));
    }
}

6.3 优缺点分析

优点:

缺点:

6.4 适用场景

七、策略比较

策略复杂度透明度事务支持跨库查询性能影响维护成本
多数据源配置单库事务需代码处理
动态数据源路由单库事务需代码处理
ORM多数据源单库事务需代码处理
分库中间件分布式事务部分支持
微服务架构最终一致性API组合

注:分布式事务支持程度取决于所选中间件或自主实现的数据一致性保障机制

八、垂直分片的最佳实践

8.1 数据模型设计

按业务领域划分表

将相关性强的表分到同一个数据库

尽量避免跨库关联查询

使用冗余字段减少跨库依赖

合理使用主键

避免使用自增主键(特别是在未来可能需要水平分片的场景)

考虑使用UUID或分布式ID生成器

建立合理的索引降低查询压力

数据冗余与一致性平衡

适当冗余关键数据降低跨库查询

建立数据同步机制保证最终一致性

区分强一致性场景和最终一致性场景

8.2 事务处理

本地事务处理

尽量将事务限制在单一数据库内

单数据库事务使用Spring的@Transactional注解

分布式事务策略

对于简单场景:两阶段提交(XA)

对于高并发场景:TCC(Try-Confirm-Cancel)

对于长事务场景:Saga模式

考虑使用Seata等分布式事务框架

最终一致性实现

基于消息队列的事件驱动架构

补偿机制和重试策略

幂等设计确保操作可重复执行

8.3 查询优化

减少跨库查询

合理划分业务边界,减少跨业务查询需求

使用数据冗余避免频繁跨库查询

考虑使用CQRS模式,为查询场景构建专用视图

查询路由策略

分析查询模式,优化分片键选择

对于复杂查询,考虑使用查询视图或搜索引擎

合理使用缓存减少数据库访问

结果聚合处理

在应用层对多数据源结果进行聚合

使用并行查询提高效率

结果分页和懒加载减少数据传输量

以上就是SpringBoot实现垂直分片的六种策略的详细内容,更多关于SpringBoot垂直分片的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文