中国现在有9亿网民,我们随便一个人做点什么都会产生大量数据,比如看一下视频发表一下感想。
点赞57万,投币45万,评论1W+,再比如前段时间的618购物节,无数网民疯狂购物产生无数的消费数据,这么庞大的数据量该如何存储?
我们都知道mysql有性能瓶颈,当数据量到达2100w左右的时候,效率就会大幅下降。
mysql> show global variables like '%page%'; +--------------------------------------+-----------+ | Variable_name | Value | +--------------------------------------+-----------+ | innodb_log_compressed_pages | ON | | innodb_max_dirty_pages_pct | 75.000000 | | innodb_max_dirty_pages_pct_lwm | 0.000000 | | innodb_page_cleaners | 1 | | innodb_page_size | 16384 | | innodb_stats_persistent_sample_pages | 20 | | innodb_stats_sample_pages | 8 | | innodb_stats_transient_sample_pages | 8 | | large_page_size | 0 | | large_pages | OFF | +--------------------------------------+-----------+ 10 rows in set (0.01 sec)也就是说innode_page_size为16kb,我们常用int,bigint和varchar作为主键,当然mysql官方推荐使用int或bigint作为主键。总所周知mysql中一个int类型的所占用字节为4个字节,一个bigint占了8个字节。
SELECT * FROM `information_schema`.`TABLES` where table_name='table_name'可以查看有关表的信息,关于索引的大小就在index_length,所以一个索引的占用是6个字节。
所以一个page我们可以存多少数据量呢,首先我们B+tree的degree是3。
int类型 16384 / (6+4) = 1638 行,也就是说我们能存
1638 * 1638 * 16 = 42928704bigint类型 16384 / (8+6) = 1170,也就是说能存
1170 * 1170 * 16 = 21977344所以我们说mysql到了2100w,就到头了,不是因为不能存,而是数据存进去,B+tree的高度会增加,遍历子节点的次数要增加。
我们可以采用水平拓展的方式去实现存储。比如9亿网民,单张表我们存2100W。那就多搞张表。
# 数据库1 db_member_0 tb_member_0 tb_member_1 tb_member_2 tb_member_3 tb_member_9 # 数据库2 db_member_1 tb_member_0 tb_member_1 tb_member_9 db_member_9db_member_x表示我第x个数据库,tb_member_x表示库里的第x个表。这样就可以存21亿的数据量。
如何存储某一位网民的假设我们的主键id是202006272319230001,这个id是用年月日时分秒加一个0001这样的数字组合而成的,我们对单库的表个数取模,比如我数据库db_member_0中用户表有10个,那id个位对10取模就存在某个表里,他的十位取模决定了存在某个库里。
也就是说0001存在0号库的1号表里,0072存7号库的2号表里。
上述说了这么多,但是我们的数据库表id可能是自增的,那查询的时候如何处理?
这个就是今天第一个要解决的问题leaf分布式ID。
There are no two identical leaves in the world.
世界上没有两片完全相同的树叶。
— 莱布尼茨
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
实际参考项目 hello-sharding-jdbc 本项目采用mysql+mybatis-Plus开发,创建2个数据库。
在每一个数据库创建2张表
-- 数据库0 use db_order_0; CREATE TABLE `tb_order_0` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `tb_order_1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- 数据库1 use db_order_1; CREATE TABLE `tb_order_0` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `tb_order_1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;还得创建一个模型表,因为得使用mybatis-Plus生成代码,所以在0号数据库生成一个逻辑表,表结构和分表的表结构一致
CREATE TABLE `tb_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;主要是mysql的依赖版本问题需要注意
<!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!-- MySQL 驱动的版本号必须是 5.1.48 --> <version>5.1.48</version> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- Apache ShardingSphere --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0-RC3</version> </dependency>日志,实际查找了4张表
2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Rule Type: sharding 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,order_id,user_id FROM tb_order 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : SQLStatement: SelectSQLStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.core.parse.sql.statement.dml.SelectStatement@509a6095, tablesContext=TablesContext(tables=[Table(name=tb_order, alias=Optional.absent())], schema=Optional.absent())), projectionsContext=ProjectionsContext(startIndex=8, stopIndex=26, distinctRow=false, projections=[ColumnProjection(owner=null, name=id, alias=Optional.absent()), ColumnProjection(owner=null, name=order_id, alias=Optional.absent()), ColumnProjection(owner=null, name=user_id, alias=Optional.absent())]), groupByContext=org.apache.shardingsphere.core.preprocessor.segment.select.groupby.GroupByContext@57cabdc3, orderByContext=org.apache.shardingsphere.core.preprocessor.segment.select.orderby.OrderByContext@75bd28d, paginationContext=org.apache.shardingsphere.core.preprocessor.segment.select.pagination.PaginationContext@129c4d19, containsSubquery=false) 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_0 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_1 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,order_id,user_id FROM tb_order_0 2020-07-01 12:51:07.113 INFO 21232 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,order_id,user_id FROM tb_order_1日志,库是根据userId对2取余得到的结果,表是orderId随机生成后对2取余的结果()
2020-07-01 12:55:01.704 INFO 17012 --- [ main] ShardingSphere-SQL : Rule Type: sharding 2020-07-01 12:55:01.704 INFO 17012 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO tb_order ( order_id,user_id ) VALUES ( ?,? ) 2020-07-01 12:55:01.704 INFO 17012 --- [ main] ShardingSphere-SQL : SQLStatement: InsertSQLStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.core.parse.sql.statement.dml.InsertStatement@61b838f2, tablesContext=TablesContext(tables=[Table(name=tb_order, alias=Optional.absent())], schema=Optional.absent())), columnNames=[order_id, user_id], insertValueContexts=[InsertValueContext(parametersCount=2, valueExpressions=[ParameterMarkerExpressionSegment(startIndex=55, stopIndex=55, parameterMarkerIndex=0), ParameterMarkerExpressionSegment(startIndex=58, stopIndex=58, parameterMarkerIndex=1)], parameters=[1, 2])]) 2020-07-01 12:55:01.705 INFO 17012 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: INSERT INTO tb_order_1 ( order_id,user_id ) VALUES (?, ?) ::: [1, 2]分2种情况
如果是走了sharding-jdbc的sharding-column键,那么实际查询查其中分库分表的一部分 QueryWrapper<TbOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id",32); List<TbOrder> tbOrders = mapper.selectList(queryWrapper);日志
2020-07-01 13:01:20.389 INFO 20300 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_0 WHERE (user_id = ?) ::: [32] 2020-07-01 13:01:20.389 INFO 20300 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_1 WHERE (user_id = ?) ::: [32] 如果没走sharding-column键,就会全库全表查询。 QueryWrapper<TbOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("id",220); List<TbOrder> tbOrders = mapper.selectList(queryWrapper);日志
2020-07-01 13:04:13.088 INFO 13268 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_0 WHERE (id = ?) ::: [220] 2020-07-01 13:04:13.088 INFO 13268 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,order_id,user_id FROM tb_order_1 WHERE (id = ?) ::: [220] 2020-07-01 13:04:13.088 INFO 13268 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,order_id,user_id FROM tb_order_0 WHERE (id = ?) ::: [220] 2020-07-01 13:04:13.088 INFO 13268 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,order_id,user_id FROM tb_order_1 WHERE (id = ?) ::: [220]回头再看这张图,就可以明白实现的基本操作,就可以理解sharding-jdbc如何分库分表的操作了
[Leaf——美团点评分布式ID生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html)
[B+Tree 数据结构](https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html)
[为什么我的mysql里index_length是0bytes](https://stackoverflow.com/questions/29692421/mysql-why-is-my-index-length-0-0-bytes)