Redis如何实现双写一致
双写一致
当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
普通实现
读操作:缓存命中,返回数据;缓存未命中查询数据库,写入缓存,返回数据。
写操作:延迟双删。
先删除缓存,还是先修改数据库?? 都会有问题的
先删除缓存正常逻辑演示
首先数据库和缓存中数据都为10
当线程1进来时,删除缓存后,将数据库更新为了20 ,此时缓存为空,数据库为20 ;线程2进来时,查询缓存未命中,然后查询数据库 20,将20写入缓存。此过程没有任何问题,如下图:
先删除缓存产生错误逻辑演示
首先数据库和缓存中数据都为10
当线程1进来时先删除了缓存,此时由于线程为交替执行,线程2进来查询缓存未命中,查询数据库数据 10,写入了缓存,此时缓存中数据为10,数据库中数据为20,产生了脏数据问题。
先更新数据库正常逻辑演示
当线程2更新数据库数据为20 ,删除缓存,此时线程1进来查询缓存未命中,查询数据库 20,写入缓存。
先更新数据库产生错误逻辑演示
首先缓存中的数据被删除或者过期,当线程1进来查询缓存未命中,查询数据库数据 10,由于线程交替,此时线程2进来更新数据库 20,并删除缓存(删不删无所谓,已经是空了),此时线程再次交替到线程1,更新缓存为10,也产生了脏数据问题。
为什么要删除两次缓存?
延迟再一次删除缓存就是为了降低脏数据的产生,为什么延迟删除?以为数据库可能为主从分离的,让主节点将数据同步到从节点。但是延时只是降低风险,并不能完全避免脏数据。
使用互斥锁保证数据强一致性
由于放入缓存数据一般为读多写少数据,所以可以采用读写锁的方式,读数据时添加共享锁,读读不互斥,写互斥;写数据时添加排他锁,读写操作都互斥。
读写锁--读数据代码示例
java
public Item getById(Integer id) {
RReadWriteLock readWriteLock = redissonClient.getReadWritelock("ITEM_READ_WRITE_LOCK");
//读之前加读锁,读锁的作用就是等待该Lockkey释放写锁以后再读
RLock readLock = readWriteLock.readLock();
try {
//开锁
readLock.lock();
System.out.printin("readLock...");
Item item = (Item) redisTemplate.opsForValue().get("item:" + id);
if (item != null) {
return item;
}
//查询业务数据
item = new Item(id, "华为手机", "华为手机", 5999.00);
//写入缓存
redisTemplate.opsForValue().set("tem:" + id, item);//返回数据
return item;
} finally {
readLock.unlock();
}
}
读写锁--更新数据代码示例
java
public Item updateById(Integer id) {
RReadWriteLock readWriteLock = redissonClient.getReadWritelock("ITEM_READ_WRITE_LOCK");
//读之前加读锁,读锁的作用就是等待该Lockkey释放写锁以后再读
RLock writeLock = readWriteLock.writeLock();
try {
//开锁
writeLock.lock();
System.out.printin("writeLock...");
//更新业务数据
Item item = new Item(id, "华为手机", "华为手机", 5299.00);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//写入缓存
redisTemplate.delete("tem:" + id, item);//返回数据
return item;
} finally {
readLock.unlock();
}
}
采用读写锁方式:可以保证强一致性,但性能会降低一些
异步通知保证数据的最终一致性
以下两种方式可以都可以保证一致性,在数据量,请求不大情况下,几乎感受不到延迟。
MQ中间件异步通知
此方式可以保证数据的最终一致性,依靠于mq的可靠性。
修改数据后,写入数据库,发布消息到mq,mq监听到消息后,执行写入缓存的操作,最终保证了数据的一致性。
Canal中间件异步通知
Canal是基于mysql的主从同步来实现的,当有数据修改后,写到数据库中,数据库一旦发生变化,就会把变化记录到binlog日志文件中,例如DDL语句,DML语句。Canal通过监听mysql的binlog日志文件,缓存服务获取变化的数据,更新到缓存中。对于业务代码几乎零侵入。