访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常。
由于不恰当的业务功能实现,或者外部恶意攻击不断地请求某些不存在的数据内存,由于缓存中没有保存该数据,导致所有的请求都会落到数据库上,对数据库可能带来一定的压力,甚至崩溃。
缓存同时失效或缓存服务异常,瞬时大量请求直接到达 DB,对 DB 造成压力导致服务异常,
总的来说,缓存穿透和雪崩带来的影响都是缓存失效或未命中导致 DB 压力大,从而可能拖垮服务。这些情况都是可能导致 DB 异常从而影响服务的可用性。
其实关键就是过滤无效的数据,增加缓存命中率,并添加对应的隔离措施以增加整个服务的可用性。
使用了缓存的话,就有双写问题。通常,涉及到双写问题,就会有数据不一致的情况。需要保存数据一致需要牺牲性能,不过实际场景中一般也不要求这么高的一致性。要求严格一致的话,可以将读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
缓存模式:Cache Aside Pattern(先淘汰缓存,再写数据库):
eg. 有个写请求,此时删除了缓存中的数据,但是还没来得及写数据库;这个时候来了一个读请求,又把数据库中的旧数据 load 到缓存中去了,然后上一个请求的写数据库操作完成了。这个时候,数据库中的是最新的数据,缓存中的却是旧数据了.
解决思路:部分请求串行化或采取补偿操作。
分布式锁实现要保证几个基本点。
互斥性:任意时刻,只有一个资源能够获取到锁。
容灾性:能够在未成功释放锁的的情况下,一定时限内能够恢复锁的正常功能。
统一性:加锁和解锁保证同一资源来进行操作。
示例:我们通过 SETNX 命令来实现的分布式锁,设置了过期时间,不至于会出现线程异常导致锁无法释放的情况。
Redis 基于 Reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器(File Event Handler)。这个文件事件处理器是单线程的,Redis 才叫做单线程的模型,采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器来处理这个事件,为啥快呢:
Redis是单线程处理网络指令请求,所以不需要考虑并发安全问题。所有的网络请求都是一个线程处理。但不代表所有模块都是单线程。
Redis 有持久化机制的,它支持 AOF 和 RDB 两种持久化方式。
RDB:通过 fork 一个子进程保存当前内存的一个快照实现备份. 适合大规模的数据恢复,但是数据的完整性和一致性不高,因为 RDB 可能在最后一次备份时宕机了。另外备份时会占用内存,因为 Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。配置方法如下:
# save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中
save <seconds> <changes>
# 指定本地数据库文件名,一般采用默认的 dump.rdb
dbfilename dump.rdb
# 默认开启数据压缩
rdbcompression yes
AOF: 通过对每条写入命令以 append-only 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。 AOF 对数据数据的完整性和一致性支持更好,但是其备份日志文件一般会比 RDB 方式的备份文件更大,恢复也更慢,同时由于 fsync 的频率方式,会影响 Redis 的性能。
# 打开aof
appendonly yes
# 日志文件
appendfilename "appendonly.aof"
# 更新条件
# appendfsync always
appendfsync everysec
# appendfsync no
# 触发重写的配置
# 时间长了日志会特别大,此时需要触发重写
# 重写的原理:Redis 会fork出一条新进程,读取内存中的数据,并重新写到一个临时文件中。
# 最后替换旧的aof文件。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
后续:Redis 4.0 之后,持久化增加了混合 RDB-AOF 持久化格式。具体可以参考
http://blog.huangz.me/diary/2016/redis-4-outline.html
Redis 主从模式原理:
Redis 2.8 之前主从模式,主从之间的数据复制只有全复制机制,通过执行命令 SLAVEOF ip port,使得其成为从服务器。全数据复制流程如下:
从服务器向主服务器发送 SYNC 命令。
主服务器收到 SYNC 请求后,执行 BGSAVE 命令在后台生成 RDB 文件,并使用一个缓冲区记录从现在开始执行的写命令。
主服务器将生成的 RDB 文件发送给从服务器,从服务器根据RDB文件更新状态。
主服务器将缓冲区中的写命令发送给从服务器,从服务器执行这些命令,最终和主服务器达到一致的状态。
命令传播
数据复制完成后,为了保持一致的状态,主服务的写命令都需要传播给其从服务器。
不足:上面的方式存在不足,即如果从服务器是和主服务器断开后,又立马重新连接上后。那么此时如果还执行全同步的话,就十分浪费。因为全同步非常占用 CPU、IO 和带宽等。
Redis 2.8 之后,支持了 PSYNC,即部分重同步机制。部分重同步机制主要依靠:
主从服务器的复制偏移量: 用于记录主从服务器的状态是否一致,以及数据复制时的偏移量
主服务器的复制积压缓冲区:固定大小的(默认 1M)FIFO 队列,用于记录主服务器的写命令
主服务器的 ID:用于判断,从服务器断开连接后重新连接到的主服务器还是不是原来那个
部分重同步流程:
主从服务器都有一个复制偏移量,当执行了写命令时,复制偏移量对应会增加。
从服务器请求同步,带上当前的 offset 和之前的主服务器 ID;
如果请求中的主服务 ID 和当前的主服务器ID不一致,或者其 offset 的值已经不再复制积压缓冲区内,那么需要执行全同步,流程同上;
否则,执行部分同步,主服务器将复制积压缓冲区中 offset 之后的命令发送给从服务器;
从服务器执行对应写命令,并修改 offset,达到和主服务器状态一致。
在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除。
key 过期的时候不删除,每次从数据库获取 key 的时候去检查是否过期,若过期,则删除,返回 null。
每隔一段时间执行一次删除(在 redis.conf 配置文件设置 hz,1s 刷新的频率)过期 key 操作。
优点:可以控制删除操作的时长和频率,来减少 CPU 时间占用,可以避免惰性删除时候内存泄漏的问题。
缺点:
对内存友好方面,不如定时策略
对 CPU 友好方面,不如惰性策略
那如果执行了上述的删除操作后,Redis 的内存空间还是不足怎么办呢?
设置了过期时间的,到期后会被上述操作删除掉;如果此时内存还是不够的话,Redis 会根据配置的策略来执行对应的操作,主要有 noeviction、allkeys-lru、allkeys-random、volatile-lru、volatile-random、volatile-ttl 这几种,balabalabala…
部分内容参考以下链接
https://gitbook.cn/books/5d07228c2df51311ff3a6498/index.html
https://gitbook.cn/books/5c97654679ca930d56250d14/index.html