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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容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
目录
相关文章
|
2月前
|
存储 缓存 数据库
解决缓存与数据库的数据一致性问题的终极指南
解决缓存与数据库的数据一致性问题的终极指南
187 63
|
19天前
|
缓存 NoSQL 数据库
缓存穿透、缓存击穿和缓存雪崩及其解决方案
在现代应用中,缓存是提升性能的关键技术之一。然而,缓存系统也可能遇到一系列问题,如缓存穿透、缓存击穿和缓存雪崩。这些问题可能导致数据库压力过大,甚至系统崩溃。本文将探讨这些问题及其解决方案。
|
27天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
38 5
|
1月前
|
缓存 NoSQL 关系型数据库
mysql和缓存一致性问题
本文介绍了五种常见的MySQL与Redis数据同步方法:1. 双写一致性,2. 延迟双删策略,3. 订阅发布模式(使用消息队列),4. 基于事件的缓存更新,5. 缓存预热。每种方法的实现步骤、优缺点均有详细说明。
|
2月前
|
缓存 监控 算法
小米面试题:多级缓存一致性问题怎么解决
【10月更文挑战第23天】在现代分布式系统中,多级缓存架构因其能够显著提高系统性能和响应速度而被广泛应用。
60 3
|
2月前
|
消息中间件 缓存 中间件
缓存一致性问题,这么回答肯定没毛病!
缓存一致性问题,这么回答肯定没毛病!
|
2月前
|
存储 缓存 API
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
47 6
|
2月前
|
应用服务中间件 PHP Apache
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
|
3月前
|
消息中间件 缓存 NoSQL
15)如何保证缓存和数据库之间的数据一致性
15)如何保证缓存和数据库之间的数据一致性
67 1
|
2月前
|
机器学习/深度学习 存储 自然语言处理
LangChain-22 Text Embedding 续接21节 文本切分后 对文本进行embedding向量化处理 后续可保存到向量数据库后进行检索 从而扩展大模型的能力
LangChain-22 Text Embedding 续接21节 文本切分后 对文本进行embedding向量化处理 后续可保存到向量数据库后进行检索 从而扩展大模型的能力
52 0