Redis(Remote Dictionary Server),作为一款高性能的Key/Value键值对存储系统,是一个使用C语言编写的高性能内存数据库,一般会用来做缓存,消息队列,分布式锁,同时还支持事务,持久化,高可用架构等。已成为许多应用程序的后端支柱。对于Java开发者来说,了解和掌握Redis的特性和应用场景是至关重要的。本文将深入探讨Redis的核心概念、数据类型、持久化机制,以及在Java中使用Redis的实践。
本文篇理论知识点,实践请查看官方文档。
Redis(Remote Dictionary Server)是一个开源的高性能键值存储系统,提供了一系列丰富的数据类型和操作。Redis具有以下核心特性:
Java开发者可以使用各种客户端库与Redis进行交互,如Jedis、Lettuce和Redisson等。以下是使用Jedis库的基本步骤:
keys:查看所有键
dbsize:键总数
exists key:检查键是否存在
del key [key ...]:删除键
expire key seconds:键过期
ttl key:通过ttl命令观察键的剩余过期时间
type key:键的数据结构类型
在查询时,检测到key过期了,此时对key进行删除。这里的缺点是,假如key长时间不被访问,也就无法删除,此时就会一直占用着内存。
每隔一段时间对redis数据进行一次检查,删除过期key。但是这里并不会对所有key进行检查,只是随机取一些key,检查是否过期,过期了然后删除。
当访问一个缓存和数据库都不存在的 key时,请求会直接打到数据库上并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。这时缓存就好像被“穿透”了一样,起不到任何作用。假如一些恶意的请求,故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力,甚至数据库挂掉,这就叫做缓存穿透。
解决方案?
方案1:接口校验。在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验,用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。
方案2:缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设宣。
方案3:布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。可把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
什么是缓存雪崩以及解决方案?
缓存雪崩是当缓存服务器重启或者大量缓存集中在某一个时间段失效,造成瞬时数据库请求量大,压力骤增,导致系统崩溃。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。
解决方案:
方案1:打散过期时间。不同的key,设置不同的过期时间(例如使用一个随机值),让缓存失效的时间点尽量均匀。
方案2:做二级缓存。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
方案3:加互斥锁。缓存失效后,通过加锁或者队列来控制写缓存的线程数量。比如对某个key只允许一个线程操作缓存,其他线程等待。
方案4:热点数据不过期。该方式和缓存击穿一样,要着重考虑刷新的时间间隔和数据异常如何处理的情况。
Redis数据持久化从客户端发起请求开始,到服务器真实地写入磁盘,需要发生如下几件事情:
其过程描述如下:
1.客户端向数据库发送写命令(数据在客户端的内存中)
2.数据库接收到客户端的写请求(数据在服务器的内存中)
3.数据库调用系统 API将数据写入磁盘(数据在内核缓冲区中)
4.操作系统将写缓冲区传输到磁盘控控制器(数据在磁盘缓存中)
5.操作系统的磁盘控制器将数据写入实际的物理媒介中(数据在磁盘中)
RDB方式的持久化是Redis数据库默认的持久化机制,是保证redis中数据可靠性的方式之一,这种方式可以按照一定的时间周期策略把内存中的数据以快照(二进制数据)的形式保存到磁盘文件中,即快照存储。对应产生的数据文件为dump.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
1.基于配置文件中的save规则周期性的执行持久化。
2.手动执行了shutdown操作会自动执行rdb方式的持久化。
3.手动调用了save或bgsave指令执行数据持久化。
4.主从复制架构下Slave连接到Master时,Master会对数据持久化,然后全量同步到Slave。
SAVE生成 RDB 快照文件,但是会阻塞主进程,服务器将无法处理客户端发来的命令请求,所以通常不会直接使用该命令。BGSAVE指令会fork子进程来生成 RDB 快照文件,阻塞只会发生在 fork 子进程的时候,之后主进程可以正常处理请求。
1.RDB 文件是压缩的二进制文件,占用空间小,保存的是某个时间点的数据,适合做备份。
2.RDB 适用于灾难恢复,它只有一个文件,文件内容都非常紧凑,方便传送到其它数据中心。
3.RDB 持久化性能较好,可由子进程处理保存工作,父进程无须执行任何磁盘 I/0 操作。
1、RDB方式在服务器故障时容易造成数据的丢失。实际项目中,我们可通过配置来控制持久化的频率。但是,如果频率太频繁,可能会对 Redis 性能产生影响。所以通常可能设置至少5分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失5分钟数据。
2、RDB持久化过程中的fork操作,可能会导致内存占用加倍。Linux系统fork 子进程采用的是 copy-on-write 的方式(写时复制,修改前先复制),在 Redis 执行 RDB 持久化期间,如果 client 写入数据很频繁,那么将增加 Redis 占用的内存,最坏情况下,内存的占用将达到原先的2倍。
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 格式的全量数据,后半段是 AOF 格式的增量数据。
开启:混合持久化的配置参数为 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 格式,可读性差。
AOF持久化是通过保存被执行的写命令来记录数据库状态的,随着写入命令的不断增加,AOF文件中的内容会越来越多,文件的体积也会越来越大。如果不加以控制,体积过大的 AOF 文件可能会对 Redis 服务器、甚至整个宿主机造成影响,并且 AOF 文件的体积越大,使用 AOF 文件来进行数据还原所需的时间就越多。
举个例子,如果你对一个计数器调用了 100 次 INCR ,那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100 条记录。然而在实际上,只使用一条 SET 命令已经足以保存计数器的当前值了,其余 99条记录实际上都是多余的。为了处理这种情况,Redis 引入了 AOF 重写,可以在不打断服务端处理请求的情况下,对 AOF 文件进行重建(rebuild)。
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 文件保存的数据库状态不一致。
Redis为了实现其高可用性,基于需求不同定义了如下几种架构形式:
Redis数据同步分为全量同步和增量同步。
全量同步发生在slave第一次连接master时,其同步过程如下:
1.slave连接master。
2.主节点会执行bgsave指令,对数据进行持久化。
3.主节点将rdb文件发送给从节点
4.从节点基于rdb文件恢复数据。
可能存在的问题
对于全量同步,RDB文件生成时,Redis主进程可能还在接收写操作,这样会导致master和slave数据的不一致。还有假如RDB文件比较大,同步给slave时,因网络阻塞或中断,可能会有部分数据的丢失。
全量同步出现了中断或全量同步之后,Master又有新的写入操作,此时会触发增量同步。
其同步过程如下:
1.从节点会继续发送同步请求。
2.主节点基于数据同步请求将写指令同步到从节点。
3.从节点执行指令进行数据更新。
主节点故障需要手动将从节点(Slave)升级为主节点(Master),主节点的写操作及存储将受到单机节点的限制。
Redis中的哨兵本质上是一个服务(Redis哨兵节点),用于监控Redis数据节点,然后进行故障自动转移,实现高可用。
Redis中的哨兵起到了一个定时监控的功能,每隔1秒会向数据节点发送心跳,检测网络和节点状态,然后还会每隔10秒向数据节点发送info命令获取新的拓扑结构。
假如在响应时间超过了哨兵配置的down-after-milliseconds值(默认30秒),则sentinel节点会认为该节点下线了。这种下线为主观下线,当某个sentinel认为一个数据主节点主观下线了,这个哨兵(sentinel)还会与其它sentinel进行通讯,询问这个主节点的状态,假如有多个sentinel节点(具体几个可以通过配置实现,一般默认是一半)认为这个主节点有问题,此时sentinel会对主节点做出客观下线的决定。此时,Sentinel节点之间会有一个领导的选举工作,它们会从中选择一个Sentinel节点作为领导者进行故障转移工作。
从slave节点中选举一个节点作为新的主节点(Master),sentinel会向其它从节点发送命令,让他们成为新的主节点的从节点,sentinel会将原有主节点更新为从节点,假如故障恢复了,让此节点连接新的主节点。
第一步:过滤不健康节点(ping不通)。
第二步:选择优先级最高的从节点。
第三步:假如优先级都一样,选择数据最完整。
第四步:选择runid最小的(每个redis实例启动后都会随机生成一个40位的runid)
第一、Redis集群要解决数据分区(分片)存储,增加存储容量、提高读写响应速度。
第二、Redis集群支持高可用,实现主从节点的自动故障转移。
第一、集群中每个节点都会定期向其它节点发送ping消息,回复为pong.
第二、当发送ping消息的节点没有收到pong回复时,则主观认为节点下线。
第三、集群内当半数以上持有槽节点认为节点主观下线,则触发客观下线流程。
第四、如果客观下线的节点是主节点,则从它的从节点中选出一个节点作为主节点。
Redis集群通过设置多个虚拟槽(总计0~16383个槽),每个虚拟槽会对应一个数据集(一个范围值),基于对key进行CRC16(key)&16383计算结果进行数据存储。
优点:数据分配均匀,通过槽实现了数据和实际节点之间的解耦,节点数量发生了变化(扩容、缩容)对整个系统的影响很小(只需要对槽进行重新分配即可)。
在使用Redis时,以下是一些最佳实践和注意事项: