当前位置: 主页 > 数据库

oracle数据库缓存机制-oracle的缓存机制

发布时间:2023-02-08 14:11   浏览次数:次   作者:佚名

点击上方“Java领袖”,选择“热门公众号”

干货第一时间送达!

介绍

为什么写这篇文章?

首先,缓存以其高并发、高性能的特点,在项目中得到了广泛的应用。 对于读取缓存oracle数据库缓存机制,大家没有疑惑,都是按照下图的流程进行业务操作。

oracle的缓存机制_数据库缓存机制_oracle数据库缓存机制

但是在更新缓存方面,更新完数据库之后,是更新缓存还是删除缓存。 或者先删除缓存,再更新数据库。 其实大家之间也有很多争论。 目前还没有综合性的博客分析这些解决方案。 所以博主冒着被大家喷的风险写了这篇文章。

文章结构

本文由以下三个部分组成

1.解释缓存更新策略

2.分析每种策略的缺点

3、针对不足之处提供改进方案

文本

我先解释一下。 理论上,为缓存设置一个过期时间是保证最终一致性的一种解决方案。 在这种方案下,我们可以为缓存中存储的数据设置一个过期时间,所有的写操作都以数据库为准,我们只对缓存操作尽力而为。 也就是说,如果数据库写入成功,缓存更新失败,只要到了过期时间,后续的读请求自然会从数据库中读取新的值,然后回填缓存。 因此,接下来讨论的思路不依赖于为缓存设置过期时间的方案。

在这里,我们讨论三种更新策略:

先更新数据库,再更新缓存

先删除缓存,再更新数据库

先更新数据库,再删除缓存

应该没人问我为什么没有先更新缓存再更新数据库的策略。

1.先更新数据库,再更新缓存

oracle数据库缓存机制_oracle的缓存机制_数据库缓存机制

这个计划普遍遭到大家的反对。 为什么? 有两个原因。

原因1(线程安全角度)

同时有更新操作的请求A和请求B,那么就会有

(1)线程A更新数据库

(2)线程B更新数据库

(3)线程B更新缓存

(4)线程A更新缓存

意思是请求A更新缓存应该早于请求B更新缓存,但是由于网络等原因,B更新缓存比A早,造成脏数据,所以不考虑。

原因二(业务场景视角)

有两点:

(1) 如果你有写数据库场景多,读数据场景少的业务需求,采用这种方案会导致数据还没读完就频繁更新缓存,浪费性能。

(2)如果你写入数据库的值不是直接写入缓存,而是经过一系列复杂的计算后才写入缓存。 然后,每次写入数据库后,写入缓存的值都会重新计算,这无疑是一种性能浪费。 显然,删除缓存更合适。

接下来的讨论是争议最大的,先删除缓存,再更新数据库。 或者先更新数据库,再删除缓存。

2.先删除缓存,再更新数据库

该程序导致不一致的原因是。 同时有一个更新操作的请求A,还有一个查询操作的请求B。 那么就会出现下面的情况:

(1)请求A执行写操作,删除缓存

(2)请求B查询,发现缓存不存在

(3)请求B查询数据库获取旧值

(4)请求B将旧值写入缓存

oracle数据库缓存机制_oracle的缓存机制_数据库缓存机制

(5)请求A将新值写入数据库

上述情况会导致不一致。 而且,如果不采用缓存设置过期时间的策略,数据永远是脏数据。

那么,如何解决呢?采用延迟双删策略

伪代码如下

public void write(String key,Object data){
       redis.delKey(key);
       db.updateData(data);
       Thread.sleep(1000);
       redis.delKey(key);
   }

转换成中文说明是

(1) 先清除缓存

(2)重新写数据库(这两步和之前一样)

(3)休眠1秒,再次清除缓存

这样就可以再次删除1秒内产生的缓存脏数据。

那么,这个1秒是怎么确定的,应该休眠多长时间呢?

针对以上情况,请读者评估自己项目读取数据业务逻辑的耗时。 那么,写数据的休眠时间可以根据读数据耗时的业务逻辑增加几百毫秒。 这样做的目的是保证读请求结束oracle数据库缓存机制,写请求可以删除读请求造成的缓存脏数据。

如果使用mysql的读写分离架构呢?

ok,这样的话,数据不一致的原因如下,还是有两个请求,一个请求A进行更新操作,一个请求B进行查询操作。

(1)请求A执行写操作,删除缓存

(2)请求A向数据库写入数据,

(3)请求B查询缓存,发现缓存没有值

(4)请求B向从库查询。 此时主从同步还没有完成,所以查询的是旧值

(5)请求B将旧值写入缓存

数据库缓存机制_oracle数据库缓存机制_oracle的缓存机制

(6)数据库完成主从同步,从库成为新值

以上情况就是数据不一致的原因。 还是用双删延迟策略。 但是修改了sleep时间,主从同步的延迟时间增加了几百ms。

采用这种同步淘汰策略,如果吞吐量降低了怎么办?

好的,然后将第二个删除设置为异步。 自己开一个线程,异步删除。 这样写的请求就不需要sleep一段时间就可以返回了。 这样做会增加吞吐量。

第二次删除,删除失败怎么办?

这是一个很好的问题,因为第二次删除失败,出现了下面的情况。 还有两个请求,一个请求A进行更新操作,一个请求B进行查询操作。 为了方便起见,假设它是一个单一的数据库:

(1)请求A执行写操作,删除缓存

(2)请求B查询,发现缓存不存在

(3)请求B查询数据库获取旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库

(6) Request A试图删除Request B写入缓存的值,但是失败了。

ok,也就是说。 如果第二次缓存删除失败,缓存和数据库不一致的问题又会出现。

如何解决?

具体解决办法,看博主对(3)更新策略的分析。

3.先更新数据库,再删除缓存

首先,一句话。 老外提出了一种叫做“Cache-Aside pattern”的缓存更新例程。其中指出

另外,知名社交网站facebook也在论文“Scaling Memcache at Facebook”中提出,他们也是采用先更新数据库再删除缓存的策略。

这种情况不存在并发问题吗?

oracle数据库缓存机制_数据库缓存机制_oracle的缓存机制

没有。假设会有两个请求,一个请求A进行查询操作,一个请求B进行更新操作,那么就会出现下面的情况

(1)缓存刚刚失败

(2)请求A查询数据库,得到一个旧值

(3)请求B将新值写入数据库

(4)请求B删除缓存

(5)请求A将找到的旧值写入缓存

ok,如果出现以上情况,确实会出现脏数据。

然而,这种情况发生的可能性有多大?

上述情况的发生有一个先天条件,即步骤(3)中的写数据库操作比步骤(2)中的读数据库操作耗时少,这样就可以使步骤(4)先于步骤(5)。 不过仔细想想,数据库的读操作比写操作快多了(不然为什么要做读写分离,读写分离的意思是因为读操作速度更快,占用资源更少),所以步骤 (3) 比步骤 (2) 花费的时间更少,这种情况很少见。

假设,有人非要吵架,得了强迫症,非要解决怎么办?

如何解决上面的并发问题呢?

首先,为缓存设置一个有效时间是一个解决方案。 其次,采用策略(2)中给出的异步延迟删除策略,保证在读请求完成后执行删除操作。

不一致还有其他原因吗?

是的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的问题。 删除缓存失败怎么办? 不会有矛盾吗? 比如写数据的请求写入数据库,删除缓存失败,就会出现不一致的情况。 这也是缓存更新策略(2)最后留下的问题。

怎么解决?

提供有保证的重试机制就足够了。 这里有两组解决方案。

选项一:

如下所示

oracle的缓存机制_数据库缓存机制_oracle数据库缓存机制

oracle数据库缓存机制_oracle的缓存机制_数据库缓存机制

流程如下

(1) 更新数据库数据;

(2) 各种问题删除缓存失败

(3)将要删除的key发送到消息队列

(4)自己消费消息,得到需要删除的key

(5) 继续重试删除操作,直到成功

但是,这种解决方案的缺点是会导致大量入侵业务代码。 于是就有了第二个方案。 方案二,启动一个订阅程序,订阅数据库的binlog,获取需要操作的数据。 在应用程序中,启动另一个程序从订阅程序中获取信息,并删除缓存。

方案二:

oracle数据库缓存机制_数据库缓存机制_oracle的缓存机制

流程如下图所示:

(1)更新数据库数据

(2)数据库将操作信息写入binlog日志

(3)订阅程序提取需要的数据和密钥

(4)再创建一个非业务代码获取信息

(5)尝试删除缓存操作,发现删除失败

(6)将信息发送到消息队列

(7) 再次从消息队列中获取数据,再次尝试操作。

备注:上面的binlog订阅程序在mysql中有一个现成的中间件canal,可以完成订阅binlog日志的功能。 至于oracle,博主目前不知道有没有现成的中间件可以使用。 另外,博主对重试机制使用了消息队列的方式。 如果对一致性的要求不是很高,就在程序中另外开启一个线程,每隔一段时间重试即可。 这些大家可以自由灵活的使用,只是提供一个思路。

总结

这篇文章其实是对网上现有一致性方案的总结。 关于先删除缓存再更新数据库的更新策略,也有维护一个内存队列的方案。 博主看了看,觉得实现起来极其复杂,也没有必要,所以文章里没必要给出。 最后希望大家有所收获。

Java负责人