专业的SQL Server、MySQL数据库同步软件

如何确保缓存和数据库的双重写入一致性?

只要您使用高速缓存,就可能需要两次存储以及两次写入高速缓存和数据库。只要您是双重写入,您肯定会遇到数据一致性问题。那么如何解决一致性问题呢?

一般来说,如果允许缓存与数据库稍有不一致,也就是说,如果您的系统不严格要求”缓存+数据库”必须保持一致,则最好不要执行此程序,即is:序列化读取请求并将写入请求写入内存队列。

序列化可以确保不会出现不一致的情况,但是这也会导致系统的吞吐量大大降低,使用比正常情况下多一倍的机器来支持在线请求。

最经典的缓存+数据库读写模式是”缓存备用模式”。

读取时,请先读取缓存。如果高速缓存不可用,请读取数据库,然后取出数据并将其放入高速缓存中,然后返回响应。

更新时,请先更新数据库,然后再删除缓存。

为什么要删除缓存而不是更新缓存?

原因很简单。在许多情况下,在复杂的缓存方案中,缓存不仅是直接从数据库中获取的值。

例如,可以更新某个表的字段,然后相应的缓存需要查询其他两个表的数据并执行操作以计算缓存的最新值。

另外,更新缓存的成本有时会很高。这是否意味着每次修改数据库时,都必须更新相应的缓存?在某些情况下可能是这种情况,但在更复杂的缓存数据计算情况下则不是这种情况。如果您经常修改缓存中涉及的多个表,则缓存也会经常更新。但是问题是,是否会经常访问此缓存?

对于板栗,将与缓存相关的表的字段在1分钟内修改20次或100次,然后将缓存更新20次和100次;但是此缓存仅在1分钟内被读取一次,有很多冷数据。实际上,如果仅删除缓存,缓存将仅在1分钟内重新计算,并且开销大大减少。缓存用于计算缓存。

实际上,删除缓存而不是更新缓存是一个懒惰的计算思路。不论是否要使用,都不要每次都进行复杂的计算,而要在需要使用时重新计算。像mybatis,hibernate一样,都具有延迟加载的想法。查询一个部门,该部门会带来一个雇员列表,不必说每次查询该部门时,也会同时找到其中的1000名雇员的数据。在80%的情况下,仅检查该部门即可访问该部门的信息。首先检查部门,并拜访部门中的员工。然后,仅当您这次要访问员工时,您才可以进入数据库查询1000名员工。

问题:在删除缓存之前修改数据库。如果删除缓存失败,将导致数据库中的新数据和缓存中的旧数据,并且数据将不一致。

java 同步中间库 java数据库同步_数据库

解决方案思想:首先删除缓存,然后再修改数据库。如果数据库修改失败,则旧数据位于数据库中,并且高速缓存为空,因此数据不会不一致。由于读取时没有缓存,因此将读取数据库中的旧数据,然后将其更新到缓存中。

数据已更改,请先删除缓存,然后再修改数据库(当前尚未修改)。发出请求,读取缓存,发现缓存为空,去查询数据库,发现修改前的旧数据,然后将其放入缓存中。随后的数据修改过程完成了数据库修改。完成后,数据库和缓存中的数据是不同的…

为什么在亿万流量和高并发的情况下,缓存中会出现此问题?

仅当同时读取和写入一条数据时,才可能出现此问题。实际上,如果您的并发性很低,尤其是读取的并发性很低,并且每天的访问次数是10,000次,那么很少会出现上述不一致的情况。但是问题在于,如果每天有数亿流量,并且每秒有成千上万的并发读取,只要每秒有数据更新请求,则可能会发生上述数据库+高速缓存不一致的情况。

解决方案如下:

更新数据时,根据数据的唯一标识符,将操作路由并发送到jvm内部队列。读取数据时,如果发现数据不在缓存中,则根据唯一标识符路由后,重新读取数据+更新缓存的操作将发送到同一jvm内部队列。

队列对应一个工作线程,每个工作线程依次获取相应的操作,然后逐个执行它们。在这种情况下,数据更改操作将首先删除缓存,然后更新数据库,但是更新尚未完成。此时,如果读取请求结束并且读取了空的缓存,则可以先将缓存更新请求发送到队列。这时,请求将被积压在队列中,然后等待高速缓存更新完成。

这里有一个优化点。实际上,在队列中,将多个更新缓存请求连接在一起是没有意义的,因此可以对其进行过滤。如果您发现队列中已经有更新缓存的请求,则无需输入”更新请求”操作,只需等待上一个更新操作请求完成即可。

与该队列相对应的工作线程完成对上一个操作的数据库的修改后,它将执行下一个操作,即更新缓存的操作。此时,将从数据库中读取最新值,然后将其写入缓存。

如果请求仍在等待时间范围内,并且连续轮询发现可以获取该值,则直接返回;否则,返回0。如果请求等待的时间超过一定的时间长度,则这次直接从数据库中读取当前的旧值。

在高并发情况下,解决方案应注意以下问题:

读取请求长时间被阻止

因为读取请求非常不同步,所以我们必须注意读取超时的问题。每个读取请求必须在超时时间内返回。

该解决方案的最大风险点是,可能会频繁更新数据,从而导致队列中进行大量更新操作,然后读取请求将出现大量超时,最后大量的请求将直接进入数据库。确保通过一些模拟的真实测试,以查看数据更新的频率。

另一点,因为在队列中,可能有多个数据项的更新操作积压,因此您需要根据业务情况进行测试,您可能需要部署多个服务,每个服务共享一些数据更新操作。如果一个内存队列实际压缩了100项库存修改操作,则完成每个库存修改操作需要10毫秒,那么最后一个产品读取请求可能在获取数据之前等待10 * 100 = 1000ms = 1s,这导致长期的阻止读取请求。

确保根据实际的业务系统操作进行一些压力测试,并模拟在线环境,以查看在最繁忙的时间内内存队列可能压缩了多少个更新操作,这可能会导致最后一次更新。与操作挂起相对应的读取请求,如果读取请求在200ms处返回,则如果计算出该请求,即使是最繁忙的时间,积压的10次更新操作,最多等待200ms,也可以。

如果内存队列中的更新操作过多,则必须添加计算机以允许部署在每台计算机上的服务实例处理更少的数据,然后每个内存队列中的更新操作积压将更少。

根据以前的项目经验,通常来说,数据写入频率非常低,因此实际上,通常来说,队列中更新的积压应该很少。像此类用于读取高并发性和读取缓存体系结构的项目一样,一般来说,写入请求很少,并且每秒几百个QPS很好。

让我们做一个粗略的估计。

如果每秒有500个写操作,如果分成5个时间片,每200ms进行100个写操作,则放入20个内存队列,每个内存队列中可能有5个写操作的积压。在对每个写操作进行性能测试之后,通常会在20ms左右完成,因此对每个内存队列的数据的读取请求将挂起一段时间,并且肯定会在200ms内返回。

经过简单的计算,我们知道数百台计算机中支持的写入QPS没问题。如果写入QPS扩展了10倍,则扩展机器,将机器扩展10倍,每台机器有20个队列。

并发读取请求太高

在这里,您还必须进行压力测试,以确保在碰巧遇到上述情况时,还存在突然大量的读取请求会在服务上挂起并延迟数十毫秒的风险,请参阅如果可以进行该服务以维持生存,则需要多少台机器来承载最大限制情况的峰值。

但是由于并非所有数据都在同一时间更新,因此缓存不会同时失效,因此每次缓存少量数据时可能会使该数据无效,然后读取请求对应那些数据过来,并发量就不会特别大。

用于多服务实例部署的请求路由

也许此服务部署了多个实例,那么必须确保传递执行数据更新操作和缓存更新操作的请求Nginx服务器被路由到相同的服务实例。

例如,对同一产品的读写请求都被路由到同一台计算机。您可以根据服务之间的特定请求参数进行哈希路由,也可以使用Nginx的哈希路由功能,等等。

热门产品的路由问题,导致请求偏斜

如果对某个产品的读写请求特别高,所有请求都在同一台计算机上位于同一队列中,可能会对机器造成过大的压力。也就是说,由于仅在更新产品数据时才清除高速缓存,然后这会导致并发读写,因此实际上必须根据业务系统对其进行查看。如果更新频率不太高,则此问题的影响不会特别大。但是某些机器的负载可能会更高。