🚑电商系统分库分表案例

This commit is contained in:
zhipeng.zhang 2019-09-11 14:39:51 +08:00
parent 5d5756592a
commit cd27b1147d
2 changed files with 148 additions and 1 deletions

View File

@ -21,6 +21,7 @@ import com.itstyle.seckill.repository.SeckillRepository;
import com.itstyle.seckill.service.ISeckillService;
@Service("seckillService")
public class SeckillServiceImpl implements ISeckillService {
/**
* 思考为什么不用synchronized
* service 默认是单例的并发下lock只有一个实例
@ -73,8 +74,21 @@ public class SeckillServiceImpl implements ISeckillService {
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
Timestamp createTime = new Timestamp(new Date().getTime());
killed.setCreateTime(createTime);
dynamicQuery.save(killed);
/**
* 这里仅仅是分表而已提供一种思路供参考测试的时候自行建表
* 按照用户 ID 来做 hash分散订单数据
* 要扩容的时候为了减少迁移的数据量一般扩容是以倍数的形式增加
* 比如原来是8个库扩容的时候就要增加到16个库再次扩容就增加到32个库
* 这样迁移的数据量就小很多了
* 这个问题不算很大问题毕竟一次扩容可以保证比较长的时间而且使用倍数增加的方式已经减少了数据迁移量
*/
String table = "success_killed_"+userId%8;
nativeSql = "INSERT INTO "+table+" (seckill_id, user_id,state,create_time)VALUES(?,?,?,?)";
Object[] params = new Object[]{seckillId,userId,(short)0,createTime};
dynamicQuery.nativeExecuteUpdate(nativeSql,params);
//支付
return Result.ok(SeckillStatEnum.SUCCESS);
}else{

View File

@ -0,0 +1,133 @@
## 一、两种方案分库分表
一般业界对订单数据的分库分表有两类思路按照订单号来切分、按照用户id来切分。
#### 方案一、按照订单号来做 hash分散订单数据
 
把订单号看作是一个字符串,做 hash分散到多个服务器去。
具体到哪个库、哪个表存储数据呢?订单号里面的数字来记录着。
如果要查询某用户的所有订单呢?
由于是根据订单号来分散数据的。他的订单分散在了多个库、多个表中。
总不能去所有的库,所有的表扫描吧。这样效率很低。
解决:维护uid和oid的关系表, 此表可以作为缓存,当数据量增大时此关系表也要进行分表。
一般使用方案二的比较多,一个用户的所有订单,都在一张表里面,那么做分页展示的时候,就容易。
#### 方案二、按照用户 id 打散订单数据
以uid来切分数据有两种思路
第一种是某个范围的uid订单到哪些库。0到2千万uid对应的订单数据到a库、a表。2千万到4千万对应的订单到b库。
为什么这种方案用得比较少呢?
容易出现瓶颈吗。某个范围内的用户,下单量比较多,那么造成这个库的压力特别大。其他库却没什么压力。
第二种是使用uid取模运算。第二种方案业界用得比较多。
一方面、处理简单,程序上做取模运算就好了。
另一方面、使用取模的方式,数据比较均匀分散到多个库去了。不容易出现单个库性能瓶颈。
但是不好处也有:即要扩容的时候,比较麻烦。就需要迁移数据了。
要扩容的时候为了减少迁移的数据量一般扩容是以倍数的形式增加。比如原来是8个库扩容的时候就要增加到16个库再次扩容就增加到32个库。这样迁移的数据量就小很多了。这个问题不算很大问题毕竟一次扩容可以保证比较长的时间而且使用倍数增加的方式已经减少了数据迁移量。
下面分析一下按照用户id取模的方式分库分表。
按照用户id作为key来切分订单数据具体如下
1、 库名称定位用户id末尾4位 Mod 32。
Mod表示除以一个数后取余下的数。比如除以32后余下8余数就是8。
代码符号是用%表示15%4=3。
2、表名称定位用户id末尾4位 Dev 32 Mod 32。
Dev表示除以一个数取结果的整数。比如得到结果是25.6取整就是25。
代码用/来表示:$get_int = floor(15/4)。15除以4是一个小数3.75向下取整就是3。一定是向下取整向上取整就变成了4了。
按照上面的规则总共可以表示多少张表呢32个库*每个库32个表=1024张表。如果想表的数量小就把32改小一些。
库ID = userId % 库数量4
表ID = userId / 库数量4 % 表数量8
或者
库ID = userId / 表数量4 % 库数量4
表ID = userId % 表数量8
上面是用计算机术语来表示, 下面用通俗的话描述。
1、库名称计算
用户id的后4位数取32的模(取模就是除以这个数后,余多少)。余下的数是0-31之间。
这样可以表示从0-31之间总共32个数字。用这个32个数字代表着32个库名称order_db_0、order_db_2.........................order_db_31
2、表名称计算
最后要存储定到哪个表名里面去呢?
用户id的最后4位数除以32取整数。将整数除以32得到余数能够表示从0-31之间32个数字表示表名称。
表名称类似这样order_tb_1、order_tb_2..........................order_tb_31。一个库里面总共32个表名称。
比如用户id19408064用最后4位数字8064除以32得到是251.9取它的整数是251。
接着将251除以32取余数余数为27。
为了保持性能,每张表的数据量要控制。单表可以维持在一千万-5千万行的数据。1024*一千万。哇,可以表示很多数据了。
## 三、思考优点和缺点
优点
订单水平分库分表为什么要按照用户id来切分呢
好处:查询指定用户的所有订单,避免了跨库跨表查询。
因为根据一个用户的id来计算节点用户的id是规定不变的那么计算出的值永远是固定的(x库的x表)
那么保存订单的时候a用户的所有订单都是在x库x表里面。需要查询a用户所有订单时就不用进行跨库、跨表去查询了。
缺点
缺点在于数据分散不均匀某些表的数据量特别大某些表的数据量很小。因为某些用户下单量多打个比方1000-2000这个范围内的用户下单特别多
而他们的id根据计算规则都是分到了x库x表。造成这个表的数据量大单表的数据量撑到极限后咋办呢?
总结一下每种分库分表方案也不是十全十美都是有利有弊的。目前来说这种使用用户id来切分订单数据的方案还是被大部分公司给使用。实际效果还不错。程序员省事至于数据量暴涨以后再说呢。毕竟公司业务发展到什么程度不知道的项目存活期多久未来不确定。先扛住再说。
思考一、b2b平台的订单分卖家和买家的时候选择什么字段来分库分表呢
上面讨论的情况是b2c平台。订单的卖家就一个就是平台自己。
b2b平台上面支持开店买家和卖家都要能够登陆看到自己的订单。
先来看看分表使用买家id分库分表和根据卖家id分库分表两种办法出现的问题
如果按买家id来分库分表。有卖家的商品会有n个用户购买他所有的订单会分散到多个库多个表中去了卖家查询自己的所有订单跨库、跨表扫描性能低下。
如果按卖家id分库分表。买家会在n个店铺下单。订单就会分散在多个库、多个表中。买家查询自己所有订单同样要去所有的库、所有的表搜索性能低下。
所以无论是按照买家id切分订单表还是按照卖家id切分订单表。两边都不讨好。
淘宝的做法是拆分买家库和卖家库,也就是两个库:买家库、卖家库。
买家库按照用户的id来分库分表。卖家库按照卖家的id来分库分表。
实际上是通过数据冗余解决的:一个订单,在买家库里面有,在卖家库里面也存储了一份。下订单的时候,要写两份数据。先把订单写入买家库里面去,然后通过消息中间件来同步订单数据到卖家库里面去。
原文https://www.cnblogs.com/heqiyoujing/p/11297432.html