Redis入门(三)
发布日期:2022-03-04 11:48:23 浏览次数:4 分类:技术文章

本文共 11168 字,大约阅读时间需要 37 分钟。

目录


第八章 Redis与SpringBoot整合

8.1 整合步骤

8.1.1 加入依赖

   在pom.xml文件中引入redis相关依赖:

<!-- redis -->

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.6.0</version>
</dependency>

8.1.2 配置文件中配置redis

#Redis服务器地址spring.redis.host=192.168.140.136#Redis服务器连接端口spring.redis.port=6379#Redis服务器登陆密码spring.redis.password=xxxxxx#Redis数据库索引(默认为0)spring.redis.database= 0#连接超时时间(毫秒)spring.redis.timeout=1800000#连接池最大连接数(使用负值表示没有限制)spring.redis.lettuce.pool.max-active=20#最大阻塞等待时间(负数表示没限制)spring.redis.lettuce.pool.max-wait=-1#连接池中的最大空闲连接spring.redis.lettuce.pool.max-idle=5#连接池中的最小空闲连接spring.redis.lettuce.pool.min-idle=0

8.1.3 添加redis配置类

@EnableCaching@Configurationpublic class RedisConfig extends CachingConfigurerSupport {    @Bean    public RedisTemplate
redisTemplate(RedisConnectionFactory factory) { RedisTemplate
template = new RedisTemplate<>(); RedisSerializer
redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer
redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; }}

第九章 Redis事务和锁机制

9.1 Redis的事务定义

   Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行过程中,不会被其他客户端发送来的命令请求所打断。

   Redis事务的主要作用就是串联多个命令防止别的命令插队

9.2 Multi、Exec、discard

   从输入Multi命令开始,输入的命令都会依次进入命令队列(先进先出)中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

   组队的过程中可以通过discard来放弃组队。

        组队成功,提交成功:

        放弃组队:

9.3 事务的错误处理

   组队中某个命令出现了报告错误,执行时整个的所有命令队列都会被取消。

   如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

        组队阶段报错,提交失败:

        组队成功,提交阶段有失败:

9.4 事务冲突

9.4.1 冲突实例

   假设很多人有同一个账户,并且同时去购物。一个请求想给金额减8000,一个请求想给金额减5000,一个请求想给金额减1000:

9.4.2 悲观锁

   悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到整个数据就会block阻塞,直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁、读写锁等,都是在做操作之前先上锁

9.4.3 乐观锁

   乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为被人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号(类似CAS操作)等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check and set机制实现事务的

9.4.5 watch/unwatch

   在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。

        在第一个redis中,加10:

        在第二个redis中,加20,由于乐观锁的存在,一开始的100和执行事务前查看的110不一致,

      因此操作失败:

   unwatch:取消watch命令对所有key的监视。如果在执行watch命令之后,exec命令或discard命令先被执行了的话,那么就不需要再执行unwatch了。

9.5 Redis事务三特性

  • 单独的隔离操作
    • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念
    • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
  • 不保证原子性
    • 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

9.6 秒杀案例的问题

9.6.1 连接超时

   多个请求要连接redis,可能产生等待,等待时间超过了限制,就产生了连接超时现象,通过Jedis连接池来解决。

9.6.2 超卖问题

   假设有一个秒杀案例,许多请求同时抵达,如果不使用事务,会出现超卖的问题,多个请求同时抵达但是都可以进行买的操作,因为他们读到的库存还是有余量的。

   此时针对超卖问题,可以使用乐观锁加事务进行解决。通过比较前后的版本号是否相同来判断是否能执行这一次的秒杀操作,如果不一致,操作就失败。

   但是这种解决又产生了一种新的问题,就是库存遗留问题,因为可能很多请求都前后版本不一致导致秒杀操作失败,但是库存其实时有余量的。

9.6.3 库存遗留问题

   使用LUA脚本来解决库存遗留问题。Lua不适合作为开发独立应用程序的语言,但是可以作为嵌入式脚本语言

   LUA脚本在Redis中的优势:

        ①将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis

          的次数。提升性能。

        ②LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队(解决了乐观锁的问题),

          可以完成一些redis事务性的操作。

        ③但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

        ④redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis利用其单线程的特性,用任

          务队列的方式解决多任务并发问题。

第十章 Redis持久化

10.1 简介

  由于Redis是一个内存数据库,将数据库中的内容保存在内存中,这与传统的MySQL等关系型数据库直接将内容保存到硬盘中相比,内存数据库的读写效率比传统数据库要快得多(因为内存的读写效率远远大于硬盘的读写效率)。但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。为了解决这个缺点,Redis提供了将内存数据持久化到硬盘,以及用持久化文件来恢复数据库数据的功能。

   Redis提供了2个不同形式的持久化方式:

        ①RDB(Redis DataBase)

        ②AOF(Append Of File)

10.2 RDB

10.2.1 简介

   RDB是在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snaphto快照(数据库中所有键值对数据),它恢复时是将快照文件直接读到内存里。

10.2.2 工作原理

   Redis会单独创建(fork命令)一个子进程来进行持久化,会先将数据写道一个临时文件中(使用临时文件的原因是防止持久化过程中发生宕机,导致文件数据不完整,使用临时文件是为了保持数据的完整性),待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失

10.2.3 Fork

  • Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
  • 在Linux中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了写时复制技术
  • 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

10.2.4 dump.rdb文件

   在redis.conf中设置了rdb文件的默认名称为dump.rdb:

10.2.5 RDB文件存放位置

   路径默认添加在redis启动目录中:

10.2.6 stop-writes-on bgsave-error

   当Redis无法写入硬盘(如硬盘已满)的话,直接关掉Redis的写操作。推荐yes。

10.2.7 rdbcompression压缩文件

   对于存储到硬盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩;如果不像消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes。

10.2.8 rdbchecksum检查完整性

   在存储快照后,可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。推荐yes。

10.2.9 自动触发:配置文件中的save

   格式:save 秒钟 写操作次数

   RDB是整个内存的压缩过的Snaphot,RDB的数据结构,可以配置复合的快照触发条件。默认是1分钟内改了10000次,或5分钟内改了100次,或1小时内改了1次

   停止RDB持久化的方法:

        ①可以不设置save指令,注释掉

        ②给save传入空字符串save " "

        ③通过命令:redis-cli config set save " "

10.2.10 手动触发:命令save和bgsave

   save:save时只管保存,其他不管,全部阻塞。该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。

   bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

   基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令

   ps:执行执行 flushall 命令,也会产生dump.rdb文件,但里面是空的.

10.2.11 RDB的备份(恢复)

   先通过config get dir命令查询rgb文件的目录。然后将*.rgb的文件拷贝到别的地方。

   恢复:关闭redis,先把备份的文件拷贝到工作目录下,将文件名重新改为dump.rdb(命令cp dump2.rgb dump.rdb),此时启动redis,备份数据会直接加载。

   Redis服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

10.2.12 RDB的特点

   优势:

        ①适合大规模的数据恢复

        ②对数据完整性和一致性要求不高更适合使用

        ③节省硬盘空间

        ④恢复速度快

   劣势:

        ①Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

        ②虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。

        ③在备份周期(一定间隔时间)做一次备份,所以如果redis意外down掉的话,就会丢失最后一

          次快照后的所有修改

10.2 13 RDB自动保存的原理

   参考:

   Redis有个服务器状态结构:

struct redisService{     //1、记录保存save条件的数组     struct saveparam *saveparams;     //2、修改计数器     long long dirty;     //3、上一次执行保存的时间     time_t lastsave;}

        ①首先看记录保存条件的数组saveparams,里面每个元素都是一个saveparams结构:

struct saveparam{     //秒数     time_t seconds;     //修改数     int changes;};

        前面在redis.conf配置文件中进行了关于save的配置,假设当前的配置如下:

save 900 1:表示900秒内如果至少有1个key的值变化,则保存

save 300 10:表示300秒内如果至少有10个key的值变化,则保存

save 60 10000:表示60秒内如果至少有10000个key的值变化,则保存

        那么服务器状态中的saveparam 数组将会是如下的样子:

        ②dirty 计数器和lastsave 属性:dirty 计数器记录距离上一次成功执行 save 命令或者 bgsave

          命令之后,Redis服务器进行了多少次修改(包括写入、删除、更新等操作)。lastsave 属

          性是一个时间戳,记录上一次成功执行 save 命令或者 bgsave 命令的时间

            通过这两个命令,当服务器成功执行一次修改操作,那么dirty 计数器就会加 1,而lastsave

          属性记录上一次执行save或bgsave的时间,Redis 服务器还有一个周期性操作函数

          severCron ,默认每隔 100 毫秒就会执行一次,该函数会遍历并检查 saveparams 数组中的

          所有保存条件,只要有一个条件被满足,那么就会执行bgsave命令。执行完成之后,dirty

          计数器更新为 0 ,lastsave 也更新为执行命令的完成时间。

10.3 AOF

10.3.1 简介

   以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis追加之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

10.3.2 AOF持久化流程

   ①客户端的请求写命令会被append追加到AOF缓冲区内;

   ②AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;

   ③AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;

   ④Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的。

10.3.3 AOF开启

   AOF是默认不开启的。

   如何开启AOF?

        在redis.conf中配置文件中,将appendonly修改为yes。文件名称默认为appendonly.aof。

      AOF文件的保存路径同RDB的路径一致

   注意:如果AOF和RDB同时开启,系统默认优先取AOF的数据(数据不会丢失)

10.3.4 AOF恢复/修复

   AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。

   正常恢复

        ①修改默认的appendonly no,改为yes

        ②将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)

        ③恢复:重启redis然后重新加载

   异常恢复

        ①修改默认的appendonly no,改为yes

        ②如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof --fix appendonly.aof进行恢复

        ③备份被写坏的AOF文件

        ④恢复:重启redis,然后重新加载

10.3.5 AOF同步频率设置

   appendfsync always:始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好。

   appendfsync everysec每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。

   appendfsync no:redis不主动进行同步,把同步时机交给操作系统

10.3.6 Rewrite压缩重写

   参考:

   由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令 bgrewriteaof 来重新。

   AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件

   AOF 文件重写触发机制:通过 redis.conf 配置文件中的 auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite-min-size:64mb 配置,也就是说默认Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

        no-appendfsync-on-rewrite:在aof重写或者写入rdb文件的时候,会执行大量IO,此时对

          于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-

          rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,

          否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间

          对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入

        auto-aof-rewrite-percentage默认值为100。aof自动重写配置,当目前aof文件大小超过

          上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,

          Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到

          AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。

        auto-aof-rewrite-min-size64mb。设置允许重写的最小aof文件大小,避免了达到约定百

          分比但尺寸仍然很小的情况还要重写。

   Redis是单线程工作,如果重写AOF需要比较长的时间,那么在重写AOF期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将AOF重写程序放到子进程中进行,这样有两个好处:

        ①子进程进行AOF重写期间,服务器进程(父进程)可以继续处理其他命令。

        ②子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保

          证数据的安全性。

   使用子进程解决了上面的问题,但是新问题也产生了:因为子进程在进行AOF重写期间,服务器进程依然在处理其它命令,这新的命令有可能也对数据库进行了修改操作,使得当前数据库状态和重写后的AOF文件状态不一致。为了解决这个数据状态不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到AOF重写缓冲区。当子进程完成AOF重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将AOF重写缓冲区的内容都写到新的AOF文件中。

   重写流程

        ①bgrewriteaof触发重写,判断是否当前有bgsavebgrewriteaof在运行,如果有,则等待该

          命令结束后再继续执行;

        ②主进程fork出子进程执行重写操作,保证主进程不会阻塞;

        ③子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和

          aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动

          作不会丢失;

        ④子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息;主进程把

          aof_rewrite_buf中的数据写入到新的AOF文件。

        ⑤使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

10.3.7 AOF的特点

   优势:

        ①备份机制更稳健,丢失数据概率更低

        ②可读的日志文本,通过操作AOF文件,可以处理误操作

   劣势:

        ①比起RDB占用更多的硬盘空间

        ②恢复备份速度要慢

        ③每次读写都同步的话,有一定的性能压力

        ④存在个别Bug,造成恢复不能

10.4 RDB-AOF混合持久化

   在Redis4.0之后,继RDB和AOF两种持久化方式,又新增了RDB-AOF混合持久化方式。这种方式结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据。

   具体配置为:aof-use-rdb-preamble。设置为yes表示开启,设置为no表示禁用。

   当开启混合持久化时,主进程先fork出子进程将现有内存副本全部以RDB方式写入aof文件中,然后将缓冲区中的增量命令以AOF方式写入aof文件中,写入完成后通知主进程更新相关信息,并将新的含有 RDB和AOF两种格式的aof文件替换旧的aof文件。

   简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式

   但是不能兼容Redis4.0之前版本的备份文件了。

PS:根据尚硅谷课程整理,如有侵权,联系删除。

转载地址:https://blog.csdn.net/xxyneymar/article/details/122431894 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:力扣题904水果成篮
下一篇:Git入门

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年03月30日 06时04分16秒