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

本文涉及的产品
云数据库 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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
存储 SQL 关系型数据库
TiDB的优势:为何选择TiDB作为您的数据库解决方案
【2月更文挑战第25天】随着数据规模的不断增长和业务需求的日益复杂化,现代企业对数据库系统的扩展性、高可用以及分布式处理能力提出了更高的要求。TiDB作为一个新型的开源分布式数据库,以其独特的设计理念与卓越的技术特性,在众多数据库解决方案中脱颖而出。本文将深入剖析TiDB的核心优势,探讨其如何帮助企业从容应对海量数据挑战、实现无缝水平扩展、保障服务高可用性,并提供灵活一致的事务支持。
|
1月前
|
SQL 关系型数据库 数据库
事务隔离级别:保障数据库并发事务的一致性与性能
事务隔离级别:保障数据库并发事务的一致性与性能
|
1月前
|
Oracle 关系型数据库 分布式数据库
分布式数据库集成解决方案
分布式数据库集成解决方案
204 0
|
1天前
|
缓存 NoSQL Redis
深度解析Redis的缓存双写一致性
【4月更文挑战第20天】
10 1
|
2天前
|
消息中间件 缓存 数据库
如何保证缓存与数据库的数据一致性?
如何保证缓存与数据库的数据一致性?
17 5
|
2天前
|
缓存 NoSQL 关系型数据库
在Python Web开发过程中:数据库与缓存,MySQL和NoSQL数据库的主要差异是什么?
MySQL与NoSQL的主要区别在于数据结构、查询语言和可扩展性。MySQL是关系型数据库,依赖预定义的数据表结构,使用SQL进行复杂查询,适合垂直扩展。而NoSQL提供灵活的存储方式(如JSON、哈希表),无统一查询语言,支持横向扩展,适用于处理大规模、非结构化数据和高并发场景。选择哪种取决于应用需求、数据模型及扩展策略。
10 0
|
29天前
|
缓存 应用服务中间件 数据库
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
33 1
|
1月前
|
缓存 监控 安全
宝塔数据库崩溃解决方案详解
宝塔数据库崩溃解决方案详解
|
1月前
|
存储 缓存 NoSQL
[Redis]——缓存击穿和缓存穿透及解决方案(图解+代码+解释)
[Redis]——缓存击穿和缓存穿透及解决方案(图解+代码+解释)
264 0
|
1月前
|
缓存 NoSQL 数据库
[Redis]——数据一致性,先操作数据库,还是先更新缓存?
[Redis]——数据一致性,先操作数据库,还是先更新缓存?

热门文章

最新文章