如何避免分表下的读扩散问题
![/images/user.jpg /images/user.jpg](/images/user.jpg)
设计路由表和不设计路由表的区别
引入路由表
笔者为了应对海量短链接生成的场景,对原先的短链接表t_link
进行了水平分表,分片键为了配合其他业务选择了gid
,即分组 id。
现在当用户输入短链接后我们将查出存储的原始网址然后重定向过去
但是问题是用户只会传来短链接网址,而不会传递gid
,我们先设计路由表t_goto_link
,并且选取短链接网址作为分片键,这样让用户首先在路由表中查询根据短链接网址查询出gid
,再拿着gid
去t_link
中查询原始链接(为何设计路由表先不挑明,读者可以根据下面的实验自行思考下)
// 根据原始短链接来查询路由表中的 gid
LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
.eq(ShortLinkGotoDO::getFullShortUrl, fullShortUrl);
ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapper);
LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
// 拿着 gid 去查询 link 表得出原始链接
.eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
.eq(ShortLinkDO::getFullShortUrl, fullShortUrl)
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getEnableStatus, 0);
ShortLinkDO shortLinkDO = baseMapper.selectOne(queryWrapper);
((HttpServletResponse) response).sendRedirect(shortLinkDO.getOriginUrl())
测试时长为 55 ms 左右
直接查询原表
LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
// 这里没有 gid,而是直接查出原始 url
.eq(ShortLinkDO::getFullShortUrl, fullShortUrl)
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getEnableStatus, 0);
ShortLinkDO shortLinkDO = baseMapper.selectOne(queryWrapper);
((HttpServletResponse) response).sendRedirect(shortLinkDO.getOriginUrl())
怎么还是和引入路由表时间差不多,都是 50 ms 左右?话说不应该啊?难道引入路由表引入了个寂寞?
而且后期肯定要针对路由信息加缓存,到时候岂不是 MySQL 和 Redis 两头空?
小插曲
于是笔者首先查看了 ShardingSphere 的真实语句:确实实打实的执行了整整 16 次UNION
操作,话说怎么样也应该比单表查询慢点吧?毕竟union
16 张表可不是闹着玩的
确定 ShardingSphere 没有问题后,笔者试着查了查本机数据库,好家伙,一下子就发现t_link
表咋大部分都是没记录的!
只有t_link_1
有 2w 左右数据
真相就水落石出了:联合表了,但是联合了 15 张空表,【联合了,但没完全联】
于是笔者开始疯狂加数据,直到把其余的t_link
表量级抬上去,插入 1w 条数据
开测!!!
结果确实花费时间要长,要是数据量级再大点(比如千万级别),还能更加体现路由表的优越性!
设计路由表的原理
不难发现,不用分片键直接查询会导致 读扩散:即联合所有的表挨个查。要是使用分片键查的话就会直接通过分片规则直接定位到一张表中(比如增加查询条件分片键 gid
后,就会根据 gid
Hash 到某一张表中,要查到数据就集中在那里)
所以即使用户传不来 gid
,我们也得要事先拿到 gid
再去查表,就类似在不改变原表索引的基础上,原表设计了固定的索引,我们即使没有索引也要建张表让原始查询条件走索引。
这不恰好是倒排索引的设计思想吗?无非就是在大量数据的场景下依然能提供普通索引列或其他更多维度的查询。所以路由表正好是提供走索引拿到分片键的捷径