在高并发的场景中,单库已经不能满足性能的要求,这才有了数据库演进之路。
单个分片
partition key两种方式:范围和hash。
范围的话比如用户的注册时间、用户的地理位置。缺点是热点分配不均匀,比如北上广的客户群里多,新注册的用户比较活跃,导致这些热点库的压力会比较大。
散列的话比如根据用户的id进行hash。优点是每个库的读写压力大概率是均匀的,缺点是扩容不方便。
多个分片
多个分片的场景,就是可能根据多个维度进行查询。比如用户表的partition key是id,那我们可以根据hash(id)知道查询哪个库。如果用户表的partition key是uname,我们也可以根据hash(uname)知道查询哪个库。如果我们设置id为partition key,那根据uname查询的时候,就不知道对应的库在哪里,此时就要全库搜索。
双写
同时下两个数据库,如果根据id查找,那去根据id分片的数据库查询,如果根据uname查找,那去根据uname分片的数据库查询。这个方案,虽然避免了全库搜索,但是用空间换时间的代价有点大啊,而且插入的时候还要保证数据库的一致性。
索引
插入数据库的时候,同步更新uname在哪个数据库。根据uname查询的时候,先查询uname在哪个库,然后根据这个值去指定的数据库查询。
缺点:
- 同样也是双写,需要保证数据的一致性。
- 读取的时候,多查询了一次,影响性能。
- 扩容的时候,需要更改表数据信息。
基因法
既然不知道对应关系,那就插入数据的时候,就给个标识符。
基因法(假设模16):
- username取后4位
- id生成60位
- 把username的后4位加到id后面
模16=2的4次方,所以是取4位。这样不管是对uname取模,还是对id取模,实际上都是对这两个的后4位取模,通过基因法,这后4位是落在一个数据库上的,所以不管通过uname还是id,都能找到对应的数据库。
扩容方案
停机扩容
这个方案比较暴力,一般在没什么人用系统的时候,比如凌晨,直接把系统停了,停机之前挂个公告通知几点到几点不能用。如果数据量比较大的话,还可能在这个时间做不完。。。
双写
首先,写一个同步的工具,把旧数据库的信息写到新数据库。在应用层中,每次查询走的是旧数据库,对数据库的修改、删除、新增,两个库都要操作。由于同步工具和应用层都会对数据库进行操作,所以有可能重复操作某个数据,此时每个表需要一个操作时间,以时间比较新的数据为准。
从库转主库
如下图所示,每个数据库都有对应的从库。迁移的时候直接把从库该主库就好。
下面是每个数据库的数据以及主从的情况:
此时把3个数据库变成6个数据库,也就是从id%3变成id%6。
以id%3=1的为例,之前数据为1,4,7,10,13,16,19的id会读取id%3=1的数据库,扩容后,1,7,13,19的id会读取id%6=1的数据库,而4,10,16的id会读取id%6=4的数据库。
这种方式,迁移几乎不用导入数据,可以瞬间切换,缺点就是数据冗余,需要在扩容后,把多余的数据清空。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。