Elasticsearch 是一款基于 Apache Lucene 构建的开源全文检索引擎,它能够轻松地进行大规模的横向扩展,以支撑 PB 级的结构化和非结构化海量数据的处理。而关系型数据库比较擅长对数据的管理,但对全文检索功能的支持相对不足,所以有时候一些实际项目需要将关系型数据库中的数据同步到 Elasticsearch 中,以提供更加强大的全文检索功能。另外,一些基于关系型数据库的历史遗留系统的存在,当遇到全文检索的新需求时,就更加需要将数据同步到 Elasticsearch 中。近日,在线银行支付平台 GoCardless 的软件工程师 Chris Sinjakli 发表了一篇题为《将数据从 PostgreSQL 同步到 Elasticsearch 的经验教训》的博文。在文章中,他结合自己的实际经历(GoCardless 使用Elasticsearch 增强搜索功能)总结了将数据从关系型数据库 PostgreSQL 同步到 Elasticsearch 的经验教训。
Chris 首先指出当需要把数据同时存储到 PostgreSQL 和 Elasticsearch 两个地方时,开发者需要深入考虑的一些问题,如当 Elasticsearch 处理有很大延迟时将会发生什么未知事情、如果更新时出现异常将会发生什么情况、怎么知道 Elasticsearch 正确处理了每次更新等。接下来 Chris 引出要解决以上问题必须做到异步的更新、达到最终一致性、进行索引重建。
关于如何做到异步更新,Chris 指出 GoCardless 开发团队构造了一个队列用于数据的异步同步,且通过线程池来协助处理。这样既可以单独更新,也可以批量更新,并使用基于 JSON 格式的数据和利用 Elasticsearch 的 API 保证了响应时间和可预知性。
关于如何确保一致性,Chris 指出 Elasticsearch 的更新API 不具有线程安全性,尤其在高并发更新时。如果只是调用该更新API 来索引更新数据的话,就有可能引起并发问题。不过,Elasticsearch 提供了一个具有乐观锁的索引版本系统,通过该系统就可以做到安全的更新。但是当在更新索引的同时,用户还是有可能搜索出脏数据。庆幸的是,Elasticsearch 还提供了另一种处理索引版本的方案,该方案是由发起请求的外部程序来设置版本类型并提供版本号,这样使得Elasticsearch 总是保持同步的文档具有最高版本号。GoCardless 开发团队考虑到PostgreSQL 的事务ID(64 位整数)在保证事务情况下能够实现自增,所以GoCardless 开发团队就使用PostgreSQL 的事务ID 作为版本号。这样就可以实现每次同步到Elasticsearch 的数据都是最合适的(尽管不是最新的),但最后仍会达到数据的一致性。
关于如何重建索引,Chris 指出以上的异步方式仍然存在丢失更新的可能,如网络分区下引起的问题。为了处理以上问题,GoCardless 开发团队采取周期地将最近写入到PostgreSQL 的记录进行一次批量同步并使用Elasticsearch 的 Bulk API 重新批量索引所同步数据的方案。该方案以较小的重复记录为代价彻底解决了更新丢失的问题,并且只需使用与原来同样的代码和在无需停止服务器的情况下即可实现索引重建。Chris 还特别指出,如果想在无需停止服务器的情况实现重建索引,这就需要从一开始就正确地使用 Elasticsearch 的索引别名。
最后,Chris 指出如果要构建更加良好的搜索体验,还有很多工作需要做,尤其是不同的应用程序有着不同的约束条件,所以他建议开发者在开始编写产品代码前就要深入思考相关问题及处理方案。
感谢郭蕾对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论