数据一致性是值:
1)缓存中存有数据,并且该数据在缓存中的数据值=数据库中的数据值。
2)缓存中没有该数据,那么数据库中的值=最新值。
在我们把Redis作为缓存的时候,如果数据需要更改,我们就得经过双写来保证缓存与数据库的数据一致性。
如果要保证强一致性的话,势必要引入2PC等分布式一致性协议,或者引入分布式锁等技术,但是这肯定也会对性能有影响,这也违背了我们使用Redis缓存的目的 -- 提高性能。
所以我们现在谈的数据库与缓存的一致性问题,该一致性其实指的就是最终一致性。
在使用缓存时,现在业务系统最常用的缓存策略是 Cache-Aside 模式(旁路缓存)。下面简单说一下该模式下的读写缓存实践。
读缓存实践
1)先读缓存,命中则返回;未命中则读数据库,然后再将该数据写入缓存。
写缓存实践
1)先写数据库,再操作缓存。
2)直接删除缓存,而不是修改。
值得注意的是,如果缓存的更新成本很高(如涉及访问多张表联合计算),建议直接删除缓存,而不是更新。当然也推荐直接删除缓存。
在旁路缓存模式下,为了尽可能保证缓存与数据库的最终一致性,我们可以采用延迟双删策略。
1)先删除缓存
2)写数据
3)休眠查询一次数据库的时间,再删除缓存。
在进行第二步写数据的时候,在高并发场景下,很有可能有另一个线程此时在数据库中读取将要删除的数据,而我们第一步已经把缓存删掉了,所以该线程会将读取的脏数据写入缓存,以致于缓存中存入的是脏数据。
为了保证第二次删除缓存成功,我们可以采用重试机制,比如重试3次,如果3次都失败的话则记录日志到数据库并通知人工介入。如果在高并发场景下,重试最好采用异步方式,比如发送消息到MQ中间件,实现异步解藕。
但是引入中间件的话就会对业务代码造成侵入。于是可以采用下一个方案 -- 启动一个专门订阅数据库binlog的服务,用来读取需要删除的数据,然后进行缓存删除操作。
该方案步骤:
1)更新数据库
2)数据库会把操作信息记录在binlog日志中
3)使用canal订阅binlog日志来获取目标数据
4)缓存删除系统获取canal里的数据,解析目标数据,尝试删除缓存
5)如果缓存删除系统尝试删除缓存失败,则将消息发送到消息队列
6)缓存删除系统重新从消息队列获取数据,再次执行删除操作。
为了尽可能保证数据库与缓存的一致性,我们可以采用延迟双删策略,同时采用异步重试机制来防止第二次删除缓存失败。异步机制我们可以利用MQ消息中间件,或者利用canal订阅数据库binlog日志来监听写请求,然后删除对应数据缓存。
我们没有办法做到强一致性,因为这是由CAP理论决定的,缓存系统适用的场景就是非强一致性的场景,所以它属于CAP中的AP。但是我们可以做到最终一致性。
CAP:C是Consistency(强一致性),A是Availability(可用性),P是Partition tolerance(分区容错性)。在分布式系统中,该系统要么满足AP,要么满足CP,而满足CP的系统的性能一般不是很高。