作者:小傅哥,博客:https://bugstack.cn
面试问:“你为什么要自研,市面不是有吗,怎么回答?” 可以从以下3个点解答;
- 维护性;市面的路由组件比如 shardingsphere 但过于庞大,还需要随着版本做一些升级,市面上已经有很多版本了。而我们需要更少的维护成本。
- 扩展性;结合自身的业务需求,我们的路由组件可以分库分表、自定义路由协议,扫描指定库表数据等各类方式。研发扩展性好,简单易用。
- 安全性;自研的组件更好的控制了安全问题。当然,我们的组件主要是为了更好的适应目前系统的诉求,所以使用自研的方式处理。甚至各个大厂也都自研一整套分布式服务,来让自己的系统更加稳定可控。
接着问,你们为什么分库分表?
我们分库分表用的非常熟。但不能为了等到系统到了200万数据,才拆。那么工作量会非常大
我们的做法是,因为有成熟方案,所以前期就分库分表了。但,为了解释服务器空间。所以把分库分表的库,用服务器虚拟出来机器安装。这样即不过多的占用服务器资源,也方便后续数据量真的上来了,好拆分。
同时,Xxx系统,是瞬时峰值较高的系统,历史数据不一定多。所以我们希望,用户可以快速的检索到个人数据,做最优响应。因为大家都知道,Xxx这东西,push发完,基本就1~3分钟结束,10分钟人都没了。所以我们这也是做了分库分表的理由。
不过用和不用是一方面,会和不会是另外一方面。不能因为不会所以不用,不用是因为不同场景的所需。像 Apache ShardingSphere 在很多大的场景还是非常好用的,所以我们需要学习积累。积累不同的思想和设计,以及积累技术的运用。
小傅哥带着大家做项目,下个项目就要用 ShardingSphere。玩的就是一个综合实力!
那么,为了让更多的小伙伴,学习起编程项目更加容易上手。小傅哥把各项在项目所需的分布式技术栈,都拆解成了碎片化的知识,让大家可以一个个案例的学习。今天我们要讲到 sharding-jdbc 了!
- 碎片知识:https://bugstack.cn/md/road-map/road-map.html实战项目:https://bugstack.cn/md/zsxq/introduce.html
本文的宗旨在于通过简单干净实践的方式教会读者,快速 Easy 的使用上 sharding-jdbc 这个笨重的大家伙!—— 这篇文章并不复杂,但市面上的案例,还真的很少有能拿过来就能运行起来的!
之所以说"笨重",是因为 Apache ShardingSphere 不只是简单意义上的路由组件,而是一款分布式 SQL 事务和查询引擎,可通过数据分片、弹性伸缩、加密等能力对任意数据库进行增强。同时它又在迭代过程中,衍生出了很多的版本,以及对应了不同的使用方式。并在 ShardingSphere 5.3 以后又做了不小的架构调整。所以很多伙伴在使用的时候,经常是找了一上午的资料,到下午下班还没对接上。
本文涉及的工程:
- xfg-dev-tech-shardingjdbc:https://gitcode.net/KnowledgePlanet/road-map/xfg-dev-tech-shardingjdbc官网:https://shardingsphere.apache.org/index_zh.html
一、路由本质
分库分表的本质是数据的散列,分摊数据库资源压力。如把原本在一台机器上的数据库存放1000万数据,分摊到n台机上,拆分这1000万的数据和后续的增量。让每个数据库资源来分摊原本需要一台数据库所提供的服务。
-
- 当使用分库分表以后,并确定如使用
用户ID
- 作为路由分片键。那么所做的CRUD操作,都是需要使用到这个用户ID,并根据ID做路由库表计算。在大厂中,开发项目。并不会说目前这个业务需求规模不大,就不使用分库分表,而是分库分表都是非常成熟的方案,并不会因为使用了就降低很大的开发效率。所以基本就是默认就使用了。那么使用了分库分表,就会很占用资源吗。也不会,因为刚开始业务体量不大的时候,都是虚拟机交叉使用,你的1台物理机虚出来10个虚拟机,大家交叉使用主备。这样你只是使用了分库分表,但库表的实际资源没占用那么多。
二、使用案例
- jdk 1.8 +ShardingSphere 5.4.1
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc-core -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
- 因为需要解析 yaml 但默认的 SpringBoot 提供版本不支持 shardingsphere-jdbc-core 使用。
1. 工程结构
- 工程中,提供了 docker 配置数据库环境操作,并提供了对应的建表测试语句。如果你本机已经安装了数据库,那么只做库表语句导入以及 yml 配置数据库连接信息就可以。sharding-jdbc-dev.yaml 配置了详细的分库分表路由信息,在 algorithms 下配置的是库表的路由算法。这里的算法要根据实际自己使用中库表数量来设置&设计,避免发生较大的数据偏移。配置完 sharding-jdbc-dev.yaml 需要在 application-dev.yml 中配置上 sharding-jdbc-dev.yaml 路径,这样才能正确加载。如果你还需要定义出自己特定的路由算法,它还支持自己写个实现类的方式处理。
2. 算法配置
sharding/sharding-jdbc-dev.yaml
#?https://shardingsphere.apache.org/index_zh.html
mode:
??#?运行模式类型。可选配置:内存模式 Memory、单机模式 Standalone、集群模式 Cluster -?目前为单机模式
??type:?Standalone
dataSources:
??ds_0:
????dataSourceClassName:?com.zaxxer.hikari.HikariDataSource
????driverClassName:?com.mysql.cj.jdbc.Driver
????jdbcUrl:?jdbc:mysql://127.0.0.1:13306/xfg_dev_tech_db_00?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
????username:?root
????password:?123456
????connectionTimeoutMilliseconds:?30000
????idleTimeoutMilliseconds:?60000
????maxLifetimeMilliseconds:?1800000
????maxPoolSize:?15
????minPoolSize:?5
??ds_1:
????dataSourceClassName:?com.zaxxer.hikari.HikariDataSource
????driverClassName:?com.mysql.cj.jdbc.Driver
????jdbcUrl:?jdbc:mysql://127.0.0.1:13306/xfg_dev_tech_db_01?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
????username:?root
????password:?123456
????connectionTimeoutMilliseconds:?30000
????idleTimeoutMilliseconds:?60000
????maxLifetimeMilliseconds:?1800000
????maxPoolSize:?15
????minPoolSize:?5
rules:
??-?!SHARDING
????#?库的路由
????defaultDatabaseStrategy:
??????standard:
????????shardingColumn:?user_id
????????shardingAlgorithmName:?database_inline
????#?表的路由
????tables:
??????user_order:
????????actualDataNodes:?ds_$->{0..1}.user_order_$->{0..3}
????????tableStrategy:
??????????standard:
????????????shardingColumn:?user_id
????????????shardingAlgorithmName:?user_order_inline
????#?路由算法
????shardingAlgorithms:
??????#?库-路由算法 2是两个库,库的数量。库的数量用哈希模2来计算。
??????database_inline:
????????type:?INLINE
????????props:
??????????algorithm-expression:?ds_$->{Math.abs(user_id.hashCode())?%?2}
??????#?表-路由算法 4是一个库里,表的数量。4 - 1 为了获得?011 这样的二进制值。不推荐 user_order_$->{Math.abs(user_id.hashCode())?% 2}?作为表的路由
??????user_order_inline:
????????type:?INLINE
????????props:
??????????algorithm-expression:?user_order_$->{(user_id.hashCode()?^?(user_id.hashCode())?>>>?16)?&?(4?-?1)}
props:
??#?是否在日志中打印 SQL。
??#?打印 SQL 可以帮助开发者快速定位系统问题。日志内容包含:逻辑 SQL,真实 SQL 和 SQL 解析结果。
??#?如果开启配置,日志将使用 Topic ShardingSphere-SQL,日志级别是 INFO。false
??sql-show:?true
??#?是否在日志中打印简单风格的 SQL。false
??sql-simple:?true
??#?用于设置任务处理线程池的大小。每个 ShardingSphereDataSource 使用一个独立的线程池,同一个 JVM 的不同数据源不共享线程池。
??executor-size:?20
??#?查询请求在每个数据库实例中所能使用的最大连接数。1
??max-connections-size-per-query:?1
??#?在程序启动和更新时,是否检查分片元数据的结构一致性。
??check-table-metadata-enabled:?false
??#?在程序启动和更新时,是否检查重复表。false
??check-duplicate-table-enabled:?false
- mode:运行模式,默认就单机模式。dataSources:数据库连接信息。rules:路由算法。defaultDatabaseStrategy 库的路由、tables 表的路由。之后在 shardingAlgorithms 中配置具体的路由算法。这里的名称都是关联的,不要配置错。props:一些属性信息,包括是否打印日志等。
与这个对比,如果你使用的路由功能并不那么大,其实自研会更加简单。当然你也可以想办法,简化 sharding-jdbc 的配置。
3. 配置引入
application-dev.yml
spring:
??datasource:
????driver-class-name:?org.apache.shardingsphere.driver.ShardingSphereDriver
????url:?jdbc:shardingsphere:classpath:sharding/sharding-jdbc-dev.yaml
- 和之前的一些 sharding 版本不同,这里是需要使用具体的 ShardingSphereDriver 和 url 地址,才能加载上我们配置的路由信息。
三、测试验证
- 基于工程中 docs/dev-ops/mysql/sql 创建库表。已经提供了库名、表和测试数据。无论你使用哪种方式,都可以安装MySql 并使用可视化工具链接。这里小傅哥用的是 Sequel Ace
1. 写入数据
@Test
public?void?test_insert()?{
????for?(int?i?=?0;?i?<?1000;?i++)?{
????????UserOrderPO?userOrderPO?=?UserOrderPO.builder()
????????????????.userName("小傅哥")
????????????????.userId("xfg_"?+?RandomStringUtils.randomAlphabetic(6))
????????????????.userMobile("+86?13521408***")
????????????????.sku("13811216")
????????????????.skuName("《手写MyBatis:渐进式源码实践》")
????????????????.orderId(RandomStringUtils.randomNumeric(11))
????????????????.quantity(1)
????????????????.unitPrice(BigDecimal.valueOf(128))
????????????????.discountAmount(BigDecimal.valueOf(50))
????????????????.tax(BigDecimal.ZERO)
????????????????.totalAmount(BigDecimal.valueOf(78))
????????????????.orderDate(new?Date())
????????????????.orderStatus(0)
????????????????.isDelete(0)
????????????????.uuid(UUID.randomUUID().toString().replace("-",?""))
????????????????.ipv4("127.0.0.1")
????????????????.ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334".getBytes())
????????????????.extData("{"device":?{"machine":?"IPhone?14?Pro",?"location":?"shanghai"}}")
????????????????.build();
????????userOrderDao.insert(userOrderPO);
????}
}
- 测试数据写入,你可以写入1000条数据,观察散列效果。并可以在这个过程中,调试修改 sharding-jdbc-dev.yaml 文件对库表路由的计算方式。
2. 查询数据
@Test
public?void?test_selectByUserId()?{
????List<UserOrderPO>?list?=?userOrderDao.selectByUserId("xfg_PrmgwQ");
????log.info("测试结果:{}",?JSON.toJSONString(list));
}
- 查询的用户ID是已经写入到数据库表里的数据,查询的时候会根据用户ID继续路由计算。
3. 散列算法
@Test
public?void?test_idx()?{
????for?(int?i?=?0;?i?<?50;?i++)?{
????????String?user_id?=?"xfg_"?+?RandomStringUtils.randomAlphabetic(6);
????????log.info("测试结果?{}",?(user_id.hashCode()?^?(user_id.hashCode())?>>>?16)?&?3);
????}
}
- 你可以尝试验证和编写新的散列算法,最终目的都是让数据尽可能散列到库表。此外,关于算法的好坏,可以基于雪崩测试计算
因为很多项目都要用到云服务器部署,通过实战来锻炼。所以如果有需要,可以这样配合使用,会更加实惠。当然也可以选择3年款的。地址:https://3.cn/1K-cfT7D
- END -