Redis 数据安全

1. 前言

       这里所说的数据安全并不是像Java语言中,多个线程修改一个变量如i++,造成结果错误。我们知道Redis是单线程的,即使有在多的客户端进行i++操作,结果也总是符合预期。而这里的数据安全更多的是指缓存数据不符合我们的预期。

2. 缓存与数据库双写时的数据一致性

       对于缓存和数据库的操作是两个操作,并不是原子性的,所以如果没有合适的方法去保证,一定会带来数据不一致性的情况。

2.1 cache aside pattern

       最经典的缓存+数据库读写的模式,cache aside pattern

  • 读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先删除缓存,然后再更新数据库。

       为什么选择删除缓存而不是先更新缓存,或者说是更新数据库成功后,在将删除的缓存进行重新保存?

       首先说一下第一种方案的缺陷:

       如果我们先更新缓存,但是如果更新数据库失败,那么此时便造成数据库与缓存状态不一致。

       第二种方案的缺陷:

       该方案相当于我们每进行一次数据的更新,就保存一次缓存,那么这些缓存是否一定是热点数据呢?如果不是的话,其不仅浪费内存,而且缓存并不仅仅是数据库中的数据,更有可能需要经过大量的计算才能保存到缓存中。

       如我之前写的一个视频排行榜程序,需要进行全表扫描,根据视频的播放量和评论数计算热度。如果视频的播放量每播放一次,便进行一次热度计算,毫无疑问,很浪费资源。

       而选择只删除缓存,不进行缓存数据重新更新,是一种懒加载的思想,当我们需要缓存的时候再重新保存。

2.2 cache aside pattern 一定能保证数据一致性吗

       考虑如下场景,序号代表执行过程,注意并不意味着请求先到来,其先来的请求一定先于后来的请求执行完毕。

在这里插入图片描述
       可以看到,即使采用cache aside pattern,在请求A更新完数据库之后,数据库中的数据与缓存中的数据依然处于不一致的状态。

       该情况通常只会出现在只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。并且如果并发量很低的话,特别是读并发很低,每天访问量就1万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。

2.3 数据库与缓存更新与读取操作进行异步串行化

       为解决上述的问题,我们可以采用数据库与缓存更新与读取操作进行异步串行化的方案。

在这里插入图片描述
       我们在JVM针对该商品创建一个内存队列,只有前面的请求结束才可以轮到后面的请求执行,如此便可以做到缓存与数据库的一致性。

       但是如果我们对于每一个读请求,都将其加入到内存队列中,毫无疑问是比较浪费时间的。我们可以用过一个volatile变量维护当前内存队列中写请求的个数,只有写请求个数不为0时,才将其加入队列,否则直接进行读取操作即可。

       在上面我们仅仅是创建了一个内存队列,倘若我们有多个商品,只使用一个内存队列显然并发性非常有限,可以针对每一种或每一类商品创建一个内存队列,这样粒度降低,并发性便可以提供。

在这里插入图片描述

       上述方案在多服务实例部署时,有这样一种情况,即对于同一个商品的操作被路由分发的不同的实例中,因为内存队列是基于JVM的,所以依然有可能造成数据的不一致。

       解决方案:通过nginx进行路由分流时,同一个商品使其分流到一台实例中。

3. redis 的并发竞争问题该如何解决

       多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。

在这里插入图片描述
       Redis中的数据变化可能有如下几种情况:

	v0 --> v1 --> v2 -- v3
	v0 --> v2 --> v1 -- v3
	v0 --> v3 --> v1 -- v2
	......

       如果我们想让数据按时间的先后顺序进行保存,即数据变化的方式为v0 --> v1 --> v2 -- v3,可以采用时间戳+CAS的方式。

       如果当前的时间戳大于缓存中的数据库才允许操作。

在这里插入图片描述
       如此便可以保证缓存中的数据符合我们最终的目标数据。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章