Java高级开发高频面试题(六)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: Java高级开发高频面试题

🍊 Redis持久化

面试题:Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?save与bgsave?

持久化主要是做灾难恢复、数据恢复,高可用。比如你 redis 整个挂了,然后 redis 就不可用了,我们要做的事情就是让 redis 变得可用,尽快变得可用。 重启 redis,尽快让它堆外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。把 redis 持久化做好, 那么即使 redis 故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。

redis持久化有三种方式:RDB,AOF,(RDB和AOF)混合持久化

默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中,也就是RDB快照。

RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。

AOF 持久化机制,是对每条写入命令作为日志,重启的时候,可以通过回放日志中的写入指令来重新构建整个数据集。

不同的持久化机制都有什么优缺点?

🎉 RDB持久化

RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据。 redis 主进程只需要 fork一个子进程,让子进程执行磁盘 IO 操作来进行 RDB持久化,对外提供的读写服务,影响非常小。但是如果数据文件特别大,可能会导致对客户端提供的服务暂停数秒。 RDB 数据文件来重启和恢复 redis 进程更快 RDB会丢失某一时间段的数据,一般来说,RDB 数据快照文件,都是每隔 5分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。

🎉 AOF持久化

AOF 可以更好的保护数据不丢失,一般 AOF 每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。 AOF日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能很高,而且文件不容易破损。 AOF 日志文件即使过大的时候,可以进行后台重写操作,也不会影响客户端的读写。在重写的时候,会进行压缩,创建出一份最小恢复数据的日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。新日志文件创建完成以后,再去读的时候,交换新老日志文件就可以了。某人不小心用 flushall 命令清空了所有数据,只要这个时候后台重写命令还没有发生,那么就可以立即拷贝 AOF 文件,将最后一 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。 AOF 日志文件通常比 RDB数据快照文件更大。 支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync一次日志文件,当然,每秒一次 fsync,性能也还是很高的。

🎉 混合持久化

仅仅使用 RDB,会导致丢失很多数据 仅仅使用 AOF,速度慢,支持的QPS低,性能不高 开启开启两种持久化方式,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

🎉 持久化底层实现原理

持久化机制具体底层是如何实现的?

📝 RDB持久化底层实现原理

RDB持久化可以通过配置与手动执行命令生成RDB文件。 可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M个改动”这一条件被满足时, 自动保存一次数据集。比如说设置让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”,自动保存一次数据集。通过 save 60 1000 命令生成RDB快照,关闭RDB只需要将所有的save保存策略注释掉即可。手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。

📝 AOF持久化底层实现原理

AOF持久化可以通过配置与手动执行命令生成RDB文件。 通过配置# appendonly yes 开启AOF持久化, 每当 Redis 执行一个改变数据集的命令时, 这个命令就会被追加到 AOF 文件的末尾,当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的,配置 Redis 多久才将数据 fsync 到磁盘一次,默认的措施为每秒 fsync 一次。AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据重新生成aof文件,可以通过配置文件达到64M才会自动重写,也可以配置aof文件自上一次重写后文件大小增长了100%则再次触发重写 手动执行命令bgrewriteaof重写AOF,AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响。

📝 混合持久化底层实现原理

通过配置# aof-use-rdb-preamble yes 开启混合持久化,开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。

📝 save与bgsave

bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。 save 它是同步阻塞的,会阻塞客户端命令和redis其它命令,和bgsave相比不会消耗额外内存。

🍊 Redis过期策略

Redis采用的过期策略

惰性删除+定期删除

🎉 惰性删除流程

在进行get或setnx等操作时,先检查key是否过期,若过期,删除key,然后执行相应操作;若没过期,直接执行相应操作

🎉 定期删除流程

对指定个数个库的每一个库随机删除小于等于指定个数个过期key,遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16),检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述),如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历,随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key,判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。

问题:定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了,怎么解决呢?走内存淘汰机制。

🎉 内存淘汰机制

Redis 内存淘汰机制有以下几个:

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。

allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

默认就是如果满的话就拒绝抛异常,正常一般用LFU和LRU二种。LFU是基于梯形数组,每个数组上面就挂了一个Counter,Counter是用来统计它的服务次数的,通过访问次数来进行升级,LFU的LRU字段里面高16位存储一个分钟数级别的时间戳,低8位存储的是一个Counter访问计数。和LRU相比,LFU避免了LRU基于最近一段时间的访问没有访问数据,突然访问变成热点数据,导致内存淘汰,没有真正意义上达到冷数据的淘汰。

🎉 RDB对过期key的处理

过期key对RDB没有任何影响,从内存数据库持久化数据到RDB文件:持久化key之前,会检查是否过期,过期的key不进入RDB文件 从RDB文件恢复数据到内存数据库:数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)

🎉 AOF对过期key的处理

过期key对AOF没有任何影响 从内存数据库持久化数据到AOF文件:当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉) AOF重写:重写时,会先判断key是否过期,已过期的key不会重写到aof文件。

🍊 Redis与数据库的数据一致性

关于redis与数据库的数据一致性,业界使用最多的是数据同步问题(双删策略)

🎉 双删策略

先更新数据库,再更新缓存;

同时有请求A和请求B进行更新操作,那么会出现:

  1. 线程A更新了数据库;
  2. 线程B更新了数据库;
  3. 线程B更新了缓存;
  4. 线程A更新了缓存;

缺点

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑!

如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

先删除缓存,再更新数据库;

同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存;

(2)请求B查询发现缓存不存在;

(3)请求B去数据库查询得到旧值;

(4)请求B将旧值写入缓存;

(5)请求A将新值写入数据库;

导致数据不一致的情形出现,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

🎉 延时双删策略

解决方案:延时双删策略

(1)先淘汰缓存;

(2)再写数据库(这两步和原来一样);

(3)休眠1秒,再次淘汰缓存;

这么做,可以将1秒内所造成的缓存脏数据,再次删除!这个一秒如何得出来的呢?评估自己的项目的读数据业务逻辑的耗时,在读数据业务逻辑的耗时基础上,加几百ms即可,确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

MySQL的读写分离架构中

一个请求A进行更新操作,另一个请求B进行查询操作。

(1)请求A进行写操作,删除缓存;

(2)请求A将数据写入数据库了;

(3)请求B查询缓存发现,缓存没有值;

(4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值;

(5)请求B将旧值写入缓存;

(6)数据库完成主从同步,从库变为新值; 导致数据不一致,解决方案使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。

采用这种同步淘汰策略,吞吐量降低怎么办? ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。

🎉 异步延时删除策略

先更新数据库,再删除缓存; 一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:

(1)缓存刚好失效;

(2)请求A查询数据库,得一个旧值;

(3)请求B将新值写入数据库;

(4)请求B删除缓存;

(5)请求A将查到的旧值写入缓存;

问题:会发生脏数据,但是几率不大,因为步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

如何解决脏数据呢?给缓存设有效时间是一种方案。其次,采用策略2(先删除缓存,再更新数据库)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。

第二次删除,如果删除失败怎么办? 这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库: (1)请求A进行写操作,删除缓存; (2)请求B查询发现缓存不存在; (3)请求B去数据库查询得到旧值; (4)请求B将旧值写入缓存; (5)请求A将新值写入数据库; (6)请求A试图去删除请求B写入对缓存值,结果失败了;ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。

解决方案一

(1)更新数据库数据;

(2)缓存因为种种问题删除失败;

(3)将需要删除的key发送至消息队列;

(4)自己消费消息,获得需要删除的key;

(5)继续重试删除操作,直到成功; 缺点:对业务线代码造成大量的侵入

解决方案二: 启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

(1)更新数据库数据;

(2)数据库会将操作信息写入binlog日志当中;

(3)订阅程序提取出所需要的数据以及key;

(4)另起一段非业务代码,获得该信息;

(5)尝试删除缓存操作,发现删除失败;

(6)将这些信息发送至消息队列;

(7)重新从消息队列中获得该数据,重试操作;

订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。重试机制,采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试。

🍊 Redis分布式锁底层实现

🎉 如何实现

redis使用setnx作为分布式锁,在多线程环境下面,只有一个线程会拿到这把锁,拿到锁的线程执行业务代码,执行业务代码需要一点时间,所以这段时间拒绝了很多等待获取锁的请求,直到有锁的线程最后释放掉锁,其他线程才能获取锁,这个就是redis的分布式锁的使用。

🎉 使用redis锁会有很多异常情况,如何处理这些异常呢

📝 1.redis服务挂掉了,抛出异常了,锁不会被释放掉,新的请求无法进来,出现死锁问题

添加try finally处理

📝 2.服务器果宕机了,导致锁不能被释放的现象

设置超时时间

📝 3.锁的过期时间比业务执行时间短,会存在多个线程拥有同一把锁的现象

如果有一个线程执行需要15s,过期时间只有10s,当执行到10s时第二个线程进来拿到这把锁,会出现多个线程拿到同一把锁执行。

续期超时时间,当一个线程执行5s后对超时时间续期10s,续期设置可以借助redission工具,加锁成功,后台新开一个线程,每隔10秒检查是否还持有锁,如果持有则延长锁的时间,如果加锁失败一直循环(自旋)加锁。

📝 4.锁的过期时间比业务执行时间短,锁永久失效

如果有一个线程执行需要15s,过期时间只有10s,当执行到10s时第二个线程进来拿到这把锁,会出现多个线程拿到同一把锁执行,在第一个线程执行完时会释放掉第二个线程的锁,以此类推,导致锁的永久失效。

给每个线程都设置一个唯一标识,避免出现程序执行的时间超过设置的过期时间,导致其他线程删除了自己的锁,只允许自己删除自己线程的锁

🍊 Redis热点数据缓存

热点数据缓存

当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

🎉 互斥锁(mutex)

解决方案一:互斥锁(mutex)

只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据。

1)从Redis获取数据,如果值不为空,则直接返回值;否则执行下面的2.1)和2.2)步骤

2.1)如果set(nx和ex)结果为true,说明此时没有其他线程重建缓存, 那么当前线程执行缓存构建逻辑

2.2)如果set(nx和ex)结果为false,说明此时已经有其他线程正在执 行构建缓存的工作,那么当前线程将休息指定时间(例如这里是50毫秒,取决于构建缓存的速度)后,重新执行函数,直到获取到数据。

优缺点:如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。

🎉 永远不过期

解决方案二:永远不过期

从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期 后产生的问题,也就是“物理”不过期。从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻 辑过期时间后,会使用单独的线程去构建缓存。

优缺点:由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

问题:怎么知道哪些数据是热点数据?因为本地缓存资源有限,不可能把所有的商品数据进行缓存,它只会缓存热点的数据。那怎么知道数据是热点数据呢?

利用redis4.x自身特性,LFU机制发现热点数据。实现很简单,只要把redis内存淘汰机制设置为allkeys-lfu或者volatile-lfu方式,再执行./redis-cli --hotkeys会返回访问频率高的key,并从高到底的排序,在设置key时,需要把商品id带上,这样就是知道是哪些商品了。

🍊 高并发

单机的 Redis,能够承载的 QPS大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。

🍊 高可用

Redis哨兵集群实现高可用,哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于主节点是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个备节点作为新的主节点。每个哨兵会向其它哨兵、主节点、备节点定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂.若“哨兵群”中的多数哨兵,都报告某一主节点没响应,系统才认为该主节点"彻底死亡",通过算法,从剩下的备节点中,选一台提升为主节点,然后自动修改相关配置。

🍊 哨兵机制

哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于主节点是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个备节点作为新的主节点。每个哨兵会向其它哨兵、主节点、备节点定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂。

若“哨兵群”中的多数哨兵,都报告某一主节点没响应,系统才认为该主节点"彻底死亡",通过算法,从剩下的备节点中,选一台提升为主节点,然后自动修改相关配置。可以通过修改sentinel.conf配置文件,配置主节点名称,IP,端口号,选举次数,主服务器的密码,心跳检测毫秒数,做多少个节点等。

🎉 Redis 哨兵主备切换的数据丢失问题

📝 异步复制导致的数据丢失

master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。 脑裂导致的数据丢失:某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master还运行着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个master ,也就是所谓的脑裂。 此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client写入的数据,因此,这部分数据也就丢失了

解决方案:

进行配置:min-slaves-to-write 1 min-slaves-max-lag 10

通过配置至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒,超过了master 就不会再接收任何请求了。

减少异步复制数据的丢失

一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。 减少脑裂的数据丢失:如果一个 master 出现了脑裂,跟其他slave 丢了连接,如果不能继续给指定数量的slave 发送数据,而且 slave 超过10 秒没有给自己ack消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失10 秒的数据。

🍊 集群模式

数据量很少的情况下,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 Redis 主从架构的高可用性。

海量数据+高并发+高可用的场景的情况下,使用Redis cluster ,自动将数据进行分片,每个 master 上放一部分数据,它支撑 N个 Redis master node,每个 master node 都可以挂载多个 slave node。 这样整个 Redis就可以横向扩容了,如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master节点就能存放更多的数据了。而且部分 master 不可用时,还是可以继续工作的。

在 Redis cluster 架构下,使用cluster bus 进行节点间通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了一种二进制的协议, gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

🎉 集群协议

集群元数据的维护:集中式、Gossip 协议

📝 集中式

集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于zookeeper对所有元数据进行存储维护。集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。

📝 gossip 协议

gossip 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。gossip好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。

在 Redis cluster 架构下,每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,每个 Redis 要放开两个端口号,比如 7001,那么用于节点间通信的就是 17001 端口,17001端口号是用来进行节点间通信的,也就是 cluster bus 的东西。每个节点每隔一段时间都会往另外几个节点发送 ping 消息,同时其它几个节点接收到 ping 之后返回 pong 。

🍊 多级缓存架构

🍊 并发竞争

Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?

多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。

CAS 类的乐观锁方案:某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。

你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

🍊 Redis cluster 的高可用与主备切换原理

如果一个节点认为另外一个节点宕机,这是属于主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是客观宕机,跟哨兵的原理几乎一样,sdown,odown。流程为:如果一个节点认为某个节点pfail 了,那么会在 gossip ping 消息中, ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成fail 。 每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。所有的 master node 开始 slave 选举投票,给要进行选举的slave 进行投票,如果大部分 master node (N/2 + 1) 都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。从节点执行主备切换,从节点切换为主节点。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
10 0
|
1天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
5 0
|
2天前
|
存储 Java 数据库连接
使用Java开发桌面应用程序
使用Java开发桌面应用程序
9 0
|
2天前
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
8 0
|
2天前
|
分布式计算 负载均衡 Java
构建高可用性Java应用:介绍分布式系统设计与开发
构建高可用性Java应用:介绍分布式系统设计与开发
7 0
|
2天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
5 0
|
2天前
|
前端开发 JavaScript Java
Java与Web开发的结合:JSP与Servlet
Java与Web开发的结合:JSP与Servlet
7 0
|
2天前
|
设计模式 算法 Java
设计模式在Java开发中的应用
设计模式在Java开发中的应用
14 0
|
2天前
|
监控 Java Maven
揭秘Java Agent技术:解锁Java工具开发的新境界
作为JDK提供的关键机制,Java Agent技术不仅为Java工具的开发者提供了一个强大的框架,还为性能监控、故障诊断和动态代码修改等领域带来了革命性的变革。本文旨在全面解析Java Agent技术的应用场景以及实现方式,特别是静态加载模式和动态加载模式这两种关键模式。
16 0
|
3天前
|
存储 Java 开发者
探索Java开发中触发空指针异常的场景
作为一名后端开发者在Java编程的世界中,想必大家对空指针并不陌生,空指针异常是一种常见而又令人头疼的问题,它可能会在我们最不经意的时候突然出现,给我们的代码带来困扰,甚至导致系统的不稳定性,而且最可怕的是有时候不能及时定位到它的具体位置。针对这个问题,我们需要深入了解触发空指针异常的代码场景,并寻找有效的方法来识别和处理这些异常情况,而且我觉得空指针异常是每个Java开发者都可能面临的挑战,但只要我们深入了解它的触发场景,并采取适当的预防和处理措施,我们就能够更好地应对这个问题。那么本文就来分享一下实际开发中一些常见的触发空指针异常的代码场景,并分享如何有效地识别和处理这些异常情况。
18 1
探索Java开发中触发空指针异常的场景