redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致
0.拙劣的解决方案:延迟双删
延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。
2种场景对应2种解决方案:
1.若一致性要求高:强一致方案
//用于根据给定的id从Redis中获取一个Item对象。如果Redis中不存在该对象,则创建一个新的业务对象并将其保存到Redis中。
public Item getById(Integer id){//读数据
//获取一个读写锁对象readwriteLock
RReadWriteLock readwriteLock=redissonclient.getReadWriteLock("ITEM_READ_WRITE_LOCK");
//读之前需要先获取读锁对象readLock,读锁的作用就是等待该Lockkey释放写锁以后再进行读取操作
RLock readLock = readwriteLock.readLock();
try{
readLock.Lock();//获取读锁
Item item=(Item)redisTempLate.opsForValue().get("item:"+id); //从Redis中查询数据
if(item != null) return item;
}finally{
readLock.unlock(); //释放读锁
}
//查询业务数据
item=newItem(id,"华为于机","华为于机",5999.00); //创建新的业务对象并保存到Redis中
redisTempLate.opsForValue().set("item:"+id,item); //保存到Redis中
return item; //返回新创建的业务对象
}
//用于更新数据库中指定ID的商品信息
public void updateById(Integer id) {//写数据
RedissonClient redissonclient = Redisson.create();
//获取名为"ITEM-READ_WRITE_LOCK"的读写锁对象。
RReadWriteLock readWriteLock = redissonclient.getReadWriteLock("ITEM-READ_WRITE_LOCK");
// 获取写锁对象writelock
RLock writelock = readWriteLock.writeLock();
try {
// 获取写锁
writelock.lock();
System.out.println("writelock...");
// 更新业务数据
Item item = new Item(id, name: "华为手机", desc: "华为手机", price: 5299.00);
Thread.sleep(10000);//让当前线程休眠10秒钟,模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
// 删除缓存中的临时数据
redisTempLate.delete(key: "item:" + id);
} finally {
writelock.unlock();//释放写锁
}
}
就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。
我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。
那这个排他锁是如何保证读写、读读互斥的呢?
其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法
2.若允许短暂延迟一致:
就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,数据同步可以有一定的延时(符合大部分业务),所以我们采用的是异步的方案同步的数据。
我们当时采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。