一道面试题: 订单表按照订单ID分库分表之后,如何根据其他字段进行高性能查询


前言

前几天滴滴第三轮面试的时候, 遇到一道面试题, 大意是说现在给定一个订单表, 是按照订单ID来进行分库分表的, 那么如果想要根据订单的商户ID来进行查询某个商户的一些订单, 或者查询某个用户的订单, 该如何处理? 当时回答的不是很好, 后来询问了原来的同事, 发现可以通过Elasticsearch来构建二级索引或者干脆直接把全量数据存储在ES中的方式来处理分库分表之后的多条件查询以及JOIN查询

Elasticsearch构建二级索引

可以将所需要的进行判断的字段, 简单理解也就是SQL语句中WHERE语句中进行判断的字段, 以及分库分表的分区字段, 在这道题中就是订单ID, 存储进Elasticsearch中. 构建一个二级索引, 每次查询的时候现在Elasticsearch中进行查询, 获取到订单ID, 然后再根据订单ID去MySQL执行查询就好了, 如果是聚合查询, 那么就需要自行在server中进行聚合

整个过程类似于MySQL中的非主键索引在执行非覆盖索引查询的时候, 执行的回表操作

Elasticsearch存储全部字段

其实还可以将数据表的全部字段以及数据再存储一份到Elasticsearch, 查询的时候直接查询Elasticsearch中的数据就好了, 也不需要再查询数据库, 缺点是Elasticsearch中存储的数据会很多

MySQL与Elsaticsearch中数据的增删改查如何保证事务?

上面提到的是使用Elasticsearch构建MySQL二级索引的查询过程, 但是这中间还有一个问题, 就是Server在写入或者修改数据的时候, 如何保证MySQL与Elasticsearch修改的事务性. 也就是MySQL中执行了增删改的动作, Elasticsearch如何保证也同步的增删改等. 这个如果不能保证, 那么就会造成数据不一致的情况.

我们在代码中最直观的一种做法就是同步写入, 伪代码如下:

@Transcational
public void saveData() {
    ...
    orderDAO.insert(data);
    esClient.insert(data);
    ...
}

但是上述的伪代码存在的问题就是, 事务性其实是不能得到保障的, 就算是先写MySQL, 然后写入ES, ES抛了异常导致MySQL回滚也不行, 我们在实际生产环境的代码没有伪代码这么简单, ES客户端的操作后面也可能还有业务逻辑, 这个saveData方法也有可能被别的事务调用, 所以这种方式其实是不行的.

订阅binlog

基于阿里巴巴开源的canal, 订阅MySQL的binlog, 把数据近实时同步到Elasticsearch中, 写入到Elasticsearch的这个过程只需要保证at least onece就可以了, 在写入Elasticsearch时自行保证幂等性

canal地址: https://github.com/alibaba/canal

编程式事务+补偿

前面的伪代码采取的是声明式的方式, 无法保证回滚了MySQL数据之后Elasticsearch中的数据也能回滚, 可以采用编程式事务的方式来操作, 回滚了MySQL数据之后自行控制Elasticsearch的回滚

Elasticsearch集群也有可能发生问题或者故障, 为了避免Elasticsearch的问题导致整个业务的失败, JD的一篇文章提到一种做法, 每当Elasticsearch写入失败之后, 就把数据写入到MySQL中, 然后构建一个Worker任务定时扫描这些数据, 做补偿

JD的文章链接: https://dbaplus.cn/news-11-2397-1.html

使用消息队列同步更改数据库

我们还可以使用消息队列来传输数据的更改, 但是值得注意的是, 需要保证消息的有序消费, 构造同一行数据的增删改乱序, 也会导致数据错乱的问题出现

弊端

上文提到的两种方案也有一些弊端

  1. 数据不一致
    1. 数据同步需要时间
    2. 数据写入到Elasticsearch之后, 保存进去的数据不能立即被查询到, 需要等待刷新周期(1s)
  2. 如果借助的canal或者消息队列这种中间件出现故障, 还需要有降级的手段
posted @ 2020-12-27 13:12  梅子酒zZ  阅读(1843)  评论(0编辑  收藏  举报