我们都知道,在开发过程中,很多时候都会用到我们的缓存,而缓存的种类也是五花八门的,我们今天来了解的就是关于缓存中的一种,那就是 redis。
redis是一个key-value存储系统。
和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。
区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
今天我们不说这个 Redis 的主从同步,我们来说说我们最常使用 Redis 的时候,会导致的一些问题。
说到这个一致性,了不起就得和大家说道说道了,为什么会出现这种情况呢?实际上就是和 Redis 的使用有很大的关系。
都知道,Redis 是一个 NoSQL 的数据库,而且他还很快,所以很多数据都会从 Mysql 中把数据取出来,然后放到我们的缓存中,然后下次读取数据的时候,从 Redis 中直接去读取,这个时候,我们就会出现问题了。
什么问题呢?
在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问MySQL等数据库。
这个业务场景,主要是解决读数据从Redis缓存,一般都是按照下图的流程来进行业务操作。
读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存Redis和数据库MySQL间的数据一致性问题。
不管是先写 MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。
举一个例子:
1.如果删除了缓存 Redis,还没有来得及写库 MySQL,另一个线程就来读取,发现缓存为空,则到数据库中读取数据,写入缓存,此时缓存中为脏数据。
2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。
因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。
其实解决方案也有不是少,今天了不起来给大家分析一下 Redis 和 Mysql 保证数据一致性的实现方案。
延时双删策略步骤如下:
其实我们可以来想一下,如果有三个线程,分别是 线程1 ,线程2,线程3,三个线程,
其中,线程1先删除缓存;
线程2读取缓存为null,同步db数据到缓存中;
线程1更新db中的数据;
线程3查询缓存中数据是旧数据;
这样的话,就会出现 Mysql 和 Redis 中的数据不一致,这时候采用延迟双删策略,去保证数据的一致性,
这时候就有人问了,为什么要休眠一段时间,然后再执行呢?
假象一下,如果没有第三步操作时,有很大概率,在两次删除Redis操作执行完毕之后,数据库的数据还没有更新,此时若有请求访问数据,便会出现我们一开始提到的数据不一致的问题。
为什么还要再次删除缓存呢?
如果我们没有第二次删除操作,此时有请求访问数据,有可能是访问的之前未做修改的 Redis 数据,删除操作执行后,Redis为空,有请求进来时,便会去访问数据库,此时数据库中的数据已是更新后的数据,保证了数据的一致性。
因为感觉这种延迟双删除可靠性并没有那么高,因为我们并不能保证删除 Redis 成功,也不能保证数据库更新也是成功的,也就是我们所说的原子性,两个组合起来只是在理想情况下。
比如双删失败我们应该怎么处理呢?
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致。
重试方案有两种实现,一种在业务层做,另外一种实现中间件负责处理。
然而,该方案有一个缺点,对业务线代码造成大量的侵入。
流程如下:
1.更新数据库数据;
2.缓存因为种种问题删除失败;
3.将消费消息,获得需要删除的key;
4.自己消费消息,获得需要删除的key;
5.重试删除操作,直到成功。
而这个放在业务层去处理的话,侵入太高,所以一般是不太推荐使用来解决这个问题。
但是呢,还有一个就是使用中间件来进行处理。
启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
流程如下:
1.更新数据库数据;
2.数据库会将操作信息写入binlog日志当中;
3.订阅程序提取出所需要的数据以及key;
4.另起一段非业务代码,获得该信息;
5.尝试删除缓存操作,发现删除失败;
6.将这些信息发送至消息队列;
7.重新从消息队列中获得该数据,重试操作。
关于延迟双删除策略,你学会了么?