1-1:写在前面

这篇文章主要是对这方面的知识点的总结,因为我自己把用户的session丢进了redis里面,就遇到了redis和db同时更新的问题,而且管理系统也没什么并发吧,就做着玩,想着如果有并发该怎么做?自己搜了不少资料,决定总结一下相关的方案,但是并未正式用到项目中,因为我只是好奇怎么解决的,生产中并未遇到,因为我还是学生。。另外,既然缓存了,还追求强一致性这是不太可取的,一般都是追求最终一致性,对于一致性要求较高的,容不得半点错的,老实读库吧。

1-2:什么样的数据适合存redis?

redis可以用来做缓存,没有问题,但是要记住频繁修改的,非常重要的数据不适合放缓存里面去。可以看下这篇:哪些数据可以放进缓存?记录生产环境一次缓存评估的过程

1-3:Redis缓存中的旧数据怎么处理?选择更新还是删除?

看到的资料大部分都在讨论两种,一个是更新缓存,第二种就是干脆删掉缓存,等有请求读缓存的时候,发现缓存里面没有去数据库重新去拿。那么,到底是选择更新呢?还是删掉呢?

更新:
更新的话,优点是缓存不会丢掉,命中缓存的概率会比较高,但是缺点就是会造成数据不一致的问题
举个例子:()代表时间顺序
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这个时候数据库中是B的数据,但是缓存中是A的数据,而且缓存中的数据不频繁更新的话,那么在一段时间内,从redis中拿到的数据就都是脏数据。

删除:
对数据进行删除操作好在哪里呢?删除数据,在更新数据的时候,就会直接把缓存中的数据删掉,不存在上面的情况,但是也会有短暂的数据不一致问题,这种情况什么时候会出现问题呢?下面会讲到。另外这种方法有一个缺点,就是会降低缓存的命中率,删除了缓存就势必要从数据库里面重新加入到缓存里面。感觉删除的操作有点像懒加载一样,等用到它的时候再去DB里面读新的。

ps:有的人说,频繁更新的话会降低redis的性能,但是吧,既然作为缓存了,你再去频繁更新数据就只能说,这个数据就不适合丢redis里面,redis里面不适合对频繁更新的数据做缓存。

1-4:先更新DB后删Redis/先删Redis后更新DB

在不出现失败的情况下:先删Redis后更新DB:

Redis一致性协议map redis db 一致性_缓存


先删除了缓存,但是B还没来得及获得到新数据,就把db中的老数据放进了缓存中,那么如果在未来时间内,不更新这个缓存的话,就一直读的脏数据。

在不出现失败的情况下:先删Redis后更新DB

Redis一致性协议map redis db 一致性_数据_02


这种情况下,B会读到旧数据,但是只是短暂的,因为A随后就会删掉缓存中的数据,之后的其他线程读到的就都是新数据了。

我还看见的另外一种其他情况:
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大!

如果出现失败的情况下,我感觉失败的情况有点多,在哪一个位置都会出现失败吧,毕竟redis和数据库都是服务,是服务就一定会有阻塞或者宕机超时的时候。那么如果有这种情况,就需要重试机制(比如删除操作出现异常丢进消息队列中,然后慢慢二次删除),重试机制下怎么保证一致性呢?我这个菜鸡没遇到过,可能具体问题具体分析吧。

所以呢,目前看来可能先更新DB再删除Redis要好一点,并且看博客里面说:知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。可能这就是好一点的处理手段吧。但是具体的,到底谁更好呢?还是具体问题具体分析。而且redis里面一般都有过期时间兜底,问题不会特别大吧。

1-5:关于延迟双删策略

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。具体步骤是:
1)先删除缓存
2)再写数据库
3)休眠500毫秒(根据具体的业务时间来定)
4)再次删除缓存。
很明显延迟双删的最大坏处就在这个休眠时间上,所以对于响应要求比较高的接口来说是致命的,并且这个策略是针对先删redis缓存,再删数据库来做的。但是我感觉对业务代码的侵入会比较多,好不好,现在不知道,只能说做个记录。

1-6:Canal 采集binlog实现同步

大概流程如下:
(1)更新数据库数据;
(2)数据库会将操作信息写入binlog日志当中;
(3)订阅程序提取出所需要的数据以及key;
(4)另起一段非业务代码,获得该信息;
(5)尝试删除缓存操作,发现删除失败;
(6)将这些信息发送至消息队列;
(7)重新从消息队列中获得该数据,重试操作。

整理就是起消息队列去订阅binlog,然后去删除key,这也是一种解决方案,应该是看到的没有太多问题的。

consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
	System.out.println(Thread.currentThread().getName() + " Receive New Messages: " + msgs);
	delcache(key);//执行真正删除
	return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//返回消费成功
 }
});

1-7:参考

参考链接由于链接丢失了,没法给了,内容是复制的,里面心得是自己写的,感谢那些出力的人。