Redis和数据库双写一致性问题,使用多线程和队列方案解决

本文介绍了在面临Redis与数据库双写一致性问题时,如何利用多线程和队列策略来解决。在并发情况下,由于更新顺序导致数据不一致,通过创建多个队列并绑定线程池,结合商品ID哈希取余策略,确保同一商品操作进入同一队列,由单独线程消费,从而避免顺序问题,提高系统性能。
摘要由CSDN通过智能技术生成

前言

前段时间找工作,遇到两次面试官都谈到数据库l和redis双写一致性问题,其中一个面试官不将码德,问了我用过redis没就直接旁敲侧击的问:在用redis时,有没有遇到过它和数据库相关麻烦和问题啊?刚好这两天新工作还没全面开展,就抽空详细写写数据库和redis双写一致性问题。

问题现象

在之前的一个项目当中,我们在测试的时候,批量更新了某些数据的数据后,更新之后发现极个别数据并没有更新!当时就再次测试,发现这个问题并不是一定出现的。而且数据库的数据是更新了的,而页面查出来的数据是老的数据,然后就发现是redis的数据没有更新。

问题原因

排查和测试了很久之后,分析发现:在更新的数据和查询数据的极端的并发情况下,会出现数据不一致问题。主要是redis和数据库双写执行的顺序导致的一致性问题。

具体情况:先有个查询线程进来从数据库查到了老的数据,由于此时redis缓存还没有查出来的数据,数据会再写入redis。但是在写入redis之前,此时并发执行了更新操作,新数据更新了mysql和redis后,查询的线程才把老的数据写入redis,导致后面的查询线程直接从redis拿出来的数据是老的数据。这个情况的问题本质是读后写问题。

问题解决思路

网上看了很多类似问题,也有很多解决方案,我觉得还比较好的解决方案是写后延时双删的方案,还有依靠消息队列的方案,但是在我这个项目中,更新本就消费方操作,在再加消息队列也不合适。
最终采用的是方案是使用多线程加队列的方案解决。

如何使用队列解决问题

问题根本原因就是redis和数据库写入的顺序问题,将更新操作或者查询操作压入队列就可以解决两个操作的顺序问题。
但是这样又有新的问题:在高并发下,所有不同的商品的更新查询数据操作都压入队列中,势必会急剧降低系统性能。所以直接将操作放入队列的操作是不可行的。
这个问题就可以引入多线程来解决:
在更新服务里面创建多个队列的集合,跟线程池的线程一一绑定,然后根据更新的id进行hash,然后对队列进行取余,这样就能够把同一个商品的操作装入到同一个队列中,然后由单独的一个线程对队列依次的进行消费。

具体实现方案及代码

创建两个关键类:ThreadPool和ProcessThread。
在ThreadPool中使用 Executors 工厂方法来创建壹個 ExecutorService 实例。创建队列的集合。

private ExecutorService executorService= Executors.newFixedThreadPool(10);
private ArrayList<ArrayBlockingQueue> queueArrayList=new ArrayList<>();

通过饿汉式的单例模式创建线程安全的线程池对象,在线程池对象中再通过一个public的init方法返回 SingleTone.getThreadPool();

private static class SingleTone {
   
        private static  ThreadPool threadPool;
        static {
   
            threadPool=new ThreadPool();
        }
         private static ThreadPool getThreadPool(){
   
            return threadPool;
        }
    }
    public static  ThreadPool init(){
   
        return SingleTone.getThreadPool();
    }

在new 线程池对象的时候就通过构造方法初始化了每个线程绑定的队列。通过submit的方式将线程提交至线程池。

private ThreadPool(){
   
        for (int i = 0; i <10 ; i++) {
   
            ArrayBlockingQueue queue=new ArrayBlockingQueue(500);
            ProcessThread thread=new ProcessThread(queue);
            
            queueArrayList.add(queue);
            executorService.submit(thread);
        }
    }

创建ProcessThread实现Callable接口,在构造方法中,添加队列对象

public class ProcessThread implements Callable<TProduct> {
   
	private ArrayBlockingQueue queue=null;

    public ProcessThread(ArrayBlockingQueue queue) {
   
        this.queue = queue;
    }

重写的call方法中执行业务的更新或者查询操作;

拿出队列中的数据传输对象,在操作完成之后使数据传输对象的标记更改data.setDone(true);

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值