当前位置:   article > 正文

深入浅出缓存(Redis)_深入浅出缓存那些事儿

深入浅出缓存那些事儿

失效时间

原则:

  • 缓存要尽量设置失效时间。
  • 时间不能很短。
  • 不同key失效时间随机。

原因:

  • 如果缓存没有失效时间,对于读多写少的场景,如果产生脏数据会永远没有机会更新,还会存在冷数据的问题,一段时间内没有被访问过的数据也会持续占据内存空间,造成空间浪费。(空间使用率)
  • 如果时间设置的很短,缓存频繁失效,高并发下数据库压力会上升,严重情况可能会宕机。
  • 缓存失效时间要随机。如果所有的key失效时间同时失效,大量的请求直接落到DB上,这就是常说的缓存雪崩。

三板斧雪崩、穿透、击穿

烂大街的缓存穿透、缓存击穿和缓存雪崩,你真的懂了?

缓存雪崩

定义:同一时间大量的缓存失效导致数据库压力升高。

解决:随机过期时间保证同一时间不会有大量的key失效。

缓存穿透

定义:访问一个数据库中不存在的值,由于每次在缓存中都查询不到会穿透到数据库中进行查询。

解决:缓存空值(最简单),或者引入布隆过滤器。接口基本的参数校验,比如过滤负值等等。

缓存击穿

定义:热点key失效的瞬间大量读请求落到数据库上。

解决: 加锁让少量的请求来重构缓存,例如分布式锁或者读写锁、本地锁等等,粒度根据实际情况选择。
热点key不过期,只有写请求会重构缓存。 做缓存时间的续期。

一致性和保证缓存一致性

一致性

携程最终一致和强一致性缓存实践

面试官: Redis 与 MySQL 双写一致性如何保证?

缓存和数据库一致性问题,看这篇就够了

一致性级别可以大致分为:最终一致性和强一致性。

引入一致性

当我们读请求变多以后,需要引入缓存来降低数据库压力,一次请求接口的过程大致如图所示。
在这里插入图片描述
当一个写请求过来应该怎么处理缓存呢?

缓存有两种处理方法:更新和删除,

  • 更新操作相当于单例中的饿汉式,在写库的时候同时修改缓存。
  • 删除操作相当于单例中的懒汉式,什么时候有读请求什么时候重构缓存。

更新缓存

如果有两个线程同时过来写同一个数据不一致情况如下图所示。
在这里插入图片描述

更新缓存的劣势:

  • 存在并发问题。
  • 站在缓存利用率的角度,写后如果没有读请求会造成资源的浪费。

结论: 不要对缓存进行更新。 不要对缓存进行更新。 不要对缓存进行更新。

删除缓存

当我们采用删除缓存时,应该先删还是后删呢?

「先更新数据库,再删除缓存」,默认情况下,短暂的不一致如图所示。
在这里插入图片描述
极端情况:如图所示,条件较为苛刻。
在这里插入图片描述
「先删除缓存,后更新数据库」

不一致的场景:在并发写读场景下,如图所示。
在这里插入图片描述
延迟双删:先删除缓存的方案可以改进一版,上述操作完成后休眠N毫秒后再次执行删除缓存。

到这里我们知道两种方案

  • 更新数据库,删除缓存,(可选二次删除缓存)
  • 删除缓存,更新数据库,二次删除缓存

方案二优点是:简单,无需引入额外的中间件,缺点是N毫秒不是特别好评估。

这两个方案的问题:无法保证缓存删除这一步一定可以成功。

考虑删除缓存失败

比较合适的做法是:消息队列或订阅binlog中间件。

可以使用分布式锁保证强一致

通过分布式锁,保障写请求更新的过程中没有读请求进来。

读写分离问题

当数据库读写分离后,如果在主库写入后,从库同步前读取到的值就是旧值。

延迟双删可以一定程度的避免这个问题。

先更新数据库方案则需要评估同步时间和删除缓存谁快谁慢。

总结

方案一:「先更新数据库,再删除缓存」。
方案二:「先更新数据库,再删除缓存,再删除缓存」。

并配合「消息队列」或「订阅变更日志」的方式来保证删除这一步的成功。

为什么有方案二:保证极端不一致。

高可用

4种 Redis 集群方案介绍+优缺点对比

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

脑裂问题

现象:主从架构下,原先的主节点跟集群失去联系,从节点升级成为新的主节点,两个主节点都会写入数据。
主要思路:阻止原先的主节点写入数据。
解决方案:通过参数限制当主节点的从节点小于N值时,停止写入数据。

# min-slaves-to-write 3
# min-slaves-max-lag 10

  • 1
  • 2
  • 3

在这里插入图片描述

https://redis.io/docs/management/sentinel/

min-replicas-to-write 1
min-replicas-max-lag 10
  • 1
  • 2

过期策略

最大内存配置

maxmemory 100mb
  • 1

https://redis.io/docs/reference/eviction/

  • noeviction: New values aren’t saved when memory limit is reached. When a database uses replication, this applies to the primary database,
  • allkeys-lru: Keeps most recently used keys; removes least recently used (LRU) keys,
  • allkeys-lfu: Keeps frequently used keys; removes least frequently used (LFU) keys
  • volatile-lru: Removes least recently used keys with the expire field set to true.
  • volatile-lfu: Removes least frequently used keys with the expire field set to true.
  • allkeys-random: Randomly removes keys to make space for the new data added.
  • volatile-random: Randomly removes keys with expire field set to true.
  • volatile-ttl: Removes keys with expire field set to true and the shortest remaining time-to-live (TTL) value.

LRU(least recently used )根据最近访问时间
LFU(least frequently used)根据访问频率
allkeys 是所有的key,volatile是带有过期时间的key。

在这里插入图片描述
4.0+支持 LFU 模式。

在这里插入图片描述
redis.windows.conf 中的示例

在这里插入图片描述
Windows下策略变更

config get maxmemory-policy
config set maxmemory-policy noeviction
config set maxmemory-policy volatile-ttl
  • 1
  • 2
  • 3

在这里插入图片描述
redis 7.0.8 最大内存和淘汰策略

# maxmemory <bytes>

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select one from the following behaviors:
#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, when there are no suitable keys for
# eviction, Redis will return an error on write operations that require
# more memory. These are usually commands that create new keys, add data or
# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE,
# SORT (due to the STORE argument), and EXEC (if the transaction includes any
# command that requires memory).
#
# The default is:
#
# maxmemory-policy noeviction
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

持久化

RDB:只持久化某一时刻的数据快照到磁盘上(创建一个子进程来做)
AOF:每一次写操作都持久到磁盘(主线程写内存,根据策略可以配置由主线程还是子线程进行数据持久化)

RDB 采用二进制 + 数据压缩的方式写磁盘,这样文件体积小,数据恢复速度也快
AOF 记录的是每一次写命令,数据最全,但文件体积大,数据恢复速度慢

如果你的业务对于数据丢失不敏感,采用 RDB 方案持久化数据
如果你的业务对数据完整性要求比较高,采用 AOF 方案持久化数据

最佳实践
在这里插入图片描述

CPU飙升

危害:
检测:
性能排查:
高消耗的命令,AOF频率,

CPU 相关:使用复杂度过高命令、数据的持久化,都与耗费过多的 CPU 资源有关

在这里插入图片描述

多级缓存

链路:nginx、分布式缓存、本地缓存、DB

https://www.cnblogs.com/Airgity/p/16741673.html

nginx 缓存(一级缓存)

分布式缓存(二级缓存)(redis)

本地缓存(三级缓存)

在这里插入图片描述

ConcurrentHashMap

深入浅出java并发编程(ConcurrentHashMap)

Ehcache

        <!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.10.8</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Guava Cache

        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Caffeine

快速上手

https://github.com/ben-manes/caffeine

        <!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>3.1.2</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Redis数据分布、分片

背景:在集群中实现一个用户数据存储的负载均衡,集群中有n个存储节点,如何均匀的把各个数据分布到这n个节点呢?,三台Redis如何尽可能高的提高缓存命中率。

轮训算法

原理:按照123123123的这种顺序循环存入。
优点:实现简单。
缺点:缓存命中率低,一个用户连续访问N次,会在3台Redis中都存储一份。

取模算法

原理:按照机器数量进行取模,%3,%5
优点:如果机器数量稳定,缓存分布稳定。
缺点:当新增节点和删除节点的时候,会涉及大量的数据迁移问题

一致性哈希

原理:系统中每个节点分配一个token,范围一般在0~232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点,如图10-3所示。

在这里插入图片描述

优点:加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。

缺点:

  • 加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
  • 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量数据节点的分布式方案。
  • 普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。

虚拟槽分区

在这里插入图片描述

秒杀不适合使用一致性哈希的原因:

现象:如果使用一致性哈希,会导致某台机器负载升高,不均匀。

解决方法:当访问不够均匀的时候将一致性HASH降级为取模。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/466905
推荐阅读
相关标签
  

闽ICP备14008679号