实时刷新缓存-处理mysql主从延迟的一些设计方案
2016-11-18 18:47
405 查看
在项目开发当中,经常有这样一种场景,对数据库进行添加、修改、删除操作的应用直接连接master库,只对数据库进行查询的应用,会先建立一个中央缓存,例如redis或者memcache,如果缓存没有命中,那么直接访问slave库。下文会介绍一下在刷新中央缓存时,如果发生主从延迟,应该如何处理。也即是,当应用System-A 把数据库写入master库的时候,System-B应用在读取slave库的时候,master库的数据还没同步到slave库,如果这个时候刷新缓存的话,会直接把旧的数据刷到缓存里的。
备注:
我们可以根据数据的
下面列出一些伪代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
使用spring的定时任务注解:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
备注:
注意要设置队列的最大容量,如果队列中的数据数量超过最大容量,可以根据自己的业务情况,删除队头或者不再加入数据。
这个方案在应用重启的数据,本地缓存会被清理,造成数据丢失。
必须有一个开关,控制是否接收消息。因为一旦生产者发送的并发量太大,会引起其他问题,这个时候,可以通过开关控制不接收消息,以便达到降级的效果。毕竟我们只是刷新缓存而已,大不了不刷。
如果MQ有如下的特性的话,也可以尝试使用:
这样的话,应用就无需使用本地缓存了,直接利用MQ。同时当应用重启的时候,消息也不会丢失。
备注:
笔者在处理这个问题时,刷新中央缓存的机制是使用MQ消息进行通知的。本文也是基于MQ这种技术背景下,想到的一些解决方案。1
本地缓存框架缓存数据
我们可以根据数据的update_time来判断master库的数据是否已经同步到slave库。假设有一个update数据库的操作,通过update_time得知,最新的master库的数据还未同步到slave库,那么我们可以把这条数据的主键存储到本地缓存当中,例如使用
LinkedBlockingQueue这个队列作为本地缓存,将数据主键id存储到队列中,然后启动一个job去扫描这个队列,一旦发现队列中有数据,则进行处理。在处理数据的过程中,如果发现该条数据还是未从master同步过来,那么继续把这条数据的主键放入队列中,等待下一次的处理,一直到master库的数据同步过来为止。如若由于数据库原因或者数据原因或者代码问题等,导致数据一直处于入队列/出队列的死循环当中,那么我们可以为数据设置一个出入队列的次数,例如5次,超过五次的,则该条数据把它丢失掉。
下面列出一些伪代码:
队列实现
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(); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
扫描本地缓存队列的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); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
消息体
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
备注:
注意要设置队列的最大容量,如果队列中的数据数量超过最大容量,可以根据自己的业务情况,删除队头或者不再加入数据。
这个方案在应用重启的数据,本地缓存会被清理,造成数据丢失。
必须有一个开关,控制是否接收消息。因为一旦生产者发送的并发量太大,会引起其他问题,这个时候,可以通过开关控制不接收消息,以便达到降级的效果。毕竟我们只是刷新缓存而已,大不了不刷。
使用MQ
如果MQ有如下的特性的话,也可以尝试使用:当数据未从master同步过来时,可以把消息的状态设置为later,让消息发送者每隔一段时间再次发送,例如2s后、5s后1分钟后,这样不断的发送,直到一个小时后,停止发送。1
这样的话,应用就无需使用本地缓存了,直接利用MQ。同时当应用重启的时候,消息也不会丢失。
相关文章推荐
- 实时刷新缓存-处理mysql主从延迟的一些设计方案
- 实时刷新缓存-处理mysql主从延迟的一些设计方案
- mysql主从同步延迟方案解决的学习心得
- MYSQL异常处理日志:主从库同步延迟时间过长的分析
- MYSQL异常处理日志:主从库同步延迟时间过长的分析
- 架构设计:系统存储(11)——MySQL主从方案业务连接透明化(上)
- 架构设计:系统存储(10)——MySQL简单主从方案及暴露的问题
- MySQL 减少主从数据同步延迟的几个方案
- 架构设计:系统存储(12)——MySQL主从方案业务连接透明化(中)
- MYSQL异常处理日志:主从库同步延迟时间过长的分析
- MYSQL异常处理日志:主从库同步延迟时间过长的分析
- 架构设计:系统存储(10)——MySQL简单主从方案及暴露的问题
- 架构设计:系统存储(11)——MySQL主从方案业务连接透明化(上)
- 前端通信:SSE设计方案(二)--- 服务器推送技术的实践以及一些应用场景的demo(包括在线及时聊天系统以及线上缓存更新,代码热修复案例)
- 架构设计:系统存储(12)——MySQL主从方案业务连接透明化(中)
- Fmod studio 手机上延迟的一些处理方案
- MySQL主从复制的一些错误处理和日常维护
- MySQL主从复制的一些错误处理和日常维护
- mysql主从同步延迟方案解决的学习心得
- MYSQL异常处理日志:主从库同步延迟时间过长的分析