深入探索Redis:Java开发者的关键指南

Redis(Remote Dictionary Server),作为一款高性能的Key/Value键值对存储系统,是一个使用C语言编写的高性能内存数据库,一般会用来做缓存,消息队列,分布式锁,同时还支持事务,持久化,高可用架构等。已成为许多应用程序的后端支柱。对于Java开发者来说,了解和掌握Redis的特性和应用场景是至关重要的。本文将深入探讨Redis的核心概念、数据类型、持久化机制,以及在Java中使用Redis的实践。

本文篇理论知识点,实践请查看官方文档。

一、Redis的核心概念

Redis(Remote Dictionary Server)是一个开源的高性能键值存储系统,提供了一系列丰富的数据类型和操作。Redis具有以下核心特性:

  1. 键值存储:最基本的数据结构是键值对,类似于哈希表。
  2. 丰富的数据类型:包括字符串、哈希、列表、集合和有序集合。
  3. 原子操作:所有的读写操作都是原子性的,确保数据的一致性。
  4. 持久化:可以通过RDB和AOF持久化机制确保数据安全。
  5. 事务处理:支持事务功能,可以执行多个命令,并确保它们作为一个原子操作执行。

在Java中使用Redis

Java开发者可以使用各种客户端库与Redis进行交互,如Jedis、Lettuce和Redisson等。以下是使用Jedis库的基本步骤:

  1. 添加依赖:在项目中添加Jedis库的依赖。可以通过Maven或Gradle进行添加。
  2. 连接Redis服务器:使用Jedis客户端连接到Redis服务器。需要提供服务器的地址和端口号。
  3. 执行命令:使用Jedis客户端提供的API执行各种Redis命令,如SET、GET、HSET等。
  4. 处理响应:根据命令返回的数据类型,对响应进行处理。例如,对于字符串类型的数据,可以直接获取返回的字符串;对于列表类型的数据,需要遍历整个列表来获取所有元素。
  5. 关闭连接:完成操作后,记得关闭与Redis服务器的连接,释放资源。

二、Redis的基础应用

Redis支持多种数据类型,每种类型都有其特定的应用场景:

深入探索Redis:Java开发者的关键指南_第1张图片

  1. String(字符串):最基础的数据类型,用于存储简单的值。例如,数字、字符串等。
  2. Hash(哈希):用于存储对象的属性。通过一个键来访问内部多个字段的值。
  3. List(列表):基于链表结构的可变序列,可以存储多个字符串元素。
  4. Set(集合):无序不重复元素集合,用于快速查找元素是否存在。
  5. Zset(排序集合):可对数据基于某个权重进行排序,可做排行榜,取TOP N操作。

常用全局命令

keys:查看所有键

dbsize:键总数

exists key:检查键是否存在

del key [key ...]:删除键

expire key seconds:键过期

ttl key:通过ttl命令观察键的剩余过期时间

type key:键的数据结构类型

Redis过期数据的删除策略 ?

  • 惰性删除

在查询时,检测到key过期了,此时对key进行删除。这里的缺点是,假如key长时间不被访问,也就无法删除,此时就会一直占用着内存。

  • 定期删除

每隔一段时间对redis数据进行一次检查,删除过期key。但是这里并不会对所有key进行检查,只是随机取一些key,检查是否过期,过期了然后删除。

什么是缓存穿透以及解决方案?

当访问一个缓存和数据库都不存在的 key时,请求会直接打到数据库上并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。这时缓存就好像被“穿透”了一样,起不到任何作用。假如一些恶意的请求,故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力,甚至数据库挂掉,这就叫做缓存穿透。

深入探索Redis:Java开发者的关键指南_第2张图片

解决方案?

方案1:接口校验。在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验,用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。

方案2:缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设宣。

方案3:布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。可把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

什么是缓存雪崩以及解决方案?

缓存雪崩是当缓存服务器重启或者大量缓存集中在某一个时间段失效,造成瞬时数据库请求量大,压力骤增,导致系统崩溃。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。

深入探索Redis:Java开发者的关键指南_第3张图片

解决方案:

方案1:打散过期时间。不同的key,设置不同的过期时间(例如使用一个随机值),让缓存失效的时间点尽量均匀。

方案2:做二级缓存。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

方案3:加互斥锁。缓存失效后,通过加锁或者队列来控制写缓存的线程数量。比如对某个key只允许一个线程操作缓存,其他线程等待。

方案4:热点数据不过期。该方式和缓存击穿一样,要着重考虑刷新的时间间隔和数据异常如何处理的情况。

三、持久化机制

说说Redis的持久化逻辑?

Redis数据持久化从客户端发起请求开始,到服务器真实地写入磁盘,需要发生如下几件事情:

深入探索Redis:Java开发者的关键指南_第4张图片

其过程描述如下:
1.客户端向数据库发送写命令(数据在客户端的内存中)

2.数据库接收到客户端的写请求(数据在服务器的内存中)

3.数据库调用系统 API将数据写入磁盘(数据在内核缓冲区中)

4.操作系统将写缓冲区传输到磁盘控控制器(数据在磁盘缓存中)

5.操作系统的磁盘控制器将数据写入实际的物理媒介中(数据在磁盘中)

说说Redis的持久化方式?

RDB方式的持久化是Redis数据库默认的持久化机制,是保证redis中数据可靠性的方式之一,这种方式可以按照一定的时间周期策略把内存中的数据以快照(二进制数据)的形式保存到磁盘文件中,即快照存储。对应产生的数据文件为dump.rdb。

RDB持久化方式的常用配置参数?

#这里表示每隔60s,如果有超过1000个key发生了变更,就执行一次数据持久化。
#这个操作也被称之为snapshotting(快照)。
save 60 1000
# 持久化 rdb文件遇到问题时,主进程是否接受写入,yes表示停止写入
#如果是no 表示redis继续提供服务。
stop-writes-on-bgsave-erroryes
#在进行快照镜像时,是否进行压缩。yes:压缩
#但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间。
rdbcompression yes
#一个CRC64的校验就被放在了文件末尾,当存储或者加载rbd文件的时候#会有一个10%左右的性能下降,为了达到性能的最大化,你可以关掉这个配置项。
rdbchecksum yes
#快照的文件名
dbfilename dump.rdb
#存放快照的目录
dir /var/lib/redis

什么情况下会触发RDB持久化?

1.基于配置文件中的save规则周期性的执行持久化。
2.手动执行了shutdown操作会自动执行rdb方式的持久化。
3.手动调用了save或bgsave指令执行数据持久化。
4.主从复制架构下Slave连接到Master时,Master会对数据持久化,然后全量同步到Slave。

save和bgsave有什么不同?

SAVE生成 RDB 快照文件,但是会阻塞主进程,服务器将无法处理客户端发来的命令请求,所以通常不会直接使用该命令。BGSAVE指令会fork子进程来生成 RDB 快照文件,阻塞只会发生在 fork 子进程的时候,之后主进程可以正常处理请求。

RDB方式持久化有哪些优势?

1.RDB 文件是压缩的二进制文件,占用空间小,保存的是某个时间点的数据,适合做备份。
2.RDB 适用于灾难恢复,它只有一个文件,文件内容都非常紧凑,方便传送到其它数据中心。
3.RDB 持久化性能较好,可由子进程处理保存工作,父进程无须执行任何磁盘 I/0 操作。

RDB方式持久化有哪些缺点?

1、RDB方式在服务器故障时容易造成数据的丢失。实际项目中,我们可通过配置来控制持久化的频率。但是,如果频率太频繁,可能会对 Redis 性能产生影响。所以通常可能设置至少5分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失5分钟数据。
2、RDB持久化过程中的fork操作,可能会导致内存占用加倍。Linux系统fork 子进程采用的是 copy-on-write 的方式(写时复制,修改前先复制),在 Redis 执行 RDB 持久化期间,如果 client 写入数据很频繁,那么将增加 Redis 占用的内存,最坏情况下,内存的占用将达到原先的2倍。

如何理解AOF方式的持久化?

Redis中AOF方式的持久化是将Redis收到的每一个写命令都追加到磁盘文件的最后,类似于MySQL的binlog。当Redis重启时,会重新执行文件中保存的写命令,然后在内存中重建整个数据库的内容。
AOF 持久化默认是关闭的,可以通过配置appendonly yes 开启。当AOF 持久化功能打开后,服务器在执行完一个写命令之后,会将被执行的写命令追加到服务器端 aof 缓冲区(aof buf)的末尾,然后再将aof buf 中的内容写到磁盘。

Linux 操作系统中为了提升性能,使用了页缓存(page cache)。。当我们将 aof buf 的内容写到磁盘上时,此时数据并没有真正的落盘,而是存在在 page cache 中,为了将 page cache 中的数据真正落盘,需要执行fsync/fdatasync 命令来强制刷盘。这边的文件同步做的就是刷盘操作,或者叫文件刷盘可能更容易理解一些。

AOF持久化方式有什么优势?

  • AOF方式 比 RDB方式的持久化更加可靠。你可以设置不同的 fsync策略(no、everysec和 always)。默认是everysec,在这种配置下,redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据。
  • AOF文件是一个基于纯追加模式的日志文件。即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机等等),我们也可以使用redis-check-aof 工具也可以轻易地修复这种问题。
  • AOF文件太大时,Redis 会自动在后台进行重写。重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写是绝对安全,因为重写是在一个新的文件上进行,同时 Redis 会继续往旧的文件追加数据。当新文件重写完毕,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。
  • AOF 文件以Redis协议的格式有序地保存了对数据库执行的所有写操作,可读性好。
    对如果你不小心执行了 FLUSHALL 命令把所有数据刷掉了,但只要 AOF文件没有被重写,那么只要停止服务器,移除 AOF 文件末尾的FLUSHALL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF持久化方式有什么劣势?

  • 对于相同的数据集,AOF 文件的大小一般会比 RDB 文件大。根据所使用的 fsync 策略,AOF 的速度可能会比 RDB 慢。通常 fsync 设置为每秒一次就能获得比较高的性能,而关闭 fsync 可以让 AOF 的速度和 RDB 一样快。
  • AOF 可能会因个别命令的原因,导致 AOF 文件在重新载入时,无法将数据恢复到原样。
    虽然这种 bug 在 AOF 文件中并不常见,但是相较而言, RDB 几乎是不可能出现这种 bug 的。

如何理解Redis的混合持久化?

混合持久化并不是一种全新的持久化方式,而是对已有方式的优化。混合持久化只发生于 AOF 重写过程。使用了混合持久化,重写后的新AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。

深入探索Redis:Java开发者的关键指南_第5张图片

开启:混合持久化的配置参数为 aof-use-rdb-preamble,配置为 yes 时开启混合持久化,在 redis4 刚引入时,默认是关闭混合持久化的,但是在 redis 5 中默认已经打开了。

关闭:使用 aof-use-rdb-preamble no 配置即可关闭混合持久化。混合持久化本质是通过 AOF 后台重写(bgrewriteaof 命令)完成的,不同的是当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入到新的 AOF 文件,然后再将 AOF 重写缓冲区(aof rewrite buf blocks)的增量命令以 AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

优点:结合 RDB 和 AOF 的优点,更快的重写和恢复,

缺点:AOF 文件里面的 RDB 部分不再是 AOF 格式,可读性差。

Redis为什么要AOF重写?

AOF持久化是通过保存被执行的写命令来记录数据库状态的,随着写入命令的不断增加,AOF文件中的内容会越来越多,文件的体积也会越来越大。如果不加以控制,体积过大的 AOF 文件可能会对 Redis 服务器、甚至整个宿主机造成影响,并且 AOF 文件的体积越大,使用 AOF 文件来进行数据还原所需的时间就越多。

举个例子,如果你对一个计数器调用了 100 次 INCR ,那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100 条记录。然而在实际上,只使用一条 SET 命令已经足以保存计数器的当前值了,其余 99条记录实际上都是多余的。为了处理这种情况,Redis 引入了 AOF 重写,可以在不打断服务端处理请求的情况下,对 AOF 文件进行重建(rebuild)。

描述一下AOF重写的过程?

Redis中的AOF重写是 生成新的 AOF 文件来代替1日 AOF 文件,这个新的AOF 文件包含重建当前数据集所需的最少命令。具体过程是遍历所有数据库的所有键,从数据库读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令。

命令:有两个 Redis 命令可以用于触发 AOF 重写,一个是BGREWRITEAOF(底层fork子进程来重写)、另一个是 REWRITEAOF命令(会阻塞主进程);

开启:AOF 重写由两个参数共同控制,auto-aof-rewrite-percentage 和auto-aof-rewrite-min-size,同时满足这两个条件,则触发 AOF 后台重写 BGREWRITEAOF。例如:

当前AOF文件比上次重写后的AOF文件大小的增长比例超过100auto-aof-rewrite-percentage 100

当前AOF文件的文件大小大于64MBauto-aof-rewrite-min-size 64mb

关闭:auto-aof-rewrite-percentage 0,指定0的百分比,以禁用自动AOF重写功能。

AOF 后台重写存在的问题?

AOF 使用子进程进行重写,解决了主进程阻塞的问题,但是仍然存在另一个问题,那就是子进程在进行 AOF 重写期间,服务器主进程还需要继续处理命令请求,新的命令可能会对现有的数据库状态进行修改,从而使得当前的数据库状态和重写后的 AOF 文件保存的数据库状态不一致。

四. Redis系统架构设计

Redis常用拓扑结构?

Redis为了实现其高可用性,基于需求不同定义了如下几种架构形式:

  • 主从架构(一主一从,一主多从,一主多从从)

深入探索Redis:Java开发者的关键指南_第6张图片

  • 主从及哨兵架构

深入探索Redis:Java开发者的关键指南_第7张图片

  • 多节点集群架构

深入探索Redis:Java开发者的关键指南_第8张图片

Redis数据同步方式?

Redis数据同步分为全量同步和增量同步。

  • 全量同步

全量同步发生在slave第一次连接master时,其同步过程如下:

1.slave连接master。

2.主节点会执行bgsave指令,对数据进行持久化。

3.主节点将rdb文件发送给从节点

4.从节点基于rdb文件恢复数据。

可能存在的问题
对于全量同步,RDB文件生成时,Redis主进程可能还在接收写操作,这样会导致master和slave数据的不一致。还有假如RDB文件比较大,同步给slave时,因网络阻塞或中断,可能会有部分数据的丢失。

  • 增量同步

全量同步出现了中断或全量同步之后,Master又有新的写入操作,此时会触发增量同步。

其同步过程如下:

1.从节点会继续发送同步请求。

2.主节点基于数据同步请求将写指令同步到从节点。

3.从节点执行指令进行数据更新。

Redis主从架构可能存在的问题?

主节点故障需要手动将从节点(Slave)升级为主节点(Master),主节点的写操作及存储将受到单机节点的限制。

如何理解Redis中的哨兵?

Redis中的哨兵本质上是一个服务(Redis哨兵节点),用于监控Redis数据节点,然后进行故障自动转移,实现高可用。

Redis中哨兵实现的底层原理?

Redis中的哨兵起到了一个定时监控的功能,每隔1秒会向数据节点发送心跳,检测网络和节点状态,然后还会每隔10秒向数据节点发送info命令获取新的拓扑结构。

假如在响应时间超过了哨兵配置的down-after-milliseconds值(默认30秒),则sentinel节点会认为该节点下线了。这种下线为主观下线,当某个sentinel认为一个数据主节点主观下线了,这个哨兵(sentinel)还会与其它sentinel进行通讯,询问这个主节点的状态,假如有多个sentinel节点(具体几个可以通过配置实现,一般默认是一半)认为这个主节点有问题,此时sentinel会对主节点做出客观下线的决定。此时,Sentinel节点之间会有一个领导的选举工作,它们会从中选择一个Sentinel节点作为领导者进行故障转移工作。

从slave节点中选举一个节点作为新的主节点(Master),sentinel会向其它从节点发送命令,让他们成为新的主节点的从节点,sentinel会将原有主节点更新为从节点,假如故障恢复了,让此节点连接新的主节点。

Redis新的主节点选举过程?

第一步:过滤不健康节点(ping不通)。
第二步:选择优先级最高的从节点。
第三步:假如优先级都一样,选择数据最完整。
第四步:选择runid最小的(每个redis实例启动后都会随机生成一个40位的runid)

Redis集群要解决什么问题?

第一、Redis集群要解决数据分区(分片)存储,增加存储容量、提高读写响应速度。
第二、Redis集群支持高可用,实现主从节点的自动故障转移。

Redis集群如何实现故障转移的?

第一、集群中每个节点都会定期向其它节点发送ping消息,回复为pong.
第二、当发送ping消息的节点没有收到pong回复时,则主观认为节点下线。
第三、集群内当半数以上持有槽节点认为节点主观下线,则触发客观下线流程。
第四、如果客观下线的节点是主节点,则从它的从节点中选出一个节点作为主节点。

Redis集群是如何实现数据存储的?

Redis集群通过设置多个虚拟槽(总计0~16383个槽),每个虚拟槽会对应一个数据集(一个范围值),基于对key进行CRC16(key)&16383计算结果进行数据存储。
优点:数据分配均匀,通过槽实现了数据和实际节点之间的解耦,节点数量发生了变化(扩容、缩容)对整个系统的影响很小(只需要对槽进行重新分配即可)。

五、最佳实践与注意事项

在使用Redis时,以下是一些最佳实践和注意事项:

  1. 合理选择数据类型:根据实际需求选择合适的数据类型,以优化存储和使用效率。例如,对于计数器类型的值,使用String类型;对于复杂对象或多个相关字段的值,使用Hash类型。
  2. 合理设置键的命名和存储方式:为了提高查询效率和管理方便性,建议为键设置有意义的名字,并合理组织数据结构。例如,使用特定前缀来标识键的类型或用途;将相关数据整合到一个哈希结构中存储。
  3. 考虑使用缓存策略:根据应用程序的需求和场景,合理利用Redis作为缓存层来加速数据访问和提高性能。例如,使用缓存预热、缓存淘汰策略等。
  4. 监控与日志记录:实施监控和日志记录机制来跟踪Redis的性能指标、故障排查和调试问题。可以使用第三方监控工具或自行实现监控逻辑来收集和分析数据。
  5. 确保数据安全与备份:除了使用持久化机制外,还应定期备份Redis中的数据,并采取措施确保数据的安全性,如设置访问控制、加密传输等。
  6. 考虑使用集群部署:对于高可用性和高性能的需求,可以考虑使用Redis集群部署方案来提高数据处理能力和可靠性。了解并选择适合的集群模式(如Redis Sentinel、Redis Cluster等)。
  7. 注意内存使用情况:由于Redis是基于内存的存储系统,因此

你可能感兴趣的:(Redis,redis,java,数据库,缓存)