缓存用得好,数据错不了。缓存延迟高,早晚得挨刀。
正常企业中使用redis都是当做缓存来用,数据来源一般都是数据库,那数据库的数据和缓存的数据,如何来实现强一致呢?我们来分析下:
redis同步策略
在redis中,缓存有个如下更新策略:
内存淘汰
不用自己维护,利用redis的内存淘汰机制,当内存不足时,自动淘汰部分数据。下次查询的时候再更新缓存。会有不同的配置策略。
这种在一定程度上也保证了一致性,但是不是我们可以控制的。因为我们无法知道哪些数据要淘汰了。
但是没有任何维护成本
超时剔除
我们在给key赋值的时候,顺便赋值一个超时时间,TTL时间,到期后redis会自动删除缓存,然后下次查询时候更新缓存,也在一定程度上保证了一致性。但是他的强弱是根据TTL时间来的,如果还在TTL时间内更新数据了,还是数据不一致。
也没有什么维护成本。
主动更新
我们在编写业务逻辑的时候,在修改数据库同时,也更新缓存,这种肯定在一定程度上做到强一致。
维护成本,是比较高的。
大部分的情况,我们都是采用主动更新。所以我们本文,也着重讲解。
那我们在实际开发中,如何选择呢?
这就需要看业务场景了:
- 低一致性需求:使用内存淘汰机制,比如我们商场系统中,商品的类型,这个商品类型为衣服,正常情况下是不会变成裤子的。此时,可以选择内存淘汰机制,往里面存就好了。是在不放心,价格超时时间。
- 高一致性需求:主动更新,并且超时剔除作为兜底方案。比如用户的订单信息,商品的订单信息等等。
主动更新策略
主动更新,一般又分为:
- 缓存的调用者,在更新数据库的同是更新缓存
- 缓存与数据库整合一个服务,由服务来维护一致性
- 其他异步线程来更新缓存
db和缓存同时更新
那又有以下问题考虑:
删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作比较多
- 删除缓存:更新数据库时,删除缓存让缓存失效,等下次查询时再更新缓存。
所以我们一般情况下,都选择删除缓存
如何保证缓存和数据库的操作同时成功和失败?
必须要保证这两个操作的原子性,不然肯定出现不一致现象。所以根据不同的部署形式来针对不同的方案。
单体系统
单体系统那简单,我们将操作数据库和缓存放入事务中,就保证同时成功和同时失败了。
分布式系统
在分布式架构中,我们的数据库和缓存可能都是不同的服务,那我们就得用到类似于TCC等分布式事务方案了。 至于TCC,不在本文讨论范围之类,可移步分布式事务 TCC
先更新缓存还是先操作数据库?
这里涉及到线程安全的问题,这里得具体看业务的容忍度了,两种皆可以的。
先删除缓存,再操作数据库。
上来就删缓存,然后再操作数据库。至于什么时候更新缓存,得到下一次查询的时候,查询不到了会自动从数据库中读取数据更新缓存。
但是,在多线程非安全的情况下,比如线程1删除缓存,去操作数据库了,但是在还未更新完数据库的时候,线程2来了,查询缓存,没有命中,直接把老数据写入到缓存中。此时线程1更新完数据库了。 那么就会带来缓存和数据库不一致。
这种其实概率还挺高的,因为删很快,但是存db很慢,很容易乘虚而入。
先操作数据库,再删除缓存。
先不管缓存,自己干好业务逻辑后,再删除缓存,下次查询的时候,再更新缓存。
但是,在多线程非安全的情况下,比如线程1查询缓存,此时由于一些原因缓存失效了,就从库中读取数据比如age=20,还未写入缓存。此时线程2来了,他先操作db中的age=30,然后再删除缓存。好,线程1此时写缓存了,把age=20写进去了。
但这种发生的概率较低,因为线程1查缓存再写缓存,一般情况下都是很快的,快到线程2在删除缓存之前就把缓存更新了。
所以,在不都不加锁的情况下,我们尽量采用 先操作数据库,再删除缓存
至于锁,在另外一篇文章中,还在构思...
可以看看这篇:redis-常见问题解决方案 ,里面有关于锁的操作。