Redis如何实现双写一致

双写一致

当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。

普通实现

1ca87f62e13b4de2922d8a571faa1101.png

读操作:缓存命中,返回数据;缓存未命中查询数据库,写入缓存,返回数据。

写操作:延迟双删。

ffcace6b373c4bcd9184b445ed450768.png

先删除缓存,还是先修改数据库?? 都会有问题的

先删除缓存正常逻辑演示

首先数据库和缓存中数据都为10
当线程1进来时,删除缓存后,将数据库更新为了20 ,此时缓存为空,数据库为20 ;线程2进来时,查询缓存未命中,然后查询数据库 20,将20写入缓存。此过程没有任何问题,如下图:
7dc3dd33abf54e3b9020756efb8e0e2d.gif

先删除缓存产生错误逻辑演示

首先数据库和缓存中数据都为10
当线程1进来时先删除了缓存,此时由于线程为交替执行,线程2进来查询缓存未命中,查询数据库数据 10,写入了缓存,此时缓存中数据为10,数据库中数据为20,产生了脏数据问题。
3482a157903441048591e8302853cdf5.gif

先更新数据库正常逻辑演示

当线程2更新数据库数据为20 ,删除缓存,此时线程1进来查询缓存未命中,查询数据库 20,写入缓存。
58d5cd62203b4556901908afcf22e53b.gif

先更新数据库产生错误逻辑演示

首先缓存中的数据被删除或者过期,当线程1进来查询缓存未命中,查询数据库数据 10,由于线程交替,此时线程2进来更新数据库 20,并删除缓存(删不删无所谓,已经是空了),此时线程再次交替到线程1,更新缓存为10,也产生了脏数据问题。
1b71504c61dd48d68d6d2eadc8c32189.gif

为什么要删除两次缓存?

延迟再一次删除缓存就是为了降低脏数据的产生,为什么延迟删除?以为数据库可能为主从分离的,让主节点将数据同步到从节点。但是延时只是降低风险,并不能完全避免脏数据。

使用互斥锁保证数据强一致性

由于放入缓存数据一般为读多写少数据,所以可以采用读写锁的方式,读数据时添加共享锁,读读不互斥,写互斥;写数据时添加排他锁,读写操作都互斥。
e5ad7be8c33b4cb3842b3ff42c49e555.png

读写锁--读数据代码示例

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();
        }
 }

读写锁--更新数据代码示例

 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监听到消息后,执行写入缓存的操作,最终保证了数据的一致性。
92a1b04fbfd441929687527731b1a9f6.png

Canal中间件异步通知

Canal是基于mysql的主从同步来实现的,当有数据修改后,写到数据库中,数据库一旦发生变化,就会把变化记录到binlog日志文件中,例如DDL语句,DML语句。Canal通过监听mysql的binlog日志文件,缓存服务获取变化的数据,更新到缓存中。对于业务代码几乎零侵入。
0da83a4806c54b06b17303bdd28c9495.png

目录

Total Likes
2
Total Comments
0