数据查询ES设计演变

分享到:

文章目录

这里总结一下过去曾参与的一个系统中,对于订单查询设计的演变过程

第一阶段:仅数据库查询

这个是最初始的阶段,数据查询直接走数据库查询。这种方式会完全依赖于数据库的性能,随着数据越来越来,查询效率也会越来越低

第二阶段:hibernate 拦截器检测数据变化并更新 ES

随着订单数据量的增多,原先直接查询数据库的方式已经无法满足对性能的要求。因此引入了 Elastic Search 来作为快速订单查询这个功能的支撑,同时增加了一个ES查询服务,用来对ES做读写操作。

考虑到某些租户的数据量很小,直接查询数据库就满足业务上的客户端响应需求。因此,增加了一个租户级别上的配置,用来开启或者关闭 ES 查询。

写 ES

hibernate拦截器检测数据变化并更新ES

由于这个系统使用的是 hibernate 来做数据的持久化,因此添加了一个 hibernate 拦截器,用来监听数据持久化的事件,根据不同的数据变更操作(新增、更新或删除),在 Elastic Search 中做不同的处理:

  • 如果是在数据库中新增一个订单, 则在 ES 中创建一个文档
  • 如果是在数据库中更新一个订单, 则在 ES 中更新对应文档
  • 如果是在数据库中删除一个订单, 则在 ES 中删除对应文档

如果租户没有开启 ES 查询,那么订单数据更新的时候,也不会写 ES 数据,这样就可以节省存储资源。

读 ES

获取订单数据

在读取订单数据的时候,会先判断某个租户是否开启了 ES 查询,如果未开启就查数据库,开启则通过 ES 查询服务来获取订单数据。

存在的问题

利用 ES 查询,响应速度明显得到提升。但是在实际的运行过程中,发现了另外一个问题:应用如果重启(比如新迭代发布),有些在内存中未处理的数据就无法同步到 ES,这就导致 ES 中的数据没有及时更新。

第三阶段:引入 Kafka

引入消息队列中间件可以解决上面的问题,将保证消息消费的任务交给中间件,而不需要在业务应用上实现这个功能。

写 ES

通过kafka写数据

与原先的方案相比,做了一些改动。hibernate 拦截器检测到订单数据变化后,将数据先写入到 Kafka,然后订单的 ES 查询服务去消费 kafka 的数据。

写入到 kafka 的数据与原先也有些变化。原先的方案中,将变更的数据与操作类型都作为参数,ES 查询服务直接根据这些参数修改 ES 中的文档数据。新的方案中,kafka 中只放租户 ID 与订单 ID,通过这两个参数查询数据库来获取信息,如果数据库中没有数据,则表示订单被删除,就删除 ES 中的文档;如果数据库中有数据,则创建或者更新ES 文档。这么做的好处就是:

  • kafka 中的数据少
  • 不用关心对数据进行了什么操作,不需要为增删改分开写逻辑,都合并为一个逻辑,就是查询数据库,根据查询结果来来处理。这样就简化了代码逻辑
  • 如果将来 ES 索引中需要增加新的字段,只需要修改查询数据库的 SQL 与写 ES 这两个部分的代码即可,降低了一些复杂度。另外,通过订单 ID 来查询数据库,本身的耗时也不会有多少。

读 ES

这个部分没有变化

第四阶段:按照租户拆分成不同的索引

公司的业务与系统设计,订单数据增长是非常快的。随着租户数量、业务量的增加,ES 索引中的文档数量越来越多,ES 查询也越来越慢。原先的方案里面,所有的租户数据都是放在一个 ES 索引里面的。因此需要将数据做拆分,不同租户的数据,放在不同的 ES 索引中

写 ES

不同租户写入不同索引

写数据的时候,根据租户 ID, 写入不同的 ES 索引。

读 ES

读数据的时候,也是根据租户 ID,从不同的 ES 索引中读取

第五阶段:拆分搜索服务

采用上面的方案,运行起来基本上已经没有问题了。只是从保持系统功能的稳定,与运维的角度来看,把原先 ES 同步服务拆分成下面三个,让每个服务专注于自己的事情:

  • 订单查询 (order-es-search):这个服务仅包含查询的逻辑。因为查询功能在较长时间内会保持稳定,将这个功能独立拆分出来是为了保持查询服务的高可用。其他部分功能的更新与重新部署,就与它无关了。
  • 订单增量同步(order-es-sync):这个服务是用来消费 kafka 消息,增量同步订单数据到 ES 中,仅处理最新变更的订单
  • 订单全量同步 (order-es-reload):这个服务是用来重新同步某个租户的所有订单的。适用于 ES 索引中需要新增字段时,将已有的存量数据全部更新。在部署层面,它仅在需要的时候才临时启动,在平时都是不需要部署的。出需要的时候,临时部署,而且可以给它临时分配较高的硬件资源,加快处理速度,数据处理完后立即关闭服务,回收资源。

写 ES

拆分后写数据

读 ES

拆分后读数据