分表分库方案实现
前言:之所以要研究分表分库这个技术点,是因为在开发的过程中,因为分表分库踩了个小坑,虽然坑不算大,但是还是调试了很久。就是我在做推荐文章进入好友动态需求的时候,这个文章本身是一种type=51存储在数据库中,对应地转发这篇文章的feed的type也是51,只不过src_fid字段需要存储源文章的feed_id。
image.png
而推荐文章这个需求需要给feed表新增一种类型type=56,对应地在feed的分库新增一种类型type=302。后面就发现遇到坑了,因为我把分库的类型放到interaction_types中
image.png ,然后sync_feed_db.insert_data新增一种类型的时候
渲染的时候就查不到源文章,导致读取数据库失败。然后调试了很久,发现在读取源文章的时候读取不到,然后这条数据读取就失败了
image.png
image.png
在往上找,发现是这里读取失败了,这里是feed封装的方法,怎么会读取不到呢,于是我就进去看了看里面的代码
if (table == conf.TBNAME_FEEDS and id_field in ('id', 'src_fid')) or \
(table != conf.TBNAME_FEEDS and id_field == 'fid'):
for global_fid in ids:
if is_explore_fid(global_fid):
explore_fids.append(self.convert_global_fid_to_fid(global_fid))
elif is_interaction_fid(global_fid):
interaction_fids.append(self.convert_global_fid_to_fid(global_fid))
elif is_generalcard_fid(global_fid):
generalcard_fids.append(self.convert_global_fid_to_fid(global_fid))
image.png
因为源文章的类型是e_id,所以在查询出来某一条feed之后,会给src_fid自动拼装上前缀,如果一条feed是e_开头,会给他的src_fid拼上e_,但是由于前面把分库的类型写成i_类型,所以去expolore库查询就会查不到。还有一个坑就是explore类型的type不能大于300,大于300就会insert失败,这里也还没找到根因。
从这个bug中我就产生了很多疑问,为什么文章的src_fid需要放到一个库上呢?以及为什么要在代码中加上这种前缀呢?feed为什么要做分表分库,它是怎么做的呢?数据如何同步的?
一、分表分库的原理
当然第一步我们需要先了解一下分表分库的基本概念,分表分库上解决写请求越来越多的问题,以及数据量暴增的问题,因为对于读请求频繁的场景,可以加缓存或者用一主多从来解决,但是如果写请求过于频繁,那么压力都会集中在master上,master上面的IO,网络,连接池都会成为一种瓶颈。或者如果单表一直膨胀,那么对于磁盘来说压力山大,而且很容易带来慢查询。所以需要分表分库来解决这个问题。
分表的方式
- 垂直拆分
垂直拆分分为两种,垂直分库和垂直分表。如果单库写请求多,并且分散在多张表的情况,这种情况可以把库里面的表分散到多个库中,每个库放在不同机器上,不然还是会遇到机器的瓶颈问题。如果压力集中到一张表,那么可以大表拆小表,把不常用较长的字段迁移到扩展表,避免跨表查询的压力 - 水平拆分
水平拆分是将单表的数据水平拆分到不同服务器上,可以是按照Hash、地理位置、或者用户id进行分表。水平拆分的难度比垂直拆分更大一些。
分表分库需要解决的问题 - 事务支持
在单库或者单表中,可以用MySQL的事务解决一致性问题,但是分表分库的话,就需要解决分布式事务的问题了。 -
order by ,group by,join
分表分库之后order by 和group by要怎么实现?以及跨库的join问题
相关产品
image.png
二、实名分表分库
为什么实名要做分表分库
- feed库硬盘将满,单表过大,性能下降。
- 慢sql难以优化
遇到的问题怎么解决的?
- 如何拆分
备选方案有几种,其中一种按照fid做hash,然后查询的时候并行地读取集群,跟业务无关。优点是逻辑简单,易于扩展,缺点是存在无fid的表,而且有fid和无fid的跨库join难以实现。还有一种是按照type来拆分,按照业务的type分在不同的库,互动类型等等,优点是业务逻辑清晰,易于拆分,缺点是跨业务的查询需要查询多个集群。最终是选择了按照type来拆分,对于同一种业务来说,很少会跨集群,这里也解释了为什么src_fid和fid的前缀要一致,这是因为要避免跨集群访问。 - 如何数据同步
数据双写->老库数据迁移到新库->下掉老库的写入逻辑 - join,order by ,group by怎么做
代码实现细节
- 将存在badge的互动消息以及渲染的数据迁移到Redis,减轻数据库压力,能够给分表分库,提供一些时间。
- view层新增一个fid到gfid的转换,保证在后端的代码,都是带前缀(e表示探索类型,i表示互动类型)的gfid,这样能够区分是在哪个集群。
- 新增一个feed_manager,提供分表分库后的数据读写方法
- 将对新老数据库进行数据双写封装成api,方便调用,保持数据一致。
三、mycat的一些实现