1.概述
业务飞速发展导致了数据规模的急速膨胀,单机数据库已经无法适应互联网业务的发展 传统的将数据集中存储至单一数据节点的解决方案,在容量、性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。我们在单库单表数据量超过一定容量水位的情况下,索引树层级增加,磁盘 IO 也很可能出现压力,会导致很多问题。 从性能方面来说,由于关系型数据库大多采用 B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。 从可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。
总而言之,无论是因为数据量增多导致的分表,还是为了解决IO压力的分库,随着业务的扩大,这些系统最终都会迎来多
数据源的接入,从而需要进行数据库路由。
作者在所负责的新项目也遇到了分库分表的需求,为了不带方向性的去描述,我列出以下我们选择ShardingSphere的原因
1.Apache ShardingSphere是基于JAVA开发的,目前公司所有后端人员的主要语言是JAVA。
2.Apache ShardingSphere至开源到目前为止在github的社区活跃程度比较高,保持着高的讨论度和commit,在2020年成
为Apache的顶级项目。
3.有明确的发展规划,可以在未来可预期的时间内会一直保持维护和更新。
2.Apache ShardingSphere
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和Sidecar(规划中)
这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式
治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的
关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在
有基础上提供增量,而非颠覆。
2.1 ShardingSphere JDBC
ShardingSphere-JDBC 是 Apache ShardingSphere 的第一个产品,也是 Apache ShardingSphere 的前身。定位为轻量级
Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依
赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
1.适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
2.支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
3.支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。
作者目前的项目目前就是使用该成品,主要的原因是有,使用poxy模式的产品需要额外的维护,而目前没有多余人员进行维护。
2.1.1 集成ShardingSphere JDBC
1.添加依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.0.1</version>
</dependency>
2.添加sharding的配置
sharding:
datasources: #定义不同的分库
db0:
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:53306/mytest?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
maximum-pool-size: 20
max-lifetime: 30000
idle-timeout: 30000
data-source-properties:
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
cachePrepStmts: true
useServerPrepStmts: true
db1:
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:53306/mytest2?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
maximum-pool-size: 20
max-lifetime: 30000
idle-timeout: 30000
data-source-properties:
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
cachePrepStmts: true
useServerPrepStmts: true
rules:
tables:
eshop_address: #基于eshop_address设置规则
actualDataNodes: db${0..1}.eshop_address_${0..15} #db${0..1}用于分库 eshop_address_${0..15} 用于分表
databaseStrategy:
inline:
shardingColumn: city_code #根据city_code进行第一级分到2个不同的库
algorithmExpression: db${city_code % 2}
tableStrategy:
inline:
shardingColumn: region_code #根据region_code分到不同的16个表中
algorithmExpression: eshop_address_${region_code % 15}
props:
sql:
show: true
3.代码集成ShardingSphere JDBC框架
ShardingJdbcConfig
private DataSource buildDataSource() throws SQLException {
Map<String, DataSource> dataSourceMap = createDataSourceMap();
DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap,
new ShardingRuleConfigurationYamlSwapper().swap(ruleProperties) ,ruleProperties.getProps());
return dataSource;
}
private Map<String, DataSource> createDataSourceMap()
{
//创建数据源集合
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourcePeoperty.getDatasources().entrySet().forEach(e->{
dataSourceMap.put(e.getKey(),new HikariDataSource(e.getValue()));
});
return dataSourceMap;
}
4.测试
http://127.0.0.1:1236/find?citycode=1
2021-06-28 21:54:29.974 INFO 55905 --- [nio-1236-exec-1] ShardingSphere-SQL : SQLStatement: SelectSQLStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement@560b7495, tablesContext=TablesContext(tables=[Table(name=eshop_address, alias=Optional.absent())], schema=Optional.absent())), projectionsContext=ProjectionsContext(startIndex=7, stopIndex=162, distinctRow=false, projections=[ColumnProjection(owner=null, name=address_id, alias=Optional.absent()), ColumnProjection(owner=null, name=user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=address_name, alias=Optional.absent()), ColumnProjection(owner=null, name=country_code, alias=Optional.absent()), ColumnProjection(owner=null, name=phone, alias=Optional.absent()), ColumnProjection(owner=null, name=province_code, alias=Optional.absent()), ColumnProjection(owner=null, name=city_code, alias=Optional.absent()), ColumnProjection(owner=null, name=region_code, alias=Optional.absent()), ColumnProjection(owner=null, name=detail, alias=Optional.absent()), ColumnProjection(owner=null, name=create_user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=create_time, alias=Optional.absent()), ColumnProjection(owner=null, name=modify_user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=modify_time, alias=Optional.absent())], columnLabels=[address_id, user_id, address_name, country_code, phone, province_code, city_code, region_code, detail, create_user_id, create_time, modify_user_id, modify_time]), groupByContext=org.apache.shardingsphere.sql.parser.relation.segment.select.groupby.GroupByContext@4aadfe2b, orderByContext=org.apache.shardingsphere.sql.parser.relation.segment.select.orderby.OrderByContext@77fb922, paginationContext=org.apache.shardingsphere.sql.parser.relation.segment.select.pagination.PaginationContext@43ab1b00, containsSubquery=false)
2021-06-28 21:54:29.974 INFO 55905 --- [nio-1236-exec-1] ShardingSphere-SQL : Actual SQL: db1 ::: select address_id,user_id,address_name,country_code,phone,province_code,city_code,region_code,
detail,create_user_id,create_time,modify_user_id,modify_time
from eshop_address
where city_code=? ::: [1]
http://127.0.0.1:1236/find?citycode=2
2021-06-28 21:59:06.503 INFO 55905 --- [nio-1236-exec-3] ShardingSphere-SQL : SQLStatement: SelectSQLStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement@560b7495, tablesContext=TablesContext(tables=[Table(name=eshop_address, alias=Optional.absent())], schema=Optional.absent())), projectionsContext=ProjectionsContext(startIndex=7, stopIndex=162, distinctRow=false, projections=[ColumnProjection(owner=null, name=address_id, alias=Optional.absent()), ColumnProjection(owner=null, name=user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=address_name, alias=Optional.absent()), ColumnProjection(owner=null, name=country_code, alias=Optional.absent()), ColumnProjection(owner=null, name=phone, alias=Optional.absent()), ColumnProjection(owner=null, name=province_code, alias=Optional.absent()), ColumnProjection(owner=null, name=city_code, alias=Optional.absent()), ColumnProjection(owner=null, name=region_code, alias=Optional.absent()), ColumnProjection(owner=null, name=detail, alias=Optional.absent()), ColumnProjection(owner=null, name=create_user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=create_time, alias=Optional.absent()), ColumnProjection(owner=null, name=modify_user_id, alias=Optional.absent()), ColumnProjection(owner=null, name=modify_time, alias=Optional.absent())], columnLabels=[address_id, user_id, address_name, country_code, phone, province_code, city_code, region_code, detail, create_user_id, create_time, modify_user_id, modify_time]), groupByContext=org.apache.shardingsphere.sql.parser.relation.segment.select.groupby.GroupByContext@236fdb64, orderByContext=org.apache.shardingsphere.sql.parser.relation.segment.select.orderby.OrderByContext@2da82179, paginationContext=org.apache.shardingsphere.sql.parser.relation.segment.select.pagination.PaginationContext@2997b014, containsSubquery=false)
2021-06-28 21:59:06.503 INFO 55905 --- [nio-1236-exec-3] ShardingSphere-SQL : Actual SQL: db0 ::: select address_id,user_id,address_name,country_code,phone,province_code,city_code,region_code,
detail,create_user_id,create_time,modify_user_id,modify_time
from eshop_address
where city_code=? ::: [2]
ShardingSphere JDBC框架会根据我们配置的路由策略寻找对应的数据库进行查询。
2.2 ShardingSphere Proxy
ShardingSphere-Proxy 是 Apache ShardingSphere 的第二个产品。 它定位为透明化的数据库代理端,提供封装了数据库
二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL 版本,它可以使用任何兼容
MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对
DBA 更加友好。
向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。
适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。
2.2.1 启动ShardingSphere Proxy
1.下载ShardingSphere Proxy
可以在官网https://shardingsphere.apache.org/document/legacy/4.x/document/cn/downloads/进行下载
2.配置
在conf文件下,根据实际的需要进行配置
├── config-encrypt.yaml
├── config-master_slave.yaml
├── config-shadow.yaml
├── config-sharding.yaml
├── logback.xml
└── server.yaml
配置proxy主要内容
server.yaml
authentication:
users:
root:
password: root
props:
sql.show: true
authentication.users.root.password: root 配置proxy的登录名和密码为root/root
props.sql.show: true
因为这次是要对分库分表进行设置,所以对config-sharding.yaml进行配置
config-sharding.yaml
schemaName: sharding_db
dataSources:
db0:
url: jdbc:mysql://localhost:53306/mytest?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
db1:
url: jdbc:mysql://localhost:53306/mytest2?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
#
shardingRule:
tables:
eshop_address:
actualDataNodes: db${0..1}.eshop_address_${0..16}
databaseStrategy:
inline:
shardingColumn: city_code #根据city_code进行第一级分到2个不同的库
algorithmExpression: db${city_code % 2}
tableStrategy:
inline:
shardingColumn: region_code #根据region_code分到不同的16个表中
algorithmExpression: eshop_address_${region_code % 16}
3.配置客户端
proxy的默认端口是3307,设置客户端的用户名和密码为root/root
4.查询
select * from eshop_address where city_code=0
ShardingSphere Proxy会根据预定好的配置策略进行查询
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Logic SQL: select * from eshop_address where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - SQLStatement: SelectStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement@16def49f, tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@18191834), tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@18191834, projectionsContext=ProjectionsContext(startIndex=7, stopIndex=7, distinctRow=false, projections=[ShorthandProjection(owner=Optional.empty, actualColumns=[ColumnProjection(owner=null, name=address_id, alias=Optional.empty), ColumnProjection(owner=null, name=user_id, alias=Optional.empty), ColumnProjection(owner=null, name=address_name, alias=Optional.empty), ColumnProjection(owner=null, name=country_code, alias=Optional.empty), ColumnProjection(owner=null, name=phone, alias=Optional.empty), ColumnProjection(owner=null, name=province_code, alias=Optional.empty), ColumnProjection(owner=null, name=city_code, alias=Optional.empty), ColumnProjection(owner=null, name=region_code, alias=Optional.empty), ColumnProjection(owner=null, name=detail, alias=Optional.empty), ColumnProjection(owner=null, name=create_user_id, alias=Optional.empty), ColumnProjection(owner=null, name=create_time, alias=Optional.empty), ColumnProjection(owner=null, name=modify_user_id, alias=Optional.empty), ColumnProjection(owner=null, name=modify_time, alias=Optional.empty)])]), groupByContext=org.apache.shardingsphere.sql.parser.binder.segment.select.groupby.GroupByContext@1c269fef, orderByContext=org.apache.shardingsphere.sql.parser.binder.segment.select.orderby.OrderByContext@5924120f, paginationContext=org.apache.shardingsphere.sql.parser.binder.segment.select.pagination.PaginationContext@383f6fb8, containsSubquery=false)
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_0 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_1 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_2 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_3 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_4 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_5 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_6 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_7 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_8 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_9 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_10 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_11 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_12 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_13 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_14 where city_code=0
[INFO ] 22:40:20.919 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_15 where city_code=0
[INFO ] 22:40:20.920 [ShardingSphere-Command-13] ShardingSphere-SQL - Actual SQL: db0 ::: select * from eshop_address_16 where city_code=0
3.对比
Sharding-JDBC | Sharding-Proxy | |
---|---|---|
数据库 | 任意 | MySQL/PostgreSQL |
异构语言 | 仅Java | 任意 |
性能 | 损耗低 | 损耗略高 |
无中心化 | 是 | 否 |
静态入口 | 无 | 有 |
高可用 | 嵌入服务后和服务一并实现高可用 | 额外维护 |
因此如果开发项目是JAVA项目,Sharding-JDBC会是比较好的选择,一旦出了问题也能通过源码进行排查。Sharding-
Proxy适合需要多库多表操作的非JAVA项目
引用:https://zhuanlan.zhihu.com/p/352202284
https://shardingsphere.apache.org/document/current/cn/overview/
github:https://github.com/tale2009/Distributed-Learning/tree/main/ShardingSphere-JDBC
https://github.com/tale2009/Distributed-Learning/tree/main/ShardingSphere-Proxy