1 背景
对外提供的Web应用为了提高查询速度都会使用缓存来提高查询速度。当数据库发生变更时,我们需要更新缓存数据,从而保证数据库与缓存的数据的最终一致。为了有效监听数据变更,通常会基于数据变更监听系统来获取更新信息,随后发送到下游(可以通过MQ,RPC等方法),最后下游来更新缓存。 常见的数据变更监听系统包括DataBus、Amazon Data Pipeline、阿里DRC。
2 基于数据变更系统缓存更新结构
下图中关系数据库以Myql为例,MQ以kafka为例,缓存以Redis为例,数据变更抓取系统以DataBus为例。
- DataBus获取MySql变更记录
- DataBus将变更记录发送到Kafka中
- 业务侧系统监听到Kafka消息后,查询最新的主库信息后更新Redis
3 基于数据变更系统缓存更新注意问题
第二节讲述基于数据变更系统更新缓存结构。实际上的细节结构要更复杂,从起点开始就可能会产生一系列问题需要我们注意。
为了保证高可用和性能,通常都会使用mysql主从结构,如1主N从。下列问题中我们均在1主N从背景下来分析。
- 数据变更系统抓取从库变更信息:有些情况下,为了减少主库流量,会通过从库拉取变更信息。当产生主从延迟时结合实际业务中的缓存策略就会导致缓存不一致的问题。 如果变更较小,也可考虑从主库获取变更信息。
- 业务数据分片:拉BinLog数据,顺序获取保证不会产生混乱。但给下游业务推送,为保证业务正常,会顺序推送。如果不按顺序推送会出现问题。例如同一个数据变更两次。如果乱序会导致业务状态错乱。根据业务特点可以发现,顺序相关的数据需要顺序发送,而顺序无关的数据可并行发送。如:用户信息记录只有一个单表,每个用户是一条记录。只要保证按用户ID顺序发送即可,不同用户ID可并行发送。 如果涉及多个表,选择好对应的业务关联字段进行分片即可。
- 根据不同mq进行特性设置:如果我们使用的是Kafka,通知使用多Partiion,为了保证消息有序,需要按业务设定的分片信息,将同一分片的信息发到同一Partition。
由于向Kafka发送消息时,可能存在某个Partition故障。最后会导致重试时将消息发送到一个正常的Parition上。这种情况也会导致乱序的发生。
最后当消费者消费某个Partition消息时,如果用了单Partition,但开启了并行消费,最后也可能导致乱序问题。
因此使用不同的MQ时,要分析其特性,设置选项,保证顺序,从而避免更新缓存时出现最终也不一致的问题。
4 写时更新
从第三节可知,基于数据变更抓取系统进行缓存更新,由于主从及消费顺序问题,可能会导致缓和数据库数据最终存不一致,混乱的情况。而使用数据变更系统的一个主要原因是解耦系统。变更和消息发送的控制均由独立系统负责。但最终缓存的更新还是要在业务侧做。 此外日常还存在刷数据的场景下,会存在直接写SQL更新数据。这时通过数据变更抓取系同来更新,也会简化工作。
但实际业务中直接写SQL更新数据频率较低。主要还是基于接口进行。因此在系统中再增加写时更新。写接口写数据后,同时异步出发缓存更新操作,进一步降低3中提到的潜在问题,导致的最终不一致问题。当前此种方式也会增加开发工作量。
5 总结
缓存是提高查询速度的有效手段,但缓存更新,保证数据库和缓存最终一致又有许多问题需要注意。基于数据变更抓取系统和直接写时更新各有优缺点,因此结合实际业务场景结合使用。