ShardingJdbc分表分库在开发中应用

为什么要分表分库?

Mysql数据库使我们后端开发在工作中用到最多的数据库,凭借着开源免费灵活小巧等优势,成为一众互联网的宠儿,但是随着业务的不断增长,Mysql单表数据量成为最大的瓶颈,在优化得当的情况下,一张表最多到上千万数据就不适合继续堆积数据了,这个时候存取数据都会变得非常慢,为了解决业务发展和Mysql的瓶颈之间的矛盾,分表分库技术就诞生了。

常见的分表分库我主要接触了2种,Mycat和ShardingJdbc。

Mycat可以理解为mysql的代理,对开发人员是透明的,主要的分表分库逻辑都在mycat服务里实现,对运维的要求较高。

ShardingJdbc可以理解为jdbc的代理,对开发人员要求较高,主要逻辑都在应用中实现,对运维无压力。

何为分库,何为分表?

分库要解决的问题主要是硬件资源问题,毕竟一台服务器的CPU、内存、网络、硬盘资源是有限的,当并发达到一定级别时,分再多表都是在消耗一台服务器的资源,达到资源瓶颈后,就需要分库来横向拓展服务器资源。

分表主要解决是单表数据量问题,因为数据量达到一定级别后,索引的性能会急剧下降,这个时候我们需要将数据量分开,放到不同的表里,解决这个问题。

那么我们在开发的过程中到底是只用分表还是要分库分表,其实取决于你对性能的要求,这个其实需要视情况而定,如果一台服务器很强悍,分了众多表资源也没有到红线,那不分库也是可以的。如果服务器一般,分了几张表就扛不住了,那就需要横向加机器去分摊硬件压力。

今天主要是大家介绍ShardingJdbc在我们工作中的应用。

简介

ShardingSphere

如何使用

主要介绍与SpringBoot的集成,我使用的方法主要是用配置来实现,会自动扫描配置分表分库的相关配置,也可以通过在java代码里配置,这个本文没有阐述。

1、添加依赖

        <!-- 分表分库中间件 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>

2、配置文件

a、通用配置

spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.shardingsphere.enabled=true
spring.shardingsphere.props.sql.show=true

b、配置数据源

spring.shardingsphere.datasource.names=m1,m2
spring.shardingsphere.sharding.default-data-source-name=m1
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://139.196.31.219:3306/pay_merchant_account_00?useSSL=false&characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.m1.username=root32
spring.shardingsphere.datasource.m1.password=Root_1234563232
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://121.36.145.42:3306/pay_merchant_account_01?useSSL=false&characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.m2.username=root212
spring.shardingsphere.datasource.m2.password=Root_1234562232

配置了两个主节点

同时配置了default-data-source-name,当不是分表分库的表都使用这个默认数据库

c、如果是有主从读写分离的库配置如下:

# 数据库配置
spring.shardingsphere.datasource.names=m1,m2,s1,s2
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://139.196.31.219:3306/order_db_1?characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.m1.username=mysql
spring.shardingsphere.datasource.m1.password=mysql
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://139.196.31.229:3306/order_db_2?characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.m2.username=mysql
spring.shardingsphere.datasource.m2.password=mysql
spring.shardingsphere.datasource.s1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s1.url=jdbc:mysql://139.196.31.239:3306/order_db_1?characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.s1.username=mysql
spring.shardingsphere.datasource.s1.password=mysql
spring.shardingsphere.datasource.s2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s2.drive-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s2.url=jdbc:mysql://139.196.31.249:3306/order_db_2?characterEncoding=utf8&useSSL=false
spring.shardingsphere.datasource.s2.username=mysql
spring.shardingsphere.datasource.s2.password=mysql
# 读写分离配置
spring.shardingsphere.sharding.master-slave-rules.ds1.name=ds1
spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names=s1
spring.shardingsphere.sharding.master-slave-rules.ds2.name=ds2
spring.shardingsphere.sharding.master-slave-rules.ds2.master-data-source-name=m2
spring.shardingsphere.sharding.master-slave-rules.ds2.slave-data-source-names=s2

配置了两个主节点两个从节点,并按照一主一从的方式配置(注意这里的Mysql主从同步需要自己配置)

d、配置分表分库策略

# 数据库片配置
spring.shardingsphere.sharding.tables.t_order.logic-table=t_order
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{1..2}.t_order_$->{1..2}
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds$->{user_id%2}
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id%2}

这里我们可以配置每个逻辑库具体的分表分库策略,包含如下配置:

表名

实际物理节点

分库字段及分库策略算法(这里用的是行表达式分片策略)

分表字段及分表策略算法(这里用的是行表达式分片策略)

因为我在程序里自己使用了雪花算法来做主键生成,这里并没有配置key-generator的配置。

e、配置绑定表

# 配置绑定表(不配置会产生笛卡尔积)
spring.shardingsphere.sharding.binding-tables[0]=t_order,t_order_desc

我在开发中一般不怎么用join,所以绑定表我并没有用。

f、配置全局表

# 公共表配置
spring.shardingsphere.sharding.broadcast-tables=t_dict

默认会在所有物理节点的数据库都创建这个表,然后每次更新的时候都会更新所有节点的表。

代码版本配置

数据源配置

package chen.huai.jie.sharing.jdbc.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * 数据源配置
 *
 * @author chenhuaijie
 */
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.m1")
    public DataSource m1DataSource() {
        return genDatasource();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.m2")
    public DataSource m2DataSource() {
        return genDatasource();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.s1")
    public DataSource s1DataSource() {
        return genDatasource();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.s2")
    public DataSource s2DataSource() {
        return genDatasource();
    }

    private DruidDataSource genDatasource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }
}

分表分库配置

package chen.huai.jie.sharing.jdbc.config;

import chen.huai.jie.sharing.jdbc.sharding.DefaultDatabasePreciseShardingAlgorithm;
import chen.huai.jie.sharing.jdbc.sharding.DefaultTablePreciseShardingAlgorithm;
import com.google.common.collect.Lists;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.core.constant.properties.ShardingPropertiesConstant;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * 分库分表配置
 *
 * @author chenhuaijie
 */
@Configuration
public class ShardingJdbcConfig {

    @Autowired
    public DataSource m1DataSource;
    @Autowired
    public DataSource m2DataSource;
    @Autowired
    public DataSource s1DataSource;
    @Autowired
    public DataSource s2DataSource;

    /**
     * 分片数据源
     *
     * @return
     * @throws SQLException
     */
    @Bean
    public DataSource dataSource() throws SQLException {
        //1、 数据源配置
        Map<String, DataSource> dataSourceMap = new HashMap<>(4);
        dataSourceMap.put("m1", m1DataSource);
        dataSourceMap.put("m2", m2DataSource);
        dataSourceMap.put("s1", s1DataSource);
        dataSourceMap.put("s2", s2DataSource);

        //2、分片配置
        ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
        shardingRuleConfiguration.getMasterSlaveRuleConfigs().addAll(masterSlaveRuleConfigurations());
        shardingRuleConfiguration.getTableRuleConfigs().addAll(tableRuleConfigurations());
        // 广播表
        shardingRuleConfiguration.getBroadcastTables().add("t_dict");
        // 绑定表
        shardingRuleConfiguration.getBindingTableGroups().add("t_order,t_order_desc");

        //3、 属性配置
        Properties properties = new Properties();
        properties.setProperty(ShardingPropertiesConstant.EXECUTOR_SIZE.getKey(), "10");
        properties.setProperty(ShardingPropertiesConstant.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(), "1024");
        properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(), "true");

        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, properties);
    }

    /**
     * 分表路由配置
     *
     * @return
     */
    private List<TableRuleConfiguration> tableRuleConfigurations() {
        List<TableRuleConfiguration> tableRuleConfigurations = new ArrayList<>(1);

        TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration("t_order", "ds$->{1..2}.t_order_$->{1..2}");
        tableRuleConfiguration.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id", new DefaultDatabasePreciseShardingAlgorithm()));
        tableRuleConfiguration.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id", new DefaultTablePreciseShardingAlgorithm()));
        tableRuleConfigurations.add(tableRuleConfiguration);

        return tableRuleConfigurations;
    }

    /**
     * 主从配置
     *
     * @return
     */
    private List<MasterSlaveRuleConfiguration> masterSlaveRuleConfigurations() {
        List<MasterSlaveRuleConfiguration> masterSlaveRuleConfigurations = new ArrayList<>(2);
        masterSlaveRuleConfigurations.add(new MasterSlaveRuleConfiguration("ds1", "m1", Lists.newArrayList("s1")));
        masterSlaveRuleConfigurations.add(new MasterSlaveRuleConfiguration("ds2", "m2", Lists.newArrayList("s2")));
        return masterSlaveRuleConfigurations;
    }
}

开发自己的分表分库策略

上面的例子我们用的是行表达式分片策略,在实际开发过程中,我们可以通过自定义的策略来替换

替换的配置如下:

spring.shardingsphere.sharding.tables.pay_merchant_account.table-strategy.standard.sharding-column=merchant_no
spring.shardingsphere.sharding.tables.pay_merchant_account.table-strategy.standard.precise-algorithm-class-name=com.merchant.account.sharding.account.PayMerchantAccountTableShardingAlgorithm

几种分片策略:

1、精准分库算法,实现PreciseShardingAlgorithm接口,并重写doSharding()方法,

public class PayMerchantAccountDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<String> {

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        String merchantNo = shardingValue.getValue();
        String columnName = shardingValue.getColumnName();
        String logicTableName = shardingValue.getLogicTableName();

        String lastNumberString = merchantNo.substring(merchantNo.length() - 1);
        Integer lastNumber = NumberUtils.parseNumber(lastNumberString, Integer.class);


        if (lastNumber <=5) {
            return availableTargetNames.stream().filter(x -> x.endsWith("1")).findAny().get();
        } else {
            return availableTargetNames.stream().filter(x -> x.endsWith("2")).findAny().get();
        }
    }
}

这里的availableTargetNames是可用的目标分片,这里其实并不区分分库策略还是分表策略,配置在分库策略那里这里的availableTargetNames就是分库的信息,配置在分表策略那里就是分表的信息。shardingValue是你操作的记录的信息,包含逻辑表名,字段名,字段值,然后通过这个方法从availableTargetNames获取一个可用的值,就是分库或者分表要使用的分片。其实还是比较简单的。因为分库分表算法都是实现同一个接口,所以我们在代码里最好明确区分一下分库还是分表策略。

2、范围分片算法

public class DefaultRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        Set<String> result = new LinkedHashSet<>();
        // between and 的起始值
        Long lower = shardingValue.getValueRange().lowerEndpoint();
        Long upper = shardingValue.getValueRange().upperEndpoint();
        // 循环范围计算分库逻辑
        for (Long i = lower; i <= upper; i++) {
            for (String databaseName : availableTargetNames) {
                if (databaseName.endsWith(i % availableTargetNames.size() + "")) {
                    result.add(databaseName);
                }
            }
        }
        return result;
    }
}

与精准分片不同的是,这里是通过一个范围来计算返回一个集合,因为范围里的数据可能来一个集合的库或者表。

3、复合分片策略

public class DefaultComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Integer> shardingValue) {
        // 得到每个分片健对应的值
        Collection<Integer> orderIdValues = this.getShardingValue(shardingValue, "order_id");
        Collection<Integer> userIdValues = this.getShardingValue(shardingValue, "user_id");

        List<String> shardingSuffix = new ArrayList<>();
        // 对两个分片健同时取模的方式分库
        for (Integer userId : userIdValues) {
            for (Integer orderId : orderIdValues) {
                String suffix = userId % 2 + "_" + orderId % 2;
                for (String databaseName : availableTargetNames) {
                    if (databaseName.endsWith(suffix)) {
                        shardingSuffix.add(databaseName);
                    }
                }
            }
        }
        return shardingSuffix;
    }

    private Collection<Integer> getShardingValue(ComplexKeysShardingValue<Integer> shardingValues, final String key) {
        Collection<Integer> valueSet = new ArrayList<>();
        Map<String, Collection<Integer>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
        if (columnNameAndShardingValuesMap.containsKey(key)) {
            valueSet.addAll(columnNameAndShardingValuesMap.get(key));
        }
        return valueSet;
    }
}

支持多分片键

4、行表达式策略

行表达式分片策略(InlineShardingStrategy),在配置中使用 Groovy 表达式,提供对 SQL语句中的 = 和 IN 的分片操作支持,它只支持单分片健。

行表达式分片策略适用于做简单的分片算法,无需自定义分片算法,省去了繁琐的代码开发,是几种分片策略中最为简单的。

它的配置相当简洁,这种分片策略利用inline.algorithm-expression书写表达式。

比如:ds-$->{order_id % 2} 表示对 order_id 做取模计算,$ 是个通配符用来承接取模结果,最终计算出分库ds-0 ··· ds-n,整体来说比较简单。

事务保证

亲测,通过@Transactional(rollbackFor = Exception.class) 做到了同库和跨库的事务处理。

测试方法,通过两个保存操作,根据分表分库逻辑会保存到两个不同实例的数据库中,异常情况会回滚,正常情况下会写到数据库。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值