问题:redis 作为缓存,mysql 的数据如何与 redis 进行同步呢?(双写一致性)
双写一致性是指当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
- 读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间。
- 写操作:延迟双删
什么是延迟双删?
- 先删除缓存,还是先修改数据库?
无论怎么操作都会存在数据不一致的问题。
- 为什么要删除缓存呢?
先删除缓存再删除数据库肯定是存在脏数据的,所以要删除两次缓存。
- 为什么要延时双删?
因为数据库是主从分离的,主从同步需要时间,所以需要延时删除。但是因为延时的时间不好控制,所以延时的过程中也可能出现脏数据。
到底怎么才能保证数据的强一致性呢?
通过加锁的方式,但是性能比较低。
如何优化呢?
首先,存入缓存的数据一般都是读多写少,所以我们可以用读写锁控制。
共享锁:读锁 readLock,加锁之后,其他线程可以共享读操作。
排他锁:独占锁 writeLock 也叫,加锁之后,阻塞其他线程读写操作。
所以我们在读操作的时候可以加共享锁,其他线程就可以读,但是不可以写。
public item getById(Integer id){RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ITEM_READ_WRITE_LOCK");// 读之前加读锁,读锁的作用就是等待该lockkey释放写锁以后再读RLock readLock = readWriteLock.readLock();try{// 开锁readLock.lock();System.out.println("readLock");Item item =(Item) redisTemplate.opsForValue().get("item:"+id);if(item != null){return item;}// 查询业务数据item = new Item(id,"华为手机","华为手机",5299.00);// 写入缓存redisTemplate.opsForValue().set("item:"+id,item);// 返回数据return item;}finally{readLock.unlock();}
}
在写操作的时候,加排他锁,其他线程都不能读写操作。
public void updateById(Integer id){RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ITEM_READ_WRITE_LOCK");// 写之前加写锁,写锁加锁成功,读锁只能等待RLock writeLock = readWriteLock.writeLock();try{// 开锁writeLock.lock();System.out.println("writeLock");// 更新业务数据Item item = new Item(id,"华为手机","华为手机",5322.00);try{Thread.sleep(10000);}catch(InterruptedEXception e){e.printStackTrace();}// 删除缓存redisTemplate.delete("item:"+id);}finally{writeLock.unlock();}
}
使用读写锁肯定可以保证数据的强一致性,但是性能肯定低。所以需要保证数据强一致性的业务场景才会使用。
允许短暂的不一致性
- 异步通知保证数据的最终一致性
- 基于 Canal 的异步通知