常州做网站公司哪家好,seo常用方法,网页无法访问是什么意思,成都网站制作怎么样1 为什么要分库分表
1.1 数据库性能瓶颈的出现
对于应用来说#xff0c;如果数据库性能出现问题#xff0c;要么是无法获取连接#xff0c;是因为在高并发的情况下连接数不够了。要么是操作数据变慢#xff0c;数据库处理数据的效率除了问题。要么是存储出现问题#xf…1 为什么要分库分表
1.1 数据库性能瓶颈的出现
对于应用来说如果数据库性能出现问题要么是无法获取连接是因为在高并发的情况下连接数不够了。要么是操作数据变慢数据库处理数据的效率除了问题。要么是存储出现问题比如单机存储的数据量太大了存储的问题也可能会导致性能的问题。 归根结底都是受到了硬件的限制比如 CPU内存磁盘网络等等。但是我们优化肯定不可能直接从扩展硬件入手因为带来的收益和成本投入比例太比。 所以我们先来分析一下当我们处理数据出现无法连接或者变慢的问题的时候我们可以从哪些层面入手。
1.2 数据库优化方案对比
数据库优化有很多层面
1.2.1 SQL 与索引
因为 SQL 语句是在我们的应用端编写的所以第一步我们可以在程序中对 SQL 语句进行优化最终的目标是用到索引。这个是容易的也是最常用的优化手段。
1.2.2 表与存储引擎
第二步数据是存放在表里面的表又是以不同的格式存放在存储引擎中的所以我们可以选用特定的存储引擎或者对表进行分区对表结构进行拆分或者冗余处理或者对表结构比如字段的定义进行优化。
1.2.3 架构
第三步对于数据库的服务我们可以对它的架构进行优化。 如果只有一台数据库的服务器我们可以运行多个实例做集群的方案做负载均衡。 或者基于主从复制实现读写分离让写的服务都访问 master 服务器读的请求都访问从服务器slave 服务器自动 master 主服务器同步数据。 或者在数据库前面加一层缓存达到减少数据库的压力提升访问速度的目的。 为了分散数据库服务的存储压力和访问压力我们也可以把不同的数据分布到不同的服务节点这个就是分库分表scale out。 注意主从replicate和分片shard的区别 主从通过数据冗余实现高可用和实现读写分离。 分片通过拆分数据分散存储和访问压力。
1.2.4 配置
第四步是数据库配置的优化比如连接数缓冲区大小等等优化配置的目的都是为了更高效地利用硬件。
1.2.5 操作系统与硬件
最后一步操作系统和硬件的优化。 从上往下成本收益比慢慢地在增加。所以肯定不是查询一慢就堆硬件堆硬件叫做向上的扩展scale up。 什么时候才需要分库分表呢我们的评判标准是什么 如果是数据量的话一张表存储了多少数据的时候才需要考虑分库分表 如果是数据增长速度的话每天产生多少数据才需要考虑做分库分表 如果是应用的访问情况的话查询超过了多少时间有多少请求无法获取连接才需要分库分表这是一个值得思考的问题。
1.3 架构演进与分库分表
1.3.1 单应用单数据库
2013 年的时候我们公司采购了一个消费金融核心系统这个是一个典型的单体架构的应用。同学们应该也很熟悉单体架构应用的特点就是所有的代码都在一个工程里面打成一个 war 包部署到 tomcat最后运行在一个进程中。 这套消费金融的核心系统用的是 Oracle 的数据库初始化以后有几百张表比如客户信息表、账户表、商户表、产品表、放款表、还款表等等 为了适应业务的发展我们这一套系统不停地在修改代码量越来越大系统变得越来越臃肿。为了优化系统我们搭集群负载均衡加缓存优化数据库优化业务代码系统但是都应对不了系统的访问压力。 所以这个时候系统拆分就势在必行了。我们把以前这一套采购的核心系统拆分出来很多的子系统比如提单系统、商户管理系统、信审系统、合同系统、代扣系统、催收系统所有的系统都依旧共用一套 Oracle 数据库
1.3.2 多应用单数据库
对代码进行了解耦职责进行了拆分生产环境出现问题的时候可以快速地排查和解决 这种多个子系统共用一个 DB 的架构会出现一些问题。 第一个就是所有的业务系统都共用一个 DB无论是从性能还是存储的角度来说都是满足不了需求的。随着我们的业务继续膨胀我们又会增加更多的系统来访问核心数据库但是一个物理数据库能够支撑的并发量是有限的所有的业务系统之间还会产生竞争最终会导致应用的性能下降甚至拖垮业务系统。
1.3.3 多应用独立数据库
所以这个时候我们必须要对各个子系统的数据库也做一个拆分。这个时候每个业务系统都有了自己的数据库不同的业务系统就可以用不同的存储方案。 所以分库其实是我们在解决系统性能问题的过程中对系统进行拆分的时候带来的一个必然的结果。现在的微服务架构也是一样的只拆应用不拆分数据库不能解决根本的问题。
1.3.4 什么时候分表
当我们对原来一个数据库的表做了分库以后其中一些表的数据还在以一个非常快的速度在增长这个时候查询也已经出现了非常明显的效率下降。 所以在分库之后还需要进一步进行分表。当然我们最开始想到的可能是在一个数据库里面拆分数据分区或者分表到后面才是切分到多个数据库中。 分表主要是为了减少单张表的大小解决单表数据量带来的性能问题 我们需要清楚的是分库分表会提升系统的复杂度如果在近期或者未来一段时间内必须要解决存储和性能的问题就不要去做超前设计和过度设计。就像我们搭建项目从快速实现的角度来说肯定是从单体项目起步的在业务丰富完善之前也用不到微服务架构。 如果我们创建的表结构合理字段不是太多并且索引创建正确的情况下单张表存储几千万的数据是完全没有问题的这个还是以应用的实际情况为准。当然我们也会对未来一段时间的业务发展做一个预判。
2 分库分表的类型和特点
从维度来说分成两种一种是垂直一种是水平。 垂直切分基于表或字段划分表结构不同。我们有单库的分表也有多库的分库。 水平切分基于数据划分表结构相同数据不同也有同库的水平切分和多库的切分。
2.1 垂直切分
垂直分表有两种一种是单库的一种是多库的。
2.1.1 单库垂直分表
单库分表比如商户信息表拆分成基本信息表联系方式表结算信息表附件表等等。
2.1.2 多库垂直分表
多库垂直分表就是把原来存储在一个库的不同的表拆分到不同的数据库。 比如消费金融核心系统数据库有很多客户相关的表这些客户相关的表全部单独存放到客户的数据库里面。合同放款风控相关的业务表也是一样的。 当我们对原来的一张表做了分库的处理如果某些业务系统的数据还是有一个非常快的增长速度比如说还款数据库的还款历史表数据量达到了几个亿这个时候硬件限制导致的性能问题还是会出现所以从这个角度来说垂直切分并没有从根本上解决单库单表数据量过大的问题。在这个时候我们还需要对我们的数据做一个水平的切分。
2.2 水平切分
当我们的客户表数量已经到达数千万甚至上亿的时候单表的存储容量和查询效率都会出现问题我们需要进一步对单张表的数据进行水平切分。水平切分的每个数据库的表结构都是一样的只是存储的数据不一样比如每个库存储 1000 万的数据。水平切分也可以分成两种一种是单库的一种是多库的
2.2.1 单库水平分表
银行的交易流水表所有进出的交易都需要登记这张表因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据所以我们根据使用频率把这张表拆分成三张表 当天表只存储当天的数据。 当月表在夜间运行一个定时任务前一天的数据全部迁移到当月表。用的是 insert into select然后 delete。 历史表同样是通过定时任务把登记时间超过 30 天的数据迁移到 history 历史表历史表的数据非常大我们按照月度每个月建立分区 费用表 消费金融公司跟线下商户合作给客户办理了贷款以后消费金融公司要给商户返费用或者叫提成每天都会产生很多的费用的数据。为了方便管理我们每个月建立一张费用表例如 fee_detail_201901……fee_detail_201912。 但是注意跟分区一样这种方式虽然可以一定程度解决单表查询性能的问题但是并不能解决单机存储瓶颈的问题
2.2.2 多库水平分表
另一种是多库的水平分表。比如客户表我们拆分到多个库存储表结构是完全一 样的。 一般我们说的分库分表都是跨库的分表。 既然分库分表能够帮助我们解决性能的问题那我们是不是马上动手去做甚至在项目设计的时候就先给它分几个库呢先冷静一下我们来看一下分库分表会带来哪些问题也就是我们前面说的分库分表之后带来的复杂性。
2.3 多案分库分表带来的问题
2.3.1 跨库关联查询
比如查询在合同信息的时候要关联客户数据由于是合同数据和客户数据是在不同的数据库那么我们肯定不能直接使用 join 的这种方式去做关联查询。 我们有几种主要的解决方案 1、字段冗余 比如我们查询合同库的合同表的时候需要关联客户库的客户表我们可以直接把一些经常关联查询的客户字段放到合同表通过这种方式避免跨库关联查询的问题。 2、数据同步比如商户系统要查询产品系统的产品表我们干脆在商户系统创建一张产品表通过 ETL 或者其他方式定时同步产品数据。 3、全局表广播表 比如行名行号信息被很多业务系统用到如果我们放在核心系统每个系统都要去关联查询这个时候我们可以在所有的数据库都存储相同的基础数据。 4、ER 表绑定表 我们有些表的数据是存在逻辑的主外键关系的比如订单表 order_info存的是汇总的商品数商品金额订单明细表 order_detail是每个商品的价格个数等等。或者叫做从属关系父表和子表的关系。他们之间会经常有关联查询的操作如果父表的数据和子表的数据分别存储在不同的数据库跨库关联查询也比较麻烦。所以我们能不能把父表和数据和从属于父表的数据落到一个节点上呢 比如 order_id1001 的数据在 node1它所有的明细数据也放到 node1order_id1002 的数据在 node2它所有的明细数据都放到 node2这样在关联查询的时候依然是在一个数据库。 上面的思路都是通过合理的数据分布避免跨库关联查询实际上在我们的业务中也是尽量不要用跨库关联查询如果出现了这种情况就要分析一下业务或者数据拆分是不是合理。如果还是出现了需要跨库关联的情况那我们就只能用最后一种办法。 5、系统层组装 在不同的数据库节点把符合条件数据的数据查询出来然后重新组装返回给客户端。
2.3.2 分布式事务
比如在一个贷款的流程里面合同系统登记了数据放款系统也必须生成放款记录如果两个动作不是同时成功或者同时失败就会出现数据一致性的问题。如果在一个数据库里面我们可以用本地事务来控制但是在不同的数据库里面就不行了。所以分布式环境里面的事务我们也需要通过一些方案来解决。 复习一下。分布式系统的基础是 CAP 理论。 1.C (一致性) Consistency对某个指定的客户端来说读操作能返回最新的写操作。对于数据分布在不同节点上的数据来说如果在某个节点更新了数据那么在其他节点如果都能读取到这个最新的数据那么就称为强一致如果有某个节点没有读取到那就是分布式不一致。 2.A (可用性) Availability非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间一个是合理的响应。合理的时间指的是请求不能无限被阻塞应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的 3.P (分区容错性) Partition tolerance当出现网络分区后系统能够继续工作。打个比方这里集群有多台机器有台机器网络出现了问题但是这个集群仍然可以正工作。 CAP 三者是不能共有的只能同时满足其中两点。基于 AP我们又有了 BASE 理论。基本可用(Basically Available)分布式系统在出现故障时允许损失部分可用功能保证核心功能可用。 软状态(Soft state)允许系统中存在中间状态这个状态不影响系统可用性这里指的是 CAP 中的不一致。 最终一致(Eventually consistent)最终一致是指经过一段时间后所有节点数据都将会达到一致。 分布式事务有几种常见的解决方案 1、全局事务比如 XA 两阶段提交应用、事务管理器™、资源管理器(DB)例如 Atomikos 2、基于可靠消息服务的分布式事务 3、柔性事务 TCCTry-Confirm-Canceltcc-transaction 4、最大努力通知通过消息中间件向其他系统发送消息重复投递定期校对
2.3.3 排序、翻页、函数计算问题
跨节点多库进行查询时会出现 limit 分页order by 排序的问题。比如有两个节点节点 1 存的是奇数 id1,3,5,7,9……节点 2 存的是偶数 id2,4,6,8,10…… 执行 select * from user_infoorder by id limit 0,10 需要在两个节点上各取出 10 条然后合并数据重新排序。 max、min、sum、count 之类的函数在进行计算的时候也需要先在每个分片上执行相应的函数然后将各个分片的结果集进行汇总和再次计算最终将结果返回
2.3.4 全局主键避重问题
MySQL 的数据库里面字段有一个自增的属性Oracle 也有 Sequence 序列。如果是一个数据库那么可以保证 ID 是不重复的但是水平分表以后每个表都按照自己的规律自增肯定会出现 ID 重复的问题这个时候我们就不能用本地自增的方式了。 我们有几种常见的解决方案 1UUIDUniversally Unique Identifier 通用唯一识别码 UUID 标准形式包含 32 个 16 进制数字分为 5 段形式为 8-4-4-4-12 的 36 个字符例如c4e7956c-03e7-472c-8909-d733803e79a9。 M 表示 UUID 版本目前只有五个版本即只会出现 12345数字 N 的一至三个最高有效位表示 UUID 变体目前只会出现 89ab 四种情况 1、基于时间和 MAC 地址的 UUID 2、基于第一版却更安全的 DCE UUID 3、基于 MD5 散列算法的 UUID 4、基于随机数的 UUID——用的最多JDK 里面是 4 5、基于 SHA1 UUID 是主键是最简单的方案本地生成性能高没有网络耗时。但缺点也很明显由于 UUID 非常长会占用大量的存储空间另外作为主键建立索引和基于索引进行查询时都会存在性能问题在 InnoDB 中UUID 的无序性会引起数据位置频繁变动导致分页。 2 数据库 把序号维护在数据库的一张表中。这张表记录了全局主键的类型、位数、起始值当前值。当其他应用需要获得全局 ID 时先 for update 锁行取到值1 后并且更新后返回。并发性比较差 3Redis 基于 Redis 的 INT 自增的特性使用批量的方式降低数据库的写压力每次获取一段区间的 ID 号段用完之后再去数据库获取可以大大减轻数据库的压力。 4雪花算法 Snowflake64bit 核心思想 a使用 41bit 作为毫秒数可以使用 69 年 b10bit 作为机器的 ID5bit 是数据中心5bit 的机器 ID支持 1024 个 节点 c12bit 作为毫秒内的流水号每个节点在每毫秒可以产生 4096 个 ID d最后还有一个符号位永远是 0。 代码snowflake.SnowFlakeTest 优点毫秒数在高位生成的 ID 整体上按时间趋势递增不依赖第三方系统稳定性和效率较高理论上 QPS 约为 409.6w/s(1000*2^12)并且整个分布式系统内不会产生 ID 碰撞可根据自身业务灵活分配 bit 位。 不足就在于强依赖机器时钟如果时钟回拨则可能导致生成 ID 重复。 当我们对数据做了切分分布在不同的节点上存储的时候是不是意味着会产生多个数据源既然有了多个数据源那么在我们的项目里面就要配置多个数据源。 现在问题就来了我们在执行一条 SQL 语句的时候比如插入它应该是在哪个数据节点上面执行呢又比如查询如果只在其中的一个节点上面我怎么知道在哪个节点是不是要在所有的数据库节点里面都查询一遍才能拿到结果 那么从客户端到服务端我们可以在哪些层面解决这些问题呢
2.4 多数据源/读写数据源的解决方案
我们先要分析一下 SQL 执行经过的流程。 DAO——MapperORM——JDBC——代理——数据库服务
2.4.1 客户端 DAO 层
第一个就是在我们的客户端的代码比如 DAO 层在我们连接到某一个数据源之前我们先根据配置的分片规则判断需要连接到哪些节点再建立连接。 Spring 中提供了一个抽象类 AbstractRoutingDataSource可以实现数据源的动态切换。 SSM 工程spring-boot-dynamic-data-source-master 步骤 1aplication.properties 定义多个数据源 2创建TargetDataSource 注解 3创建 DynamicDataSource 继承 AbstractRoutingDataSource 4多数据源配置类 DynamicDataSourceConfig 5创建切面类 DataSourceAspect对添加了TargetDataSource 注解的类进行拦截设置数据源。 6在 启 动 类 上 自 动 装 配 数 据 源 配 置Import({DynamicDataSourceConfig.class}) 7在 实 现 类 上 加 上 注 解 如 TargetDataSource(name DataSourceNames.SECOND)调用在 DAO 层实现的优势不需要依赖 ORM 框架即使替换了 ORM 框架也不受影响。实现简单不需要解析 SQL 和路由规则可以灵活地定制。 缺点不能复用不能跨语言
2.4.2 ORM 框架层
第二个是在框架层比如我们用 MyBatis 连接数据库也可以指定数据源。我们可以基于 MyBatis 插件的拦截机制拦截 query 和 update 方法实现数据源的选择。 例如https://github.com/colddew/shardbatis https://docs.jboss.org/hibernate/stable/shards/reference/en/html_single/
2.4.3 驱动层
不管是MyBatis还是Hibernate还是Spring的JdbcTemplate本质上都是对JDBC的封装所以第三层就是驱动层。比如 Sharding-JDBC就是对 JDBC 的对象进行了封装。JDBC 的核心对象 DataSource数据源 Connection数据库连接 Statement语句对象 ResultSet结果集 那我们只要对这几个对象进行封装或者拦截或者代理就可以实现分片的操作。
2.4.4 代理层
前面三种都是在客户端实现的也就是说不同的项目都要做同样的改动不同的编程语言也有不同的实现所以我们能不能把这种选择数据源和实现路由的逻辑提取出来做成一个公共的服务给所有的客户端使用呢 这个就是第四层代理层。比如 Mycat 和 Sharding-Proxy都是属于这一层。
2.4.5 数据库服务
最后一层就是在数据库服务上实现也就是服务层某些特定的数据库或者数据库的特定版本可以实现这个功能。