ShardingSphere结合MySQL实现分库分表的项目实践
作者:nana_Wang123
ShardingSphere介绍
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由JDBC、Proxy和Sidecar(规划中)这3款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
- 一套开源的分布式数据库中间件解决方案
- 有3个产品:JDBC、Proxy、Sidecar
分库分表
当我们使用读写分离、索引、缓存后,数据库的压力还是很大的时候,这就需要使用到数据库拆分了。
- 一般来说, MySQL推荐的单表数据量在500w ~ 800w, 超过800w则建议分表.
- 或是在系统接口响应时间明显变慢, 并且通过代码优化, 改写sql等形式无法获得明显提升, 且明确了性能瓶颈在数据库时, 建议分表
- 分库则是在分表后单库性能到达瓶颈后进行, 如果个人或项目有钱任性的除外.分库主要解决的是并发量大的问题。
垂直拆分
分表
表中的字段较多,一般将不常用的、 数据较大、长度较长的拆分到“扩展表“。一般情况加表的字段可能有几百列,此时是按照字段进行数竖直切。注意垂直分是列多的情况。
分库
一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,比如用户相关的表放在一个数据库里,订单相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。
优点:
- 拆分后业务清晰,拆分规则明确。
- 系统之间整合或扩展容易。
- 数据维护简单。
缺点:
- 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度。
- 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
- 事务处理复杂。
水平拆分
分表
单表的数据量太大。按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。这种情况是不建议使用的,因为数据量是逐渐增加的,当数据量增加到一定的程度还需要再进行切分。比较麻烦。
分库
水平分库理论上切分起来是比较麻烦的,它是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
优点:
- 不存在单库大数据,高并发的性能瓶颈。
- 对应用透明,应用端改造较少。
- 按照合理拆分规则拆分,join 操作基本避免跨库。
- 提高了系统的稳定性跟负载能力。
缺点:
- 拆分规则难以抽象。
- 分片事务一致性难以解决。
- 数据多次扩展难度跟维护量极大。
- 跨库 join 性能较差。
分片后的常见问题
数据倾斜
这个几乎是无法避免的, 即使是id取模, 也会因为数据的删除导致每张分表的数据不一样, 或者id是UUID, 取模也会导致数据发生倾斜. 但是一般来说倾斜只要不是太离谱, 都在我们的接受范围以内.
id生成策略
如果分片之前你的id是递增的, 那么分片后你就无法保证id的全局唯一性, 这时比较常见的业内方案就是UUID或者SnowFlake.
当然如果想要排序和分页, 就需要有个id生成器去统一集中生成连续的id(参考下文).
全路由
这个是最糟糕的情况, 这种情况会让我们的查询比分片之前还要慢, 可以在自定义的分片算法中校验这种情况直接抛出异常, 然后coder们根据日志中的报错来统计这部分sql加以改写.
jpa级联
如果jpa级联中包含分表, 则需要拆除这种级联关系, 以免导致上述全路由情况发生.
排序&分页
如果只是单独分页, Sharding Sphere会剔除数据不写入内存, 实际上不会导致内存的大量占用, 但如果加上排序, 那情况就不容乐观了, 官方建议通过可以保证连续性的id去加以限制.
四种分片算法&五种分片策略
4种分片算法
精确分片算法
对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。
范围分片算法
对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。
复合分片算法
对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。
Hint分片算法
对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
5种分片策略
标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
行表达式分片策略
对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8}
表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0
到t_user_7
。
Hint分片策略
对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。
不分片策略
对应NoneShardingStrategy。不分片的策略。
ShardingSphere-JDBC
定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
需要注意的是,分库分表并不是由 ShardingSphere-JDBC 来做,它是用来负责操作已经分完之后的 CRUD 操作。
水平分表实操(单分片键,分片类型:HASH_MODE)
环境使用:SpringBoot 2.7.12 + MybatisPlus + ShardingSphere-jdbc 5.2.0 + Druid连接池
本示例为单库,库内有6个分表,并且按照order_id的hash值进行取模计算得到实际表
添加Maven依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>
按照水平分表创建数据库
CREATE TABLE `t_order_5` ( `order_id` bigint NOT NULL AUTO_INCREMENT, `price` double(12,2), `user_id` int NOT NULL, `address_id` bigint NOT NULL, `city` varchar(32) NULL DEFAULT NULL, `status` tinyint NULL DEFAULT NULL, `interval_time` datetime NULL DEFAULT NULL, `creator` varchar(32) NULL DEFAULT NULL, `create_time` datetime NULL DEFAULT NULL, `updater` varchar(32) NULL DEFAULT NULL, `update_time` datetime NULL DEFAULT NULL, PRIMARY KEY (`order_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic
配置Sharding-jdbc分片策略
application.yml内容:
spring: main: banner-mode: off shardingsphere: # 配置数据源,给数据源起别名ds datasource: ds: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 1234 names: ds rules: sharding: binding-tables: - t_order,t_order_item broadcast-tables: t_address # 分片算法 sharding-algorithms: t-order-algorithm: type: HASH_MOD props: sharding-count: '6' t-order-item-inline: type: INLINE props: algorithm-expression: t_order_item_$->{order_id % 2} tables: # 分表 t_order: actual-data-nodes: ds.t_order_$->{0..5} # 真实表名 table-strategy: standard: sharding-algorithm-name: t-order-algorithm sharding-column: order_id # 分片键 t_order_item: actual-data-nodes: ds.t_order_item_$->{0..1} table-strategy: standard: sharding-algorithm-name: t-order-item-inline sharding-column: order_id props: sql-show: true #mybatis-plus: # global-config: # banner: false # db-config: # id-type: assign_id #使用雪花算法开启数据入库时ID自增 # logic-delete-field: deleted # logic-delete-value: 1 # logic-not-delete-value: 0 # #开启mp的日志(控制台输出) # configuration: # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mybatis: mapper-locations: classpath*:/mappers/*-mapper.xml type-aliases-package: com.panliu.domain configuration: default-fetch-size: 20 default-statement-timeout: 30 map-underscore-to-camel-case: true use-generated-keys: true
测试代码运行
@Slf4j @SpringBootTest public class OrderMapperTests { private final static String[] CITIES = {"shanghai", "beijing"}; /** * -120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000 */ private final static long[] PRICES = {-120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000}; @Autowired private OrderMapper orderMapper; @Test //@Disabled void save() { ThreadLocalRandom random = ThreadLocalRandom.current(); Date[] dates = create(); IntStream.range(0, 20).forEach(i -> { Order order = new Order(); order.setOrderId(System.nanoTime() + i); order.setPrice(PRICES[i % PRICES.length]); order.setAddressId(i); order.setCity(CITIES[i % 2]); order.setUserId(Math.abs(random.nextInt())); order.setCreator("user.0" + i); order.setIntervalTime(dates[i % 5]); order.setUpdater(order.getCreator()); log.info("====>{}", order); orderMapper.save(order); }); } @Test // @Disabled void findAll() { List<Order> list = orderMapper.findAllAtPrice(60000); log.info("===>{}", list); } private Date[] create() { Date[] dates = new Date[6]; try { Date date = DateUtils.parseDate("2023-08-14 00:00:00", Locale.CHINA, "yyyy-MM-dd HH:mm:ss"); IntStream.range(0, 6).forEach(i -> dates[i] = DateUtils.addDays(date, i)); } catch (ParseException e) { log.error("date parse fail: ", e); } return dates; } }
运行结果
复合分片(根据城市和order_id分片)
application.yml配置
rules: sharding: sharding-algorithms: t-order-algorithm: type: COMPLEX_INLINE props: algorithm-expression: t_order_$->{city}_$->{order_id % 2} sharding-columns: city,order_id tables: t_order: actual-data-nodes: ds.t_order_$->{['shanghai','beijing']}_$->{0..1} table-strategy: complex: sharding-algorithm-name: t-order-algorithm sharding-columns: city,order_id
分片结果
到此这篇关于使用ShardingSphere实现MySQL分库分表的文章就介绍到这了,更多相关ShardingSphere MySQL分库分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!