分布式缓存

缓存预热

redis 启动时可以把一些数据放入 redis 中,结合 spring bean 的初始化回调方案是可行的实现方式

  • 实现的 InitializingBean 接口的 afterPropertiesSet()方法
  • 使用 @PostConstruct 注解标注方法

内存回收

随着时间推移,redis 放入的数据越多,使用的内存越大,不能无休止使用内存,所以要做一些处理来释放内存

redis 内存回收策略一般两种方式:给 key 设置过期时间、内存自动淘汰的策略

过期策略

给 key 设置过期时间,到达时间后该 key 就释放

到达时间缓存就会立即删除吗?答案是否定的,给每个 key 设置一个定时器代价太大了,key 比较少影响还不大,当 key 过多的时候性能影响就不能接受了。redis 使用两种方式搭配完成内 key 过期的处理:惰性删除、周期删除

惰性删除

  • 每次访问缓存时,检查 key 的存活时间(ttl),如果到期,删除 key
  • 当一个 key 已经到达过期时间,但是很长时间不会再次访问,这个 key 就不会被删除,这是惰性删除的弊端

周期删除:故名思意,是一个定时任务,周期性的抽样检查部分 key,当达到 ttl 的 key 就删除,两种模式 SLOW、FAST

内存淘汰策略

当 redis 使用的内存达到阈值时(当内存充足就不会淘汰),执行每个命令前会淘汰一些数据,淘汰策略如下

  1. noeviction:不淘汰任何 key,当内存不足时拒绝写入新数据,这是默认策略
  2. volatile-ttl:对设置了 ttl 的 key,比较 ttl 的值,ttl 值越小越先被淘汰
  3. allkeys-random:对所有的 key 进行随机淘汰
  4. volatile-random:对设置了 ttl 的 key 进行随机淘汰
  5. allkeys-lru:对所有的 key 进行 LRU 算法淘汰
  6. volatile-lru:对设置了 ttl 的 key 进行 LRU 算法淘汰
  7. allkeys-lfu:对所有的 key 进行 LFU 算法淘汰
  8. volatile-lfu:对设置了 ttl 的 key 进行 LFU 算法淘汰

LRU(least recently used):最近最少使用。当前时间减去最近访问时间,这个值越大越优先被淘汰

LFU(least frequently used):最少频率使用。redis 会统计每个 key 的访问频率,最越少越优先被淘汰

数据同步

  1. 设置有效期:给缓存设置 ttl,到期后自动删除,下一次查询时,缓存为空,再把数据库的数据加入缓存
    • 优点:简单方便
    • 缺点:时效性差,当缓存过期前数据库发生了更新,导致数据库和缓存不一致
  2. 同步双写:在有效期的前提下,如果修改数据库,同步修改缓存(写进一个事务中,要么都成功,要么都失败)
    • 优点:时效性强,缓存和数据库一致
    • 缺点:代码侵入
  3. 异步通知:在有效期的前提下,当数据库发生更新,不是同步修改缓存,而是发送一个消息,通知缓存修改,只管发消息,具体什么时候修改缓存不用管
    • 优点:保证了数据一致性,代码侵入也降低了
    • 缺点:时效性一般,因为不清楚什么时候消费者接收到消息,最终一致,但是不是实时一致
  4. 使用中间件,比如 canel,canel 会监听 mysql 的 binlog ,当发现数据变化可以做自定义的业务处理
    • 基于 mysql 的主从同步,canel 伪装成一个从节点,读取主节点的 binlog 从而具有知晓数据变化的能力
    • 前提必须 mysql 是主从模式,如果 mysql 是单台服务就不能使用 canel 了

BigKey

  • 怎么定义是大 key?

    1. key 本身数据量过大,比如 String 的 key,value 达到 10k

    2. key 中成员数过多,比如 list 或 set,成员数超过 1000 个

    3. key 的成员数据量过大,比如 hash 的 key,可能成员数不大,但是所有的字段的 value 总和达到 10m

  • 怎么查找大 key?

    1. MEMORY USAGE key 查看一个 key 占用了多少字节(但是这个不推荐使用,占用 cpu 会比较高)
    2. 如果是字符串,可以直接使用 STRLEN key 查看 value 的长度(推荐,但只能查一个)
    3. 如果是集合,可以使用 LLEN key 查看集合的成员个数(推荐,但只能查一个)
    4. redis-cli --bigkeys 命令查找每种数据类型占用内存最大的一个 key(不推荐,只能查每种类型占用最大的一个)
    5. SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] (推荐方式)
    6. 利用第三方工具,比如 Redis_Rdb_Tools 分析 RDB 快照文件(推荐,不会影响 redis)
  • BigKey 带来什么问题?

    1. 网络阻塞:比如一个 key 达到了 10m,只需要少量的 QPS 就可能导致网络带宽被占满
    2. 数据倾斜:集群环境下,数据分片不均匀,多个节点,同为 1000 个 key,可能某个节点的 key 全都是 BigKey
    3. Redis 阻塞:当一个集合的成员过多,要进行排序等运算,这是很耗时的
    4. CPU 压力:对 key 的读取和写入要进行序列化,当大 key 过多时,导致 CPU 飙升
  • 怎么处理大 Key?

    目的是要删除大 key,重新组成多个小 的 key,不能直接使用 del 删除,当集合出现大 key 时,del 同步删除会阻塞 redis

    1. 如果是 4.0 及之上的版本,可以使用 unlink 命令异步删除大 key
    2. 如果是 4.0 以下版本,一个成员一个成员的删除

缓存穿透(数据本就不存在)

数据本身就不存在,比如查询 id = -1 的用户,缓存中肯定没有,数据库中同样没有,每次查询还是会落在数据库上(黑客恶意攻击)

解决方案:1,接口层处理,不合理的数据直接拦截,不到数据库;2,没有的数据也加入缓存,ttl 设置短点

缓存雪崩(数据库存在、缓存不存在)

还有个概念时缓存击穿,缓存击穿和缓存雪崩都是 数据库存在但是缓存不存在 的场景,大量请求最终查询数据库把数据库搞崩了

原因可能是热点数据过期(缓存击穿)、大批量缓存同时过期(缓存雪崩)

解决方案:1,如果是缓存击穿,可以设置热点数据永不过期;2,如果时缓存雪崩,过期时间不要太集中,在 ttl 加一点随机时间来分散过期

数据一致性

上面数据同步略带提过,单纯的同步双写在多线程场景中也是有问题的,

a 线程同步双写的操作有三步:更新数据库、删除缓存、添加缓存

b 线程在 a 线程删除缓存时,也更新了缓存,流程结束。这是 a 线程添加缓存,覆盖了 b 线程的数据

b 线程后来,b 线程是最新的数据,但是 b 比 a 先走完,所以 a 的值覆盖了 b 的值

解决方案:延时删除,所有线程更新数据库后,删除缓存,不添加缓存,隔几秒再次删除缓存。等下一次查询的时候再加入缓存

为什么要隔几秒?就是等业务做完再次删除,保证每次业务完都删除缓存,这样就能在下一次查询的时候加入缓存时的数据是最新的

posted @   CyrusHuang  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示