Skip to content

Commit 6aa1499

Browse files
committed
分库分表平滑迁移
1 parent ed707d2 commit 6aa1499

File tree

13 files changed

+1921
-17
lines changed

13 files changed

+1921
-17
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@
163163
**重要知识点**
164164

165165
- [SpringBoot自动装配原理](https://topjavaer.cn/advance/excellent-article/3-springboot-auto-assembly.html)
166+
- [SpringBoot如何解决跨域问题](https://topjavaer.cn/framework/springboot/springboot-cross-domain.html)
167+
- [SpringBoot实现电子文件签字+合同系统](https://topjavaer.cn/framework/springboot/springboot-contract.html)
166168

167169
## Spring MVC
168170

docs/advance/excellent-article/24-generic.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# 泛型详解
2+
13
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。
24

35
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

docs/advance/excellent-article/27-mq-usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## 消息队列常见的使用场景
1+
# 消息队列常见的使用场景
22

33
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题
44

docs/advance/excellent-article/8-interface-idempotent.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 面试官:如何保证接口幂等性?一口气说了12种方法
1+
# 面试官:如何保证接口幂等性?一口气说了9种方法
22

33
大家好,我是大彬~
44

@@ -36,30 +36,26 @@
3636

3737
### **按钮只可操作一次**
3838

39-
一般是提交后把按钮置灰或loding状态,消除用户因为重复点击而产生的重复记录,比如添加操作,由于点击两次而产生两条记录
39+
一般是提交后把按钮置灰或loding状态,消除用户因为重复点击而产生的重复记录,比如添加操作,由于点击两次而产生两条记录
4040

4141
### **token机制**
4242

4343
功能上允许重复提交,但要保证重复提交不产生副作用,比如点击n次只产生一条记录,具体实现就是进入页面时申请一个token,然后后面所有的请求都带上这个token,后端根据token来避免重复请求。
4444

4545
![](http://img.topjavaer.cn/img/接口幂等.png)
4646

47-
### **使用Post/Redirect/Get模式**
48-
49-
在提交后执行页面重定向,这就是所谓的Post-Redirect—Get(PRG)模式,简单来说就是当用户提交连表单后,跳转到一个重定向的信息页面,这样就避免用户按F5刷新导致的重复提交,而且也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退导致同样重复提交的问题。
50-
51-
### **在session存放特殊标志**
52-
53-
在服务端,生成一个唯一的标识符,将它存入session,同时前端获取这个标识符的值将它写入表单的隐藏中,用于用户输入信息后点击一起提交,在服务器端,获取表单中隐藏字段的值,与session中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将session中的唯一标识符移除,不相等则表示是重复提交,不再做处理。
54-
5547
### **使用唯一索引防止新增脏数据**
5648

5749
利用数据库唯一索引机制,当数据重复时,插入数据库会抛出异常,保证不会出现脏数据。
5850

5951
### **乐观锁**
6052

61-
如果更新已有数据,可以进行加锁更新,也可以设计表结构时使用乐观锁,通过version来做乐观锁,这样既能保证执行效率,又能保证幂等, 乐观锁的version版本在更新业务数据要自增
53+
如果更新已有数据,可以进行加锁更新,也可以设计表结构时使用乐观锁,通过version来做乐观锁,这样既能保证执行效率,又能保证幂等, 乐观锁的version版本在更新业务数据要自增。
54+
55+
```mysql
6256
update table set version = version + 1 where id = #{id} and version = #{version}
57+
```
58+
6359
示例: 当有重复请求的时候,第一个请求会获取当前商品的version版本号,得到的version为1,紧接着由于第一个请求还没更新商品的version,第二个请求获取的version依然也是1, 这时候第一个请求操作更新的时候带上version并作为条件并且自增更新,这时候商品的version就会变成2,当第二个请求去操作更新的时候明显version不一致导致更新失败。
6460

6561
### **select + insert or update or delete**
@@ -77,12 +73,10 @@ update table set version = version + 1 where id = #{id} and version = #{version}
7773
### **防重表**
7874

7975
以支付为例: 使用唯一主键去做防重表的唯一索引,比如使用订单号作为防重表的唯一索引,每一次请求都根据订单号向防重表中插入一条数据,插入成功说明可以处理后面的业务,当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败,直接返回操作失败,直到第一次请求返回结果,可以看出防重表作用就是加锁的功能。
80-
注: 最好结合状态机幂等先判断一下
76+
77+
> 注: 最好结合状态机幂等先判断一下
8178
8279
### **缓冲队列**
8380

8481
将请求都快速地接收下来后放入缓冲队列中,后续使用异步任务处理队列中的数据,过滤掉重复的请求,该解决方案优点是同步处理改成异步处理、高吞吐量,缺点则是不能及时地返回请求结果,需要后续轮询得处理结果。
8582

86-
### **全局唯一号**
87-
88-
比如通过source来源 + 唯一序列号传入给后端,后端来判断请求是否重复,在并发时只能处理一个请求,其他相同并发请求要么返回请求重复,要么等待前面请求执行完成后再执行。
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
## 分库分表概述
2+
3+
在业务量不大时,单库单表即可支撑。
4+
5+
当数据量过大存储不下、或者并发量过大负荷不起时,就要考虑分库分表。
6+
7+
### 1.1 分库分表相关术语
8+
9+
- 读写分离: 不同的数据库,同步相同的数据,分别只负责数据的读和写;
10+
- 分区: 指定分区列表达式,把记录拆分到不同的区域中(必须是同一服务器,可以是不同硬盘),应用看来还是同一张表,没有变化;
11+
- 分库:一个系统的多张数据表,存储到多个数据库实例中;
12+
- 分表: 对于一张多行(记录)多列(字段)的二维数据表,又分两种情形:
13+
- (1) 垂直分表: 竖向切分,不同分表存储不同的字段,可以把不常用或者大容量、或者不同业务的字段拆分出去;
14+
- (2) 水平分表: 横向切分,按照特定分片算法,不同分表存储不同的记录。
15+
16+
### 1.2 真的要采用分库分表?
17+
18+
需要注意的是,分库分表会为数据库维护和业务逻辑带来一系列复杂性和性能损耗,`除非预估的业务量大到万不得已,切莫过度设计、过早优化`
19+
20+
规划期内的数据量和性能问题,尝试能否用下列方式解决:
21+
22+
- 当前数据量:如果没有达到几百万,通常无需分库分表;
23+
- 数据量问题:增加磁盘、增加分库(不同的业务功能表,整表拆分至不同的数据库);
24+
- 性能问题:升级CPU/内存、读写分离、优化数据库系统配置、优化数据表/索引、优化 SQL、分区、数据表的垂直切分;
25+
- 如果仍未能奏效,才考虑最复杂的方案:数据表的水平切分。
26+
27+
## 分库分表如何平滑过渡?
28+
29+
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上?
30+
31+
单库单表的系统给迁移到分库分表,有以下方案:
32+
33+
### 停机迁移方案
34+
35+
最简单但是有点 low 的方案,在网站或者 app 挂个公告,说凌晨某个时间段进行维护,无法访问。
36+
37+
接着到了时间点了就停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个**导数的一次性工具**,将单库单表的数据读出来,写到分库分表里面去。
38+
39+
导数完了之后,修改系统的数据库连接配置等,然后启动系统连到新的分库分表上去。
40+
41+
具体流程图如下:
42+
43+
![](http://img.topjavaer.cn/img/database-shard-method-1.png)
44+
45+
### 双写迁移方案
46+
47+
这个比较常用的一种迁移方案,不用停机。
48+
49+
简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,**除了对老库增删改,都加上对新库的增删改**,这就是所谓的**双写**,同时写俩库,老库和新库。
50+
51+
然后**系统部署**之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要去判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。简单来说,就是不允许用老数据覆盖新数据。
52+
53+
导完一轮之后,有可能数据还是存在不一致,那么就用脚本自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。
54+
55+
当数据完全一致了之后,所有机器使用分库分表的最新代码重新部署一次,此时就完成迁移了。
56+
57+
![](http://img.topjavaer.cn/img/database-shard-method-2.png)

0 commit comments

Comments
 (0)