实时刷新缓存-处理mysql主从延迟的一些设计方案

在项目开发当中,常常有这样一种场景,对数据库进行添加、修改、删除操做的应用直接链接master库,只对数据库进行查询的应用,会先创建一个中央缓存,例如redis或者memcache,若是缓存没有命中,那么直接访问slave库。下文会介绍一下在刷新中央缓存时,若是发生主从延迟,应该如何处理。也便是,当应用System-A 把数据库写入master库的时候,System-B应用在读取slave库的时候,master库的数据还没同步到slave库,若是这个时候刷新缓存的话,会直接把旧的数据刷到缓存里的。mysql

备注:redis

笔者在处理这个问题时,刷新中央缓存的机制是使用MQ消息进行通知的。本文也是基于MQ这种技术背景下,想到的一些解决方案。复制代码

本地缓存框架缓存数据

咱们能够根据数据的update_time 来判断master库的数据是否已经同步到slave库。假设有一个update数据库的操做,经过update_time得知,最新的master库的数据还未同步到slave库,那么咱们能够把这条数据的主键存储到本地缓存当中,例如使用LinkedBlockingQueue 这个队列做为本地缓存,将数据主键id存储到队列中,而后启动一个job去扫描这个队列,一旦发现队列中有数据,则进行处理。在处理数据的过程当中,若是发现该条数据仍是未从master同步过来,那么继续把这条数据的主键放入队列中,等待下一次的处理,一直到master库的数据同步过来为止。如若因为数据库缘由或者数据缘由或者代码问题等,致使数据一直处于入队列/出队列的死循环当中,那么咱们能够为数据设置一个出入队列的次数,例如5次,超过五次的,则该条数据把它丢失掉。spring

下面列出一些伪代码:sql

队列实现

public class DelayQueue {
    private static final Logger LOGGER = LoggerFactory.getLogger(DelayQueue.class);
    private static final int QUEUE_MAX_ELEMENT_COUNT = 20000;
    private  LinkedBlockingQueue<MessageElement> queue = new  LinkedBlockingQueue<MessageElement>(QUEUE_MAX_ELEMENT_COUNT);
    private static class SingletonHolder {
        private static final DelayQueue INSTANCE = new DelayQueue ();
    }

    private DelayQueue (){}

    public static final DelayQueue getInstance() {
        return SingletonHolder.INSTANCE;
    }


    /**
     *把元素插入队列,若是此时队列已满,则丢弃掉
     */
    public void offer(MessageElement messageElement){
            boolean result = queue.offer(messageElement);
            //队列满了
            if (!result) {
                LOGGER.warn(dataBase masterSlaveDataDelayQueue full);
            }
    }

    /**
     * 把头部的元素出栈
     */
    public MessageElement poll (){
        return queue.poll();
    }
}复制代码

扫描本地缓存队列的job

使用spring的定时任务注解:数据库

/**
     *该方法是单线程调度的,若是该线程未执行完,后续的调度将不会执行
     */

    @Scheduled(cron="0 0/5 * * * ?")
    public void handleQueueMessage(){
        while(true){
            String result = "true";//最好从配置文件读取,当值为false时,不接收消息
            if (Constants.FALSE.equals(result)) {
                return;
            }
            MessageElement messageElement = DelayQueue .getInstance().poll();
            if (messageElement == null) {
                break;
            }
            LOGGER.info("receiveMessage from delay queue"+messageElement.toString());
            salesService.handleMessage(messageElement);
        }
    }复制代码

消息体

public class MessageElement {

    private Long id;//数据主键
    private AtomicInteger count = new AtomicInteger();//控制出入队列的最大次数

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }

    public AtomicInteger getCount() {
        return count;
    }
    public void setCount(AtomicInteger count) {
        this.count = count;
    }
}复制代码

备注:缓存

  1. 注意要设置队列的最大容量,若是队列中的数据数量超过最大容量,能够根据本身的业务状况,删除队头或者再也不加入数据。bash

  2. 这个方案在应用重启的数据,本地缓存会被清理,形成数据丢失。并发

  3. 必须有一个开关,控制是否接收消息。由于一旦生产者发送的并发量太大,会引发其余问题,这个时候,能够经过开关控制不接收消息,以便达到降级的效果。毕竟咱们只是刷新缓存而已,大不了不刷。框架

    使用MQ

    若是MQ有以下的特性的话,也能够尝试使用:ui

当数据未从master同步过来时,能够把消息的状态设置为later,让消息发送者每隔一段时间再次发送,例如2s后、5s后1分钟后,这样不断的发送,直到一个小时后,中止发送。复制代码

这样的话,应用就无需使用本地缓存了,直接利用MQ。同时当应用重启的时候,消息也不会丢失。


原文连接


实时刷新缓存-处理mysql主从延迟的一些设计方案

相关文章
相关标签/搜索