图片来自抱途网有一天,阿雄去面试了!于是就出现了这样的情况:面试官:“阿雄,自我介绍一下!”阿雄:“我叫阿雄,国际电商公司的!”面试官:“我看到你在你的项目中使用了Elasticsearch,你是如何同步数据的?”阿雄:“在代码中写的时候,写数据库的时候,同时写Elasticsearch!”面试官:“那怎么保证写数据库和写Elasticsearch的原子性?如果写数据库成功,写Elasticsearch失败怎么办?”阿雄:“我先回去等通知!”其实本文所讨论的数据同步策略并不局限于两个固定的存储系统,而是想讨论一种通用的数据同步策略。主要分为以下三个部分:背景介绍双写缺点改进方案在产品韩的带领下,业务快速成长。阿雄发现数据库越来越慢,于是阿雄加了一些缓存,比如Redis,用来缓存一些数据,提高系统的响应速度。过了一段时间,产品韩发现搜索速度很慢,就让阿雄换了。阿雄在网上发现现在业界使用一些Elasticsearch做一些全文检索的操作,于是阿雄把一些需要全文检索的数据放到了Elasticsearch中,提高了系统的搜索能力!随着数据的膨胀,阿雄慢了下来才发现,对数据库进行了一些数据分析操作,性能明显跟不上。于是阿雄将数据库中的数据导入到Hadoop中,然后对数据进行分析。省略一万字……终于,阿雄和产品韩幸福的在一起了。OK,好了,现在分析上面的场景!思考第一个问题。①DataBase、Redis、Elasticsearch、Hadoop中的数据是相关的还是相互独立的?很明显是有关系的,这些数据源里面的数据都是有关联的。只是形式不同!比如一条Product数据,在数据库:Redis中,key为product:pId:1,value为:{"pId":"1","productName":"macbook"}如图以上,只是数据格式不同!那么,现在想想第二个问题。②既然这些数据源之间的数据是相关的,那么如何保证这些数据源之间的数据一致性呢?一个比较简单易思的解决方案是在程序中硬编码。比如现在有两个数据源DataSource1和DataSource2,我们往里面写入数据。代码如下:ProductService{\\??omitpublicvoidsyncData(){x1.writeDataSource1();x2.writeDataSource2();}}这就是我们题目中提到的双写!那么,重写会带来什么危害呢?OK,继续往下看!双写的缺点①一致性问题比如我们现在有两个client,同时向两个DataSources写入数据:一个client向其中输入X为1,另一个client向其中输入X为5,则有会出现如下情况:如图,两个DataSources的数据不一致,一个是1,一个是5。除非接下来有新的请求,改变了x数据,才能出现这种现象更正!否则,你可能永远都不会知道。②原子性问题因为我们需要同时向DataSource1和DataSource2写入数据,所以需要保证:x1.writeDataSource1();x2.writeDataSource2();这两个操作一起成功,或者一起失败!如果使用双写方式,这个问题就无法避免!那么有没有通用的方法来解决这些问题呢?可以的,只要数据有变化就可以按顺序记录下来!那么具体是怎么做的呢,我们继续往下看吧!改进方案假设,如果我们能按顺序记录数据,写入消息队列,然后其他系统按消息顺序恢复数据,看看会发生什么?此时的架构图如下:在这个架构下,所有的数据变化都写到一个消息队列中去。所有其他数据源都可以从消息队列中恢复数据!那么,此时是否还存在一致性和原子性的问题呢?①一致性问题OK。在这种情况下,各个数据源之间的数据必须是一致的。因为在消息队列中已经定义了写入顺序,所以各个数据源都可以按照消息在消息队列中的顺序来恢复数据,不存在竞争。所以不会出现不一致的问题!②原子性问题OK,这样的话,写入DataSource失败怎么办?比如网络有问题,这条消息恢复失败。这个问题其实很容易解决。一般我们在按照消息的顺序恢复数据的时候,都会记录下坐标。如果写入失败,则停止恢复数据。下次可以从这个坐标恢复数据。但是在上图中,对DataBase的写是异步的。这不符合很多业务场景下“先写后读”的需求。因此,在实际执行中,进行了一些改动!一般的做法是提取数据库中的变化!如下图所示:图中的中间件,比如Oracle中的oraclegoldengate,可以提取数据变化。MySQL中的Canal可以提取数据的变化。至于消息队列,可以选择Kafka。直接抽取数据变化到Kafka,其他数据源从Kafka获取数据,避免直接重写造成一致性和原子性问题。小结本文就项目中常见的数据同步问题进行探讨,希望您有所收获。作者:孤烟编辑:陶家龙来源:转载自公众号孤烟(ID:zrj_guduyan)
