分库分表实践-springboot+mybatis+druid+shardingsphere

0 前言

0.1 Sharding-Sphere

Sharding-Sphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款相互独立的产品组成。他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
Sharding-Sphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。

0.2 Sharding-JDBC

我们这边主要用的是里面的Sharding-JDBC:

定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
分库分表实践-springboot+mybatis+druid+shardingsphere_第1张图片
系统架构图
  1. 配置
    1.1 springboot+mybatis+druid

    1.2 sharding-spheres
    pom.xml引入:

    io.shardingsphere
    sharding-jdbc-spring-boot-starter
    3.0.0.M2

1.3 分片设计

逻辑表结构:

CREATE TABLE `t_order` (
  `order_id` int(11) NOT NULL,
  `user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
)

按照order_id进行范围分片(order_id < 100的在shard1,其他的在shard2);
再按user_id进行hash分片(user_id % 2 分表落在t_order_0和t_order_1上)

数据库结构如下图:

分库分表实践-springboot+mybatis+druid+shardingsphere_第2张图片
数据库结构

application.properties

server.port=9001

mybatis.mapper-locations=classpath:mapper/*.xml

sharding.jdbc.datasource.names=ds0,ds1

sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds0.url=jdbc:mysql://localhost:3306/shard1?useSSL=false
sharding.jdbc.datasource.ds0.username=root
sharding.jdbc.datasource.ds0.password=password

sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds1.url=jdbc:mysql://localhost:3306/shard2?useSSL=false
sharding.jdbc.datasource.ds1.username=root
sharding.jdbc.datasource.ds1.password=password

sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
sharding.jdbc.config.sharding.tables.t_order.database-strategy.standard.sharding-column=order_id
sharding.jdbc.config.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.joey.springboot.shardingdemo.conf.OrderShardingAlgorithm
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{user_id % 2}
sharding.jdbc.config.sharding.tables.t_order.key-generator-column-name=order_id

sharding.jdbc.config.sharding.props.sql.show=true

具体的配置参数含义见官方文档
http://shardingsphere.io/document/current/cn/manual/sharding-jdbc/configuration/config-spring-boot/

其中OrderShardingAlgorithm配置类为:

package com.joey.springboot.shardingdemo.conf;

import io.shardingsphere.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingsphere.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;

import java.util.Collection;

public class OrderShardingAlgorithm implements PreciseShardingAlgorithm {

    @Override
    public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
        if(shardingValue.getValue().compareTo(100) > 0) {
            return "ds1";
        }
        return "ds0";
    }
}

  1. 测试代码

controller:

package com.joey.springboot.shardingdemo.controller;

import com.joey.springboot.shardingdemo.dao.Order;
import com.joey.springboot.shardingdemo.service.OrderService;
import io.shardingsphere.core.jdbc.core.connection.ShardingConnection;
import io.shardingsphere.core.jdbc.core.transaction.TransactionLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.sql.DataSource;
import java.sql.SQLException;

@RestController
public class OrderController {

    @Autowired
    OrderService orderService;

    @Autowired
    DataSource dataSource;

    @RequestMapping("/order/{id}")
    public Order getOrderById(@PathVariable int id) {
        System.out.println(orderService.getOrderById(id));
        return orderService.getOrderById(id);
    }

    @RequestMapping(value="/order", method= RequestMethod.POST)
    @ResponseBody
    @Transactional(rollbackFor = Exception.class)
    public Order addOrder(@RequestBody Order order) {
        orderService.addOrder(order);
        Order order1 = new Order();
        order1.setOrderId(order.getOrderId()+100);
        order1.setUserId(order.getUserId()+1);
        orderService.addOrder(order1);
        return orderService.getOrderById(order.getOrderId());
    }
}

service:

package com.joey.springboot.shardingdemo.service;

import com.joey.springboot.shardingdemo.dao.Order;
import com.joey.springboot.shardingdemo.dao.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public Order getOrderById(int id) {
        return orderMapper.selectByPrimaryKey(id);
    }


    public int addOrder(Order order) {
        return orderMapper.insert(order);
    }
}

model:

package com.joey.springboot.shardingdemo.dao;

public class Order {
    private Integer orderId;

    private Integer userId;

    public Integer getOrderId() {
        return orderId;
    }

    public void setOrderId(Integer orderId) {
        this.orderId = orderId;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }
}

mapper:

package com.joey.springboot.shardingdemo.dao.mapper;

import com.joey.springboot.shardingdemo.dao.Order;

public interface OrderMapper {
    int deleteByPrimaryKey(Integer orderId);

    int insert(Order record);

    int insertSelective(Order record);

    Order selectByPrimaryKey(Integer orderId);

    int updateByPrimaryKeySelective(Order record);

    int updateByPrimaryKey(Order record);
}

mapper.xml




  
    
    
  
  
    order_id, user_id
  
  
  
    delete from t_order
    where order_id = #{orderId,jdbcType=INTEGER}
  
  
    insert into t_order (order_id, user_id)
    values (#{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER})
  
  
    insert into t_order
    
      
        order_id,
      
      
        user_id,
      
    
    
      
        #{orderId,jdbcType=INTEGER},
      
      
        #{userId,jdbcType=INTEGER},
      
    
  
  
    update t_order
    
      
        user_id = #{userId,jdbcType=INTEGER},
      
    
    where order_id = #{orderId,jdbcType=INTEGER}
  
  
    update t_order
    set user_id = #{userId,jdbcType=INTEGER}
    where order_id = #{orderId,jdbcType=INTEGER}
  

这样可以方便地使用@Transactional注解实现弱XA

  • 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中。
  • 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能回滚。
  • 不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交。

3.问题

3.1问题描述

版本号
springboot 1.5.10.RELEASE
sharding-jdbc 3.0.0.M2

用spring自带的@Transactional 报错:

Could not dispatch event: io.shardingsphere.core.transaction.listener.TransactionListener@6e43ddd6 to public void io.shardingsphere.core.transaction.listener.TransactionListener.listen(io.shardingsphere.core.transaction.event.TransactionEvent) throws java.sql.SQLException

3.2问题原因

触发事务的时候会调用TransactionListener.listen中的:

TransactionManager transactionManager = TransactionContextHolder.get().getTransactionManager();

从ThreadLocal中获得的TransactionContext中的transactionManager为空
springboot启动的时候,加载了TransactionLoader,里面的doXaTransactionConfiguration是set了ThreadLocal中的transactionManager,但是set只对当前线程的ThreadLocal变量有效,所以导致在有新线程起来的时候再初始化TransactionContext时,transactionManager就为空。

3.3 问题解决

修改源码中的TransactionContext类

    private TransactionManager transactionManager = new WeakXaTransactionManager();

你可能感兴趣的:(分库分表实践-springboot+mybatis+druid+shardingsphere)