分库分表
1. 拆分方式
垂直拆分:按不同业务拆成多个库水平拆分:相同业务的库再拆分成多个库或表,如按用户ID或订单ID拆分。
拆分之后再连接数据库的方式:Client或Proxy
Client: 性能好,连接数多一点,不依赖中间服务不会出现单点故障,但每个项目都得依赖一个jar包,这使得大公司项目稳定后推进升级过程缓慢。
Proxy: 性能有损耗,会依赖中间服务,同时这个服务还得做高可用,架构变复杂了,但好处是业务方无感知,升级方便。
Client方式代表框架:ShardingJDBC
2. 垂直拆分多个库后多数据源接入
给每个库配置一个MybatisConfiguration,每个配置类里会设置好对应的所有mapper.xml,再使用如OrderMapper时就会使用订单库的数据源,使用StoreMapper就会使用店铺的数据源。@MapperScan(sqlSessionFactoryRef = "sqlSessionFactoryOrder", basePackages = {"jiagoubaiduren.mapper.order"}) @Configuration public class OrderMybatisAutoConfiguration { ... private final String[] MAPPER_XML_PATH = new String[] {"classpath*:ordermapper/*.xml"}; @Bean(name = "sqlSessionFactoryOrder") @Primary public SqlSessionFactory sqlSessionFactoryOrder() throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSourceOrder()); factory.setVfs(SpringBootVFS.class); factory.setMapperLocations(resolveMapperLocations()); return factory.getObject(); } public Resource[] resolveMapperLocations() { return Stream.of(Optional.ofNullable(MAPPER_XML_PATH).orElse(new String[0])) .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new); } ... }
3. 读写分离
问题1:上读写分离之后要考虑主从同步的延迟问题,原先刚写入的订单信息马上读详情肯定是没问题的,但现在读详情不一定能读取到,因为读走从库了而从库可能还没同步完成。解决:
1. 把这些有影响的业务梳理出来,某些场景下强制走主库,如查详情就强制走主库,查订单列表就走从库。
2. 通用的解决方式:插入一个订单,同时往redis插入一条缓存,过期时间必须大于你的主从同步延迟时间。 在订单详情查询的接口中,先判断有没有缓存,如果有缓存,那说明这个数据是刚刚插入的,还 没同步到从库中,这个时候就直接查master。如果没缓存,那就证明数据同步过来了,查slave。
问题2:如何让老业务无感知,上读写分离后旧代码先不用动,再慢慢梳理业务慢慢改。
解决:用ShardingJDBC,定义一个拦截所有dao的切面,让其默认都走主库,再定义一个走从库的注解,只有指定了这个注解才走从库。
4. 分片算法选择
1. 按时间适合查询场景只查最新的
2. 按范围
适合数字类型字段进行分表,一般像自增的主键ID
3. 取模
用的多,如userId % 10 = 数据在第几个表
5. 按某字段分库分表后如何满足多维度的查询
如根据用户id分之后,买家端好查询,卖家端呢,卖家要查自己店铺下的订单该怎么查,一个店铺下对应很多个userId,这数据就可能分布在多个表里。1. 买家端的复杂查询:用二级索引
用ES来构建这个二级索引,性能好,支持sharding,适合存储大量数据。数据同步用binlog或双写。
2. 查订单:让订单号的后四位为用户id的后四位,同时分库分表也是按用户id后四位来分,这样可以让按订单查询时变得简单,直接取后四位就知道是哪个表了。
3. 卖家端查询:空间换时间,多存储一份数据,用binlog同步数据时按店铺id分库分表存储,淘宝也是这么做的。
6. 不停机上线分库分表,无缝迁移数据
主要有两种迁移方式:1. 双写模式,侵入式的,要改代码。
1. 先把双写代码上线,这时新数据会写到新老库里2. 用程序消费binlog同步新数据,旧数据还得跟上面一样单独用脚本做同步。
2. 老数据同步到新库,因为修改老库时可能新库还没有那条数据而导致修改失败,所以同步完后再把新老库数据对比一遍,如有不同则用老库数据覆盖,至此新老库数据就完全一样了。
3. 再把读写操作切换到新库
以上总结主要来自B站一位UP
/Volumes/D/code/java/order-sharding-samples
架构摆渡人-Sharding Sphere,Sharding JDBC-真实订单业务,亿级数据带你实战分库分表
2023/12/01 22:45