21、如何解决Redis的并发竞争Key问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 Redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问 题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的 与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有 序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁 无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
22、如何保证缓存与数据库双写时的数据一致性
首先说一句,你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如 何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的 情况,最好不要做这个方案,最好将读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
最经典的缓存+数据库读写的模式,就是 预留缓存模式Cache Aside Pattern。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先删除缓存,然后再更新数据库,这样读的时候就会发现缓存中没有数据而直接去数据库中拿数据了。(因为要删除,狗日的编辑器可能会背着你做一些优化,要彻底将缓存中的数据进行删除才行)
互联网公司非常喜欢问这道面试题因为缓存在互联网公司使用非常频繁
在高并发的业务场景下,数据库的性能瓶颈往往都是用户并发访问过大。所以,一般都使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接去访问MySQL等数据库,从而减少网络请求的延迟响应。
23、数据为什么会出现不一致的情况?
这样的问题主要是在并发读写访问的时候,缓存和数据相互交叉执行。
一、单库情况下
同一时刻发生了并发读写请求,例如为A(写) B (读)2个请求
A请求发送一个写操作到服务端,第一步会淘汰cache,然后因为各种原因卡主了,不在执行后面业务(例:大量的业务操作、调用其他服务处理消耗了1s)。
B请求发送一个读操作,读cache,因为cache淘汰,所以为空
B请求继续读DB,读出一个脏数据,并写入cache
A请求终于执行完全,在写入数据到DB
总结:因最后才把写操作数据入DB,并没同步。cache里面一直保持脏数据
脏数据是指源系统中的数据不在给定的范围内或对于实际业务毫无意义,或是数据格式非法,以及在源系统中存在不规范的编码和含糊的业务逻辑。
二、主从同步,读写分离的情况下,读从库而产生脏数据
A请求发送一个写操作到服务端,第一步会淘汰cache
A请求写主数据库,写了最新的数据。
B请求发送一个读操作,读cache,因为cache淘汰,所以为空
B请求继续读DB,读的是从库,此时主从同步还没同步成功。读出脏数据,然后脏数据入cache
最后数据库主从同步完成
总结:这种情况下请求A和请求B操作时序没问题,是主从同步的时延问题(假设1s),导致读请求读取从库读到脏数据导致的数据不一致
根本原因:
单库下,逻辑处理中消耗1s,可能读到旧数据入缓存
主从+读写分离,在1s的主从同步时延中,到从库的旧数据入缓存