当前位置: 首页 > news >正文

Redis与缓存

文章目录

    • Redis与缓存
      • 一致性问题
      • 大Key问题
      • 缓存穿透
      • 缓存击穿
      • 缓存雪崩

Redis与缓存

Redis作为缓存具有高性能、丰富的数据结构和灵活的过期机制等优点。由于Redis将数据存储在内存中,它能提供极低的延迟和高吞吐量,适合用于缓存数据库查询结果、会话数据和实时数据处理等场景。Redis的多种数据结构支持不同的缓存需求,如缓存静态内容、实现简单的消息队列,以及处理实时统计信息。

用户第一次访问数据库中的某些数据。整个过程会比较慢,因为是从硬盘上读取的。如果将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快,但随之而来的也会存在一些问题。

一致性问题

只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题。缓存一致性问题发生在缓存中的数据与源数据之间存在不一致的情况。这种不一致可能会导致系统中的数据错误或不准确。

解决一致性问题的关键在于,当源数据发生更改时,缓存中的数据也需要更新。

在更新源数据时,同时更新缓存和源数据库。这种方式保持了数据的一致性,因为所有写操作都会同时在缓存和数据库中完成。写操作的延迟可能增加,特别是在高负载情况下,更新操作可能成为性能瓶颈。适用于对数据一致性要求高的场景,例如金融系统和实时数据处理系统,其中一致性比性能更重要。

另一种方法是异步更新,先更新缓存,再通过后台任务异步更新源数据库。这种方式提高了写操作的性能,因为数据库更新是异步进行的,减少了写入延迟。缓存的写入速度较快,有助于提升用户体验。但数据库和缓存之间可能出现最终一致性问题,数据库更新的延迟可能导致缓存数据与源数据库不一致。适用于数据一致性要求可以容忍一定延迟的场景,如在线购物网站和社交媒体平台,其中用户体验和性能优于即时一致性。

缓存失效策略在读取数据时,如果缓存中不存在数据或数据已过期,会从源数据库加载数据并更新缓存。这种方式通过重新加载来保持数据的一致性。缓存失效或数据过期时,总是从源数据库中获取最新数据,避免了缓存和数据库间的数据不一致问题。读取延迟可能增加,特别是缓存频繁失效时,且频繁的数据库访问可能增加负载。适用于读取操作较多的场景,例如内容分发网络或新闻网站,通过缓存失效平衡数据一致性和性能。

当缓存或数据库更新失败时,也会导致数据不一致的问题。为了解决这个问题,可以采取几种方法。可以设置自动重试机制,如果更新操作失败,系统会尝试重新执行更新,增加成功的可能性,虽然这样可能会增加系统的负担。另一种方法是使用备用缓存,在主缓存更新失败时,将数据写入备用缓存,并在主缓存恢复正常时进行同步,这样可以保持系统的正常运转。记录更新失败的情况,并触发报警,也能帮助快速发现问题,防止问题长时间存在。还可以将缓存更新操作放在后台任务中,并设置补偿机制来修复数据不一致。如果后台任务失败,补偿机制会尝试重新同步数据。

大Key问题

所谓的大Key问题是指某个Keyvalue比较大,所以本质上是大value问题。因为Key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。大Key占用的内存非常多,可能导致Redis实例的内存使用量急剧增加。大Key的读取、写入或删除可能会显著拖慢Redis的性能。例如,操作一个非常大的列表会占用大量的CPU和IO资源,导致其他操作的响应时间变慢。

Key问题一般是由于业务方案设计不合理,没有预见value的动态增长问题产生的。一直往value塞数据,没有删除机制,迟早要爆炸或数据没有合理做分片,将大Key变成小Key。在线上一般通过设置Redis监控,及时发现和处理大Key问题。可以使用工具监控键的大小,避免存储异常大的数据项。

解决思路:

  • 可在应用层或客户端设置最大键值大小限制,防止大Key被写入Redis
  • 定期检查Redis中的大Key,并进行必要的清理或优化操作。
  • 如果Redis中已经存在大Key,根据大Key的实际用途可以分为可删除和不可删除,如果发现某些大Key并非热Key就可以在DB中查询使用,则可以在Redis中删掉。
    如果不可删除,则需要拆分大Key,将大Key拆分成多个小Key,然后进行删除。

缓存穿透

缓存穿透是指查询请求绕过缓存直接访问数据库,通常是因为请求中的数据在缓存中不存在。这个问题可能导致缓存失效,增加数据库负担,并影响系统性能。缓存穿透的原因通常是,用户请求的数据在缓存和数据库中都不存在,或者是请求的数据在缓存中未命中,直接查询数据库,并未将结果正确地缓存起来。

在这里插入图片描述

解决方法:

  • 布隆过滤器:布隆过滤器是一种空间高效的数据结构,用于判断某个元素是否在集合中。它可以减少对数据库的访问次数,通过在缓存层使用布隆过滤器,来快速判断请求的数据是否可能存在于数据库中。将可能存在的键加入布隆过滤器。在每次查询之前,先检查布隆过滤器。如果布隆过滤器显示数据不存在,则直接返回空值或错误,不访问数据库。
    public class BloomFilterExample {private BloomFilter<String> bloomFilter;private StringRedisTemplate redisTemplate;public BloomFilterExample(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;// Initialize Bloom Filter with an expected insertions and false positive probabilitythis.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 100000, 0.01);}public String getData(String key) {// Check if the key is in the Bloom Filterif (!bloomFilter.mightContain(key)) {return null; // Key definitely not in cache or DB}// Check cacheString value = redisTemplate.opsForValue().get(key);if (value != null) {return value;}// Load from DB (simulate)value = loadFromDatabase(key);// Cache the result and add to Bloom Filterif (value != null) {redisTemplate.opsForValue().set(key, value);bloomFilter.put(key);}return value;}private String loadFromDatabase(String key) {// Simulate DB accessreturn "DatabaseValueFor" + key;}
    }
    
  • 缓存空对象:当数据库查询返回空结果时,将空结果缓存到Redis中,使用一个特殊的标识,如空字符串、null、特定的空值对象等。后续相同的查询可以直接从缓存中获取空结果,避免再次访问数据库。设置空对象的缓存时间较短,避免长时间缓存无效数据。
    public class CacheEmptyObjectExample {private static final String EMPTY_OBJECT_PLACEHOLDER = "EMPTY";private StringRedisTemplate redisTemplate;public CacheEmptyObjectExample(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public String getData(String key) {// Check cacheString value = redisTemplate.opsForValue().get(key);if (EMPTY_OBJECT_PLACEHOLDER.equals(value)) {return null; // Data definitely does not exist}if (value != null) {return value;}// Load from DB (simulate)value = loadFromDatabase(key);if (value != null) {redisTemplate.opsForValue().set(key, value);} else {redisTemplate.opsForValue().set(key, EMPTY_OBJECT_PLACEHOLDER);}return value;}private String loadFromDatabase(String key) {// Simulate DB accessreturn null; // Simulate no data found}
    }
    

缓存击穿

缓存击穿 是指在缓存中某个热点数据的缓存失效时,多个请求同时访问数据库,导致数据库压力剧增的情况。这种问题通常发生在缓存数据过期或被删除时,如果请求大量集中在短时间内,可能会导致数据库负载急剧上升。

在这里插入图片描述

解决方法:

  • 加锁机制:在缓存失效时,对数据的访问进行加锁,保证只有一个请求能够从数据库中加载数据并更新缓存。其他请求需要等待锁释放后,才能获取缓存中的数据。
    public class CacheLockExample {private StringRedisTemplate redisTemplate;private RedisTemplate<String, Object> redisLockTemplate;private static final String LOCK_KEY_PREFIX = "lock:";public CacheLockExample(StringRedisTemplate redisTemplate, RedisTemplate<String, Object> redisLockTemplate) {this.redisTemplate = redisTemplate;this.redisLockTemplate = redisLockTemplate;}public String getData(String key) {String cacheKey = "cache:" + key;String lockKey = LOCK_KEY_PREFIX + key;// Check cacheString value = redisTemplate.opsForValue().get(cacheKey);if (value != null) {return value;}// Acquire lockBoolean lockAcquired = redisLockTemplate.opsForValue().setIfAbsent(lockKey, "locked");if (lockAcquired != null && lockAcquired) {try {// Load from DB (simulate)value = loadFromDatabase(key);// Cache the resultif (value != null) {redisTemplate.opsForValue().set(cacheKey, value);}} finally {// Release lockredisLockTemplate.delete(lockKey);}} else {// Wait for lock to be released and retrytry {Thread.sleep(100); // Wait for 100 milliseconds} catch (InterruptedException e) {Thread.currentThread().interrupt();}return redisTemplate.opsForValue().get(cacheKey);}return value;}private String loadFromDatabase(String key) {// Simulate DB accessreturn "DatabaseValueFor" + key;}
    }
    
  • 缓存预热:在缓存过期前,提前将热点数据加载到缓存中,减少缓存失效对数据库的冲击。这可以通过定期刷新缓存或使用缓存预热策略来实现。
    public class CachePreheatExample {private StringRedisTemplate redisTemplate;public CachePreheatExample(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public void preheatCache() {// Simulate loading all hotspot datafor (String key : getHotspotKeys()) {String value = loadFromDatabase(key);if (value != null) {redisTemplate.opsForValue().set("cache:" + key, value);}}}private Iterable<String> getHotspotKeys() {// Simulate getting all hotspot keysreturn List.of("key1", "key2", "key3");}private String loadFromDatabase(String key) {// Simulate DB accessreturn "DatabaseValueFor" + key;}
    }
    

缓存雪崩

缓存雪崩 是指当大量缓存同时失效或遭遇问题时,造成大量请求同时涌入数据库,导致数据库负载过重,从而引发服务不可用的情况。这种情况常见于缓存失效时间集中或缓存服务宕机等场景。

在这里插入图片描述

解决方法:

  • 缓存过期时间随机化:通过对缓存的过期时间进行随机化,避免所有缓存同时过期。根据业务需求设置合理的缓存过期时间。避免缓存过期时间设置过长或过短,导致缓存失效的集中现象。
    public class CacheRandomExpirationExample {private StringRedisTemplate redisTemplate;private static final int EXPIRATION_TIME = 600; // 10 minutespublic CacheRandomExpirationExample(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public String getData(String key) {String cacheKey = "cache:" + key;String value = redisTemplate.opsForValue().get(cacheKey);if (value != null) {return value;}// Load from DB (simulate)value = loadFromDatabase(key);if (value != null) {// Set cache with random expiration time between 10 and 20 minutesint expiration = EXPIRATION_TIME + (int) (Math.random() * 600);redisTemplate.opsForValue().set(cacheKey, value, expiration, TimeUnit.SECONDS);}return value;}private String loadFromDatabase(String key) {// Simulate DB accessreturn "DatabaseValueFor" + key;}
    }
    
  • 使用多级缓存:引入多级缓存策略,比如本地缓存和分布式缓存结合使用。这样即使分布式缓存失效,本地缓存仍能提供服务,减少对数据库的压力。
    public class MultiLevelCacheExample {private StringRedisTemplate redisTemplate;private Cache<String, String> localCache;public MultiLevelCacheExample(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.localCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();}public String getData(String key) {// Check local cache firstString value = localCache.getIfPresent(key);if (value != null) {return value;}String cacheKey = "cache:" + key;value = redisTemplate.opsForValue().get(cacheKey);if (value != null) {localCache.put(key, value);return value;}// Load from DB (simulate)value = loadFromDatabase(key);if (value != null) {redisTemplate.opsForValue().set(cacheKey, value, 10, TimeUnit.MINUTES);localCache.put(key, value);}return value;}private String loadFromDatabase(String key) {// Simulate DB accessreturn "DatabaseValueFor" + key;}
    }
    

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • go-kratos 学习笔记(8) redis的使用
  • OpenCV 图像基础
  • 养宠空气净化器哪家好?养宠空气净化器质量好的牌子推荐
  • Ubuntu一键导入openVPN配置文件
  • PHP多功能投票系统源码小程序
  • 用来跳转的<a> 标签,原来还有这么多强大又实用的功能
  • 通信原理-思科实验五:家庭终端以太网接入Internet实验
  • 征服 Docker 镜像访问限制:KubeSphere v3.4.1 成功部署全攻略
  • 【Redis进阶】集群
  • cf960(div2)
  • Gogs搭建免费好用的Git服务器
  • 力扣面试题(一)
  • 大语言模型系列——Transformer 介绍与使用
  • Dav_笔记11:SQL Tuning Overview-sql调优 之 3
  • springboot整合 knife4j 接口文档
  • 【Leetcode】104. 二叉树的最大深度
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • C学习-枚举(九)
  • eclipse(luna)创建web工程
  • ERLANG 网工修炼笔记 ---- UDP
  • gulp 教程
  • Meteor的表单提交:Form
  • MySQL的数据类型
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • ReactNative开发常用的三方模块
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 分类模型——Logistics Regression
  • 十年未变!安全,谁之责?(下)
  • 微服务入门【系列视频课程】
  • 由插件封装引出的一丢丢思考
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • # .NET Framework中使用命名管道进行进程间通信
  • #NOIP 2014# day.2 T2 寻找道路
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • (1)SpringCloud 整合Python
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (函数)颠倒字符串顺序(C语言)
  • (黑马C++)L06 重载与继承
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (四)鸿鹄云架构一服务注册中心
  • (一)插入排序
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)