那么第二种条件为隐式设置。比如订单列表通常是按照订单创建时间来排序,那么当翻页到限制的条件时,我们可以改变这个时间。
sharding node 1: orderID createDateTime 100000 2018-01-10 10:10:10 200000 2018-01-10 10:10:11 300000 2018-01-10 10:10:12 400000 2018-01-10 10:10:13 500000 2018-01-20 10:10:10 600000 2018-01-20 10:10:11 700000 2018-01-20 10:10:12 sharding node 2: orderID createDateTime 110000 2018-01-11 10:10:10 220000 2018-01-11 10:10:11 320000 2018-01-11 10:10:12 420000 2018-01-11 10:10:13 520000 2018-01-21 10:10:10 620000 2018-01-21 10:10:11 720000 2018-01-21 10:10:12我们假设上面是一个订单列表,orderID 订单号大家就不要在意顺序性了。因为 sharding 之后所有的 orderID 都会由发号器统一发放,多个集群多个消费者同时获取,但是创建订单的速度是不一样的,所以顺序性已经不存在了。
上面的两个 sharding node 基本上订单号是交叉的,如果按照时间排序 node 1 和 node 2 是要交替获取数据。
比如我们的查询条件和分页参数:
where createDateTime>'2018-01-11 00:00:00' pageParameter:pageSize:5、currentPage:1获取的结果集为:
orderID createDateTime 100000 2018-01-10 10:10:10 200000 2018-01-10 10:10:11 300000 2018-01-10 10:10:12 400000 2018-01-10 10:10:13 110000 2018-01-11 10:10:10前面 4 条记录来自 node 1 后面 1 条数据来自 node 2 ,整个排序集合为:
sharding node 1: orderID createDateTime 100000 2018-01-10 10:10:10 200000 2018-01-10 10:10:11 300000 2018-01-10 10:10:12 400000 2018-01-10 10:10:13 500000 2018-01-20 10:10:10 sharding node 2: orderID createDateTime 110000 2018-01-11 10:10:10 220000 2018-01-11 10:10:11 320000 2018-01-11 10:10:12 420000 2018-01-11 10:10:13 520000 2018-01-21 10:10:10按照这样一直翻页下去每翻页一次就需要在 node 1 、node 2 多获取 5 条数据。这里我们可以通过修改查询条件来让整个翻页变为重新查询。
where createDateTime>'2018-01-11 10:10:13'因为我们可以确定在 ‘2018-01-11 10:10:13’ 时间之前所有的数据都已经查询过,但是为什么时间不是从 ‘2018-01-21 10:10:10’ 开始,因为我们要考虑并发情况,在 1s 内会有多个订单进来。
这种方式是实现最简单,不需要借助外部的计算来支撑。这种方式有一个问题就是要想重新计算分页的时候不丢失数据就需要保留原来一条数据,这样才能知道开始的时间在哪里,这样就会在下次的分页中看到这条时间。但是从真实的深分页场景来看也可以忽略,因为很少有人会一页一页一直到翻到500页,而是直接跳到最后几页,这个时候就不存在那个问题。
如果非要精准控制这个偏差就需要记住区间,或者用其他方式来实现了,比如全量查询表、sharding 索引表、最大下单 tps 值之类的,用来辅助计算。
(可以利用数据同步中间件建立单表多级索引、多表多维度索引来辅助计算。我们使用到的数据同步中间件有 datax、yugong、otter、canal 可以解决全量、增量同步问题)。
分表策略分表有多种方式,mod、rang、presharding、自定义路由,每种方式都有一定的侧重。
我们主要使用 mod + presharding 的方式,这种方式带来的最大的一个问题就是后期的节点变动数据迁移问题,可以通过参考一致性 hash 算法的虚拟节点来解决。
数据表拆分和 cache sharding 有一些区别,cache 能接受 cache miss ,通过被动缓存的方式可以维护起 cache 数据。但是数据库不存在 select miss 这种场景。
在 cache sharding 场景下一致性 hash 可以用来消除减少、增加 sharding node 时相邻分片压力问题。 但是数据库一旦出现数据迁移一定是不能接受数据查询不出来的。所以我们为了将来数据的平滑迁移,做了一个 虚拟节点 + 真实节点 mapping 。
physics node : node 1 node 2 node 3 node 4 virtual node : node 1 node 2 node 3.....node 20 node mapping : virtual node 1 ~ node 5 {physics node 1} virtual node 6 ~ node 10 {physics node 2} virtual node 11 ~ node 15 {physics node 3} virtual node 16 ~ node 20 {physics node 4}为了减少将来迁移数据时 rehash 的成本和延迟的开销,将 hash 后的值保存在表里,将来迁移直接查询出来快速导入。
hash 片 2 的次方问题在我们熟悉的 hashmap 里,为了减少冲突和提供一定的性能将 hash 桶的大小设置成 2 的 n 次方,然后采用 hash&(legnth-1) 位与的方式计算,这样主要是大师们发现 2 的 n 次方的二进制除了高位是 0 之外所有地位都是 1,通过位与可以快速反转二进制然后地位加 1 就是最终的值。