1传统项目结构
2数据库性能瓶颈
①数据库连接数据库连接是非常稀少的资源,MySQL数据库默认个连接,单机最大连接。如果一个库里既有用户相关的数据又有商品、订单相关的数据,当海量用户同时操作时,数据库连接就很可能成为瓶颈。
②数据量MySQL单库数据量在万以内性能比较好,超过阈值后性能会随着数据量的增大而变弱。MySQL单表的数据量是w-0w之间性能比较好,超过0w性能也会下降。
③硬件问题因为单个服务的磁盘空间是有限制的,如果并发压力下所有的请求都访问同一个节点,肯定会对磁盘IO造成非常大的影响。
3数据库性能优化
①参数优化②缓存、索引③读写分离④分库分表(最终方案)
4分库分表介绍
4.1使用背景
当**表的数量**达到了几百上千张表时,众多的业务模块都访问这个数据库,压力会比较大,考虑对其进行分库。当**表的数据**达到了几千万级别,在做很多操作都比较吃力,考虑对其进行分库或者分表
4.2数据切分方案
数据的切分(Sharding)根据其切分规则的类型,可以分为两种垂直切分和水平切分模式
4.2.1垂直切分
按照业务模块进行切分,将不同模块的表切分到不同的数据库中。
4.2.1.1分库
4.2.1.2分表
按照字段将大表拆分成小表,另当表中含有Blob、Clob(用于存头像、小图片等)等二进制类型的字段时,因其不能使用索引,考虑性能问题需将其拆分出来。
4.2.2水平切分
将一张大表按照一定的切分规则,按照行切分成不同的表或者切分到不同的库中
4.2.2.1范围式拆分
好处:增删数据库实例时数据迁移是部分迁移,扩展能力强。坏处:热点数据分布不均,访问压力不能负载均衡。
4.2.2.2hash式拆分
好处:热点数据分布均匀,访问压力能负载均衡。坏处:增删数据库实例时数据都要迁移,扩展能力差。
4.2.2.3水平切分规则
①按照ID取模:对ID进行取模,余数决定该行数据切分到哪个表或者库中。②按照日期:按照年月日,将数据切分到不同的表或者库中。③按照范围:可以对某一列按照范围进行切分,不同的范围切分到不同的表或者数据库中。
4.2.3切分原则
①能不切分尽量不要切分。②如果要切分一定要选择合适的切分规则,提前规划好。③数据切分尽量通过数据冗余或表分组(TableGroup)来降低跨库Join的可能。
4.2.4说明
垂直切分是程序员切分,水平切分是利用TDDL、Cobar、Mycat、sharding-jdbc等进行切分。
4.3分库分表需要解决的问题
4.3.1分布式事务问题
解决方案:①采用补偿事务,例如TCC来解决分布式事务问题。②用记录日志等方式来解决分布式事务问题。
4.3.2分布式主键ID冲突问题
解决方案:①利用Redis的incr命令生成主键。②用UUID生成主键(不建议:字段比较长、不好排序)。③利用snowake算法生成主键。
4.3.3跨库join问题
解决方案:①将有E-R关系的表存储到一个库中。②对于数据量少的表建成全局表,分布到各个库中③对于必须跨库join的,最多支持跨两张表的跨库join
4.4案例分析
情况:有用户表user(uid、name、city、sex、age、timestamp),共5亿条数据,机器为x位系统,查询维度比较单一
问题:分几张表?PartitionKey如何选择?
分析:根据分表原则,单行数据大于字节则1千万一张表,单行数据小于字节则5千万一张表,用户表单行数据小于字节,单张表可存5千条记录,5亿除以5千万等于10,向上取整,共分为16张表。city、timestamp做为PartitionKey会造成热点数据分布不均匀,故使用uid作为PartitionKey,算法为uid模以16
4.5分库分表实现技术
①阿里的TDDL、Cobar②基于阿里Cobar开发的③当当网的sharding-jdbc
5ShardingJDBC
5.1ShardingJDBC简介
ApacheShardingSphere(Incubator)是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立,却又能够混合部署配合使用的产品组成。
5.1.1ShardingJDBC架构
5.1.2ShardingJDBC对多数据库的支持
5.1.3ShardingJDBC核心概念
数据分片:将数据按照一定的规则进行切分得到数据分片,数据分片分为垂直分片和水平分片。
分片键:用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。
一致性hash环:
*逻辑表*:水平拆分的数据库(表)的相同逻辑和数据结构表的总称。*真实表*:在分片的数据库中真实存在的物理表。*数据节点*:数据分片的最小单元。由数据源名称和数据表组成。*绑定表*:指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。*广播表*:指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
5.2分片算法与分片策略
5.2.1分片算法
Sharding-JDBC的分片算法有精确分片算法、范围分片算法、复合分片算法、Hint分片算法四种。
5.2.1.1精确分片算法
用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。
publicclassMyPreciseShardingAlgorithmimplementsPreciseShardingAlgorithmLong{
OverridepublicStringdoSharding(CollectionStringcollection,PreciseShardingValueLongpreciseShardingValue){for(Stringeach:collection){if(each.endsWith(Long.parseLong(preciseShardingValue.getValue().toString())%2+"")){returneach;}}thrownewIllegalArgumentException();}}5.2.1.2范围分片算法
用于处理使用单一键作为分片键的BETWEENAND进行分片的场景。需要配合StandardShardingStrategy使用。
publicclassMyRangeShardingAlgorithmimplementsRangeShardingAlgorithmLong{
OverridepublicCollectionStringdoSharding(CollectionStringcollection,RangeShardingValueLongrangeShardingValue){log.info("Rangecollection:"+JSON.toJSONString(collection)+",rangeShardingValue:"+JSON.toJSONString(rangeShardingValue));CollectionStringcollect=newArrayList();RangeLongvalueRange=rangeShardingValue.getValueRange();for(Longi=valueRange.lowerEndpoint();i=valueRange.upperEndpoint();i++){for(Stringeach:collection){if(each.endsWith(i%collection.size()+)){collect.add(each);}}}returncollect;}}5.2.1.3复合分片算法
用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。
publicclassMyComplexShardingAlgorithmimplementsComplexKeysShardingAlgorithm{
OverridepublicCollectionStringdoSharding(CollectionStringcollection,CollectionShardingValueshardingValues){log.info("collection:"+JSON.toJSONString(collection)+",shardingValues:"+JSON.toJSONString(shardingValues));CollectionLongorderIdValues=getShardingValue(shardingValues,"order_id");CollectionLonguserIdValues=getShardingValue(shardingValues,"user_id");ListStringshardingSuffix=newArrayList();/**例如:根据user_id+order_id双分片键来进行分表*///SetListIntegervalueResult=Sets.cartesianProduct(userIdValues,orderIdValues);for(LonguserIdVal:userIdValues){for(LongorderIdVal:orderIdValues){Stringsuffix=userIdVal%2+"_"+orderIdVal%2;collection.forEach(x-{if(x.endsWith(suffix)){shardingSuffix.add(x);}});}}returnshardingSuffix;}privateCollectionLonggetShardingValue(CollectionShardingValueshardingValues,finalStringkey){CollectionLongvalueSet=newArrayList();IteratorShardingValueiterator=shardingValues.iterator();while(iterator.hasNext()){ShardingValuenext=iterator.next();if(nextinstanceofListShardingValue){ListShardingValuevalue=(ListShardingValue)next;if(value.getColumnName().equals(key)){returnvalue.getValues();}}}returnvalueSet;}}5.2.1.4Hint分片算法
用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
5.2.2分片策略
Sharding-JDBC的分片策略有标准分片策略、复合分片策略、行表达式分片策略、Hint分片策略四种。
5.2.2.1标准分片策略
提供对SQL语句中的=、IN和BETWEENAND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEENAND分片。如果不配置RangeShardingAlgorithm,SQL中的BETWEENAND将按照全库路由处理。
5.2.2.2复合分片策略
提供对SQL语句中的=、IN和BETWEENAND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
5.2.2.3行表达式分片策略
使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发。行表达式的使用非常直观,只需要在配置中使用-{expression}TODO标识行表达式即可。目前支持数据节点和分片算法这两个部分的配置。行表达式的内容使用的是Groovy的语法,Groovy能够支持的所有操作,行表达式均能够支持。
5.2.2.4Hint分片策略
通过Hint而非SQL解析的方式分片的策略。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQLHint灵活的注入分片字段。
5.3ShardingJDBC核心组件
5.3.1解析引擎
SQL语句经过解析引擎的词法解析、语法解析,形成语法树。
5.3.2路由引擎
标准路由是ShardingSphere最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。
5.3.3改写引擎
工程师面向逻辑库与逻辑表书写的SQL,并不能够直接在真实的数据库中执行,SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。它包括正确性改写和优化改写两部分。
5.3.4执行引擎
ShardingSphere采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。执行引擎的目标是自动化的平衡资源控制与执行效率。执行引擎分为准备和执行两个阶段
5.3.5归并引擎
将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。ShardingSphere支持的结果归并从功能上分为遍历、排序、分组、分页和聚合5种类型,它们是组合而非互斥的关系。