聊一聊缓存和数据库不一致性问题的产生及主流解决方案以及扩展的思考2

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 聊一聊缓存和数据库不一致性问题的产生及主流解决方案以及扩展的思考

聊一聊缓存和数据库不一致性问题的产生及主流解决方案以及扩展的思考1:https://developer.aliyun.com/article/1394667

2.3、Redis的序列化机制

Redis 的默认序列化机制是JDK序列化机制,这点可以在下图的源码中看出。

image.png

从源码中也可以看出,如果没有设置序列化机制,则defaulatSeralizer = new JdkSerializationRedisSerializer() ,可以明显看出,使用的就是JDK 的序列化机制。

JDK默认序列化机制并非不能使用,只是它具有一定的局限性

  • 它只适用于Java项目,对其他语言编写的项目不兼容,如Go或者PHP
  • 在Redis的可视化页面,无法进行较好的展示

我们需要将 Redis 的默认序列化机制改为JSON格式,一方面兼容性较高,另一方面在可视化界面也较好查看。

在修改之前,先看一眼默认的JdkSerializationRedisSerializer类吧。

public class JdkSerializationRedisSerializer implements RedisSerializer

我们可以看到它也是实现了RedisSerializer接口,点进接口去,在IDEA中按下ctrl+H可以查看整个类从上至下的树结构。

我们来可以看看它的实现类有哪些,有没有已经实现JSON相关的序列化机制的实现类。

image.png

从接口实现树上可以看到,有三个可以直接转换为JSON序列化的实现类

  • GenericJackson2JsonRedisSerializer
  • FastJsonRedisSerializer
  • Jackson2JsonRedisSerializer

这三者都是可以的,具体的区别,用法搜索或者看一下源码就会大概懂的使用了~,不是我的讨论重点。

我这里直接使用的是Jackson2JsonRedisSerializer,作用就是序列化object对象为json字符串。

2.4、更改Redis的默认序列化机制

那么如何更改勒?

在之前也已经看到在RedisAutoConfiguration中已经帮我们注入了RedisTemplete和StringTemplete,我们现在要更改他们的设置,所以就改为手动注入。

RedisTemplete中的afterPropertiesSet方法中可以看到当RedisSerializer defaultSerializer;当为 null 时,默认使用JdkSerializationRedisSerializer的序列机制。

那么更改就很简单啦~

创建一个 MyRedisConfig 配置类,将RedisTemplete和StringTemplete改为手动注入,在注入RedisTemplete时,手动set一个Jackson2JsonRedisSerializer类即可。

 /**
  * @description:
  * @author: Ning Zaichun
  * @date: 2022年09月06日 23:21
  */
 @Configuration
 public class MyRedisConfig {
 ​
     @Bean
     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
         RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
 ​
 ​
         Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer(Object.class);
 ​
         ObjectMapper objectMapper = new ObjectMapper();
         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
 ​
         serializer.setObjectMapper(objectMapper);
 ​
         //设置value 值的序列化
         redisTemplate.setValueSerializer(serializer);
         //key的序列化
         redisTemplate.setKeySerializer(new StringRedisSerializer());
 ​
         // set hash  hashkey 值的序列化
         redisTemplate.setHashKeySerializer(new StringRedisSerializer());
         // set hash value 值的序列化
         redisTemplate.setHashValueSerializer(serializer);
 ​
         redisTemplate.setConnectionFactory(redisConnectionFactory);
         redisTemplate.afterPropertiesSet();
         return redisTemplate;
     }
 ​
     @Bean
     public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
         return new StringRedisTemplate(redisConnectionFactory);
     }
 }
 ​

测试: 还是之前一样的代码

 /**
     * 虽然大家可以在Java 程序中看到取出来的值是正常的,
 * 但是在平时开发和测试的时候,我们还是需要借助 Redis 的可视化工具来查看的,
 * 你会发现,我们采用默认的序列化机制(JDK序列化机制),在Redis可视化软件中,会无法直接查看的,
 * 都是转码之后的数据: \xac\xed\x00\x05t\x00\x06user:2
     * 图片见下面第二张图
     */
     @Test
     public void test8() {
     Map<String, Student> map = new HashMap<>();
     Student s1 = new Student();
     s1.setSchool("xxxx1");
     s1.setUsername("ningzaichun1号");
     s1.setAge(3);
     Student s2 = new Student();
     s2.setSchool("xxxx2");
     s2.setUsername("ningzaichun2号");
     s2.setAge(5);
     map.put("user:1",s1);
     map.put("user:2",s2);
     redisTemplate.opsForHash().putAll("student:key",map);
 ​
     List<Object> values = redisTemplate.opsForHash().values("student:key");
     values.forEach(System.out::println);
     //Student(username=ningzaichun1号, school=xxxx1, age=3)
     //Student(username=ningzaichun2号, school=xxxx2, age=5)
 }

效果图:

image.png

2.5、RedisConnectionFactory

想了想,都写到这里来了,还是说说RedisConnectionFactory(Redis连接工厂)吧。

SpringBoot 对于 Redis 连接工厂的实现有两个,一个是SpringBoot 2.0 默认使用的`LettuceConnectionFactory,另一个是早期就出现的JedisConnectionFactory

首先得说明,两者是有区别的。

Jedis

  • Jedis 是一个优秀的基于 Java 语言的 Redis 客户端
  • Jedis 在实现上是直接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景下使用 Jedis,需要使用连接池,每个线程都使用自己的 Jedis 实例,当连接数量增多时,会消耗较多的物理资源。

Lettuce

  • SpringBoot 2.0 及之后版本 Redis 的默认连接工厂
  • Lettuce则完全克服了其线程不安全的缺点;
  • Lettuce是一个可伸缩的线程安全的 Redis客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。
  • 它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型。

一些关于 Lettuce的使用,大家可以看看这篇文章

初探 Redis 客户端 Lettuce:真香! 作者: 博客园-vivo互联网技术

三、缓存的三大面试常客

3.1、缓存穿透

缓存穿透是指用户在不断访问一个缓存和数据库中都没有的数据

缓存无法命中,从而导致一直请求数据库,流量过大就会导致数据库的崩溃.

如发起为id为 -1 的数据或id为特别大不存在的数据,这时的用户往往可能是恶意攻击者,这种恶意攻击会导致数据库压力非常大,扛不住的结果就是宕机。

image.png

解决方案:

1、缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也没有,如果我们不存储这个空数据,那么持续的访问就会导致我们的数据库压力倍增,此时我们就可以将空结果(null)存入到缓存中并且设置一个较短的过期时间

2、接口层增加校验,如用户鉴权校验,编写一些特殊数据的校验,预防这样的事故的发生。如将id<=0的查询请求直接拒绝掉。

image.png

3.2、缓存雪崩

Redis挂掉了,请求全部走数据库

如一个系统,每天的高峰期是每秒5000个请求,Redis缓存在的时候,可以差不多抗住,但是Redis的机器突然网络出问题了,完全访问不了。

那么此时每秒5000的请求都会直接打到数据库上,数据扛住了没啥事,扛不住了就是GG啦。

有哪些方案可以来预防和处理这样的故障呢?

image.png

从三个角度讨论:

1、部署角度:实现 Redis 的高可用,主从+哨兵,Redis集群。

2、应用程序角度:

  • 本地缓存 + 限流&降级
  • 允许的话,设置热点数据永远不过期
  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
    (但实际而言,这一点很多时候其实不做的,因为如果加上随机时间后,再碰撞又该如何呢?)

3、恢复角度:Redis 的 RDB+AOF组合持久化策略,方便redis宕机后及时恢复数据

image.png

浅谈一下限流和降级的好处:

  • 限流会限制最高的访问人数,保证系统的正常运行,不会崩溃。
  • 服务降级,会返回用户一些合适的提示信息,对于用户而言,刷新个四五次还是有可能访问成功的。
  • 都可以保证系统的运行,不至于让系统崩溃。

3.3、缓存击穿

缓存击穿和缓存穿透其实非常类似,但是缓存击穿说的是数据在缓存中没有,但是在数据库中有的数据。

看到过的面试题中,最常举例的场景就是:

Redis 中大某一个热点key在突然失效,并且此时正处于高并发期间,导致流量全部打到数据库上,造成数据库极大的压力。我们通常将这样的事件称之为缓存击穿

image.png

其实读懂问题,解决方案也很好说明:

  1. 设置热点数据不过期;
  2. 第一时间去数据库获取数据填充到redis中,并且在请求数据库时需要加锁,避免所有的请求都直接访问数据库,一旦有一个请求正确查询到数据库时,将结果存入缓存中,其他的线程获取锁失败后,让其睡眠一会,之后重新尝试查询缓存,获取成功,直接返回结果;获取失败,则再次尝试获取锁,重复上述流程。

流程图:大致如下

image.png

大致流程就是这样的~

聊一聊缓存和数据库不一致性问题的产生及主流解决方案以及扩展的思考3:https://developer.aliyun.com/article/1394672

目录
相关文章
|
2月前
|
运维 监控 关系型数据库
AI 时代的 MySQL 数据库运维解决方案
本文探讨了大模型与MySQL数据库运维结合所带来的变革,介绍了构建结构化运维知识库、选择合适的大模型、设计Prompt调用策略、开发MCP Server以及建立监控优化闭环等关键步骤。通过将自然语言处理能力与数据库运维相结合,实现了故障智能诊断、SQL自动优化等功能,显著提升了MySQL运维效率和准确性。
320 18
|
4月前
|
关系型数据库 数据库 RDS
【瑶池数据库训练营及解决方案本周精选(探索PolarDB,参与RDS迁移、连接训练营)】(5.30-6.8)
本周精选聚焦数据库迁移训练营、快速连接云数据库RDS训练营及智能多模态搜索解决方案。为用户提供模拟教程与实战演练,学习RDS MySQL实例连接与数据管理技能,助力企业智能化发展。每周解锁数据库实战新场景,抓紧时间,精彩不容错过!
|
6月前
|
Cloud Native 关系型数据库 分布式数据库
登顶TPC-C|云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
阿里云PolarDB云原生数据库在TPC-C基准测试中以20.55亿tpmC的成绩刷新世界纪录,展现卓越性能与性价比。其轻量版满足国产化需求,兼具高性能与低成本,适用于多种场景,推动数据库技术革新与发展。
|
4月前
|
Cloud Native 关系型数据库 分布式数据库
阿里云PolarDB与沃趣科技携手打造一体化数据库解决方案,助推国产数据库生态发展
阿里云瑶池数据库与沃趣科技将继续深化合作,共同推动国产数据库技术的持续创新与广泛应用,为行业生态的繁荣注入更强劲的技术动力。
阿里云PolarDB与沃趣科技携手打造一体化数据库解决方案,助推国产数据库生态发展
|
3月前
|
运维 监控 关系型数据库
AI 时代的 MySQL 数据库运维解决方案
本方案将大模型与MySQL运维深度融合,构建智能诊断、SQL优化与知识更新的自动化系统。通过知识库建设、大模型调用策略、MCP Server开发及监控闭环设计,全面提升数据库运维效率与准确性,实现从人工经验到智能决策的跃迁。
430 26
|
2月前
|
SQL 安全 关系型数据库
数据库安全管理新范式:DBKEEPER一体化数据库权限管控堡垒机解决方案
在数字化时代,数据库安全至关重要。DBKEEPER提供一站式数据库安全访问与权限管控解决方案,支持多种数据库,具备精细化权限管理、数据脱敏、高危操作拦截、全面审计等功能,助力企业实现智能、安全的数据治理,满足金融、医疗、互联网等行业合规需求。选择DBKEEPER,让数据库安全管理更高效!
数据库安全管理新范式:DBKEEPER一体化数据库权限管控堡垒机解决方案
|
6月前
|
关系型数据库 MySQL 数据库连接
docker拉取MySQL后数据库连接失败解决方案
通过以上方法,可以解决Docker中拉取MySQL镜像后数据库连接失败的常见问题。关键步骤包括确保容器正确启动、配置正确的环境变量、合理设置网络和权限,以及检查主机防火墙设置等。通过逐步排查,可以快速定位并解决连接问题,确保MySQL服务的正常使用。
937 82
|
5月前
|
Cloud Native 关系型数据库 分布式数据库
登顶TPC-C|云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
|
5月前
|
负载均衡 算法 关系型数据库
大数据新视界--大数据大厂之MySQL数据库课程设计:MySQL集群架构负载均衡故障排除与解决方案
本文深入探讨 MySQL 集群架构负载均衡的常见故障及排除方法。涵盖请求分配不均、节点无法响应、负载均衡器故障等现象,介绍多种负载均衡算法及故障排除步骤,包括检查负载均衡器状态、调整算法、诊断修复节点故障等。还阐述了预防措施与确保系统稳定性的方法,如定期监控维护、备份恢复策略、团队协作与知识管理等。为确保 MySQL 数据库系统高可用性提供全面指导。
|
6月前
|
缓存 NoSQL 关系型数据库
WordPress数据库查询缓存插件
这款插件通过将MySQL查询结果缓存至文件、Redis或Memcached,加速页面加载。它专为未登录用户优化,支持跨页面缓存,不影响其他功能,且可与其他缓存插件兼容。相比传统页面缓存,它仅缓存数据库查询结果,保留动态功能如阅读量更新。提供三种缓存方式选择,有效提升网站性能。
106 1

热门文章

最新文章