Redis的持久化

Redis 的持久化是指将内存中的数据保存到硬盘上,以防止进程退出或者故障导致数据丢失。Redis 提供了两种持久化方式,分别是 RDB 和 AOF。

RDB 持久化是通过定期生成内存数据的快照文件来实现的。RDB 文件是一个二进制格式的文件,包含了某个时间点的全部数据。Redis 可以根据配置文件中的 save 参数来决定何时触发 RDB 持久化,也可以通过 save 或 bgsave 命令来手动触发。RDB 持久化的优点是文件体积小,恢复速度快,适合备份和传输。缺点是无法做到实时或者近实时的持久化,可能会丢失最后一次快照之后的数据。另外,RDB 持久化需要 fork 子进程来执行,这会占用一定的 CPU 和内存资源,可能会影响 Redis 的性能。

AOF 持久化是通过记录每一条修改数据的命令来实现的。AOF 文件是一个文本格式的文件,包含了所有执行过的写命令。Redis 可以根据配置文件中的 appendfsync 参数来决定何时将 AOF 缓冲区的内容同步到硬盘上,也可以通过 bgrewriteaof 命令来手动重写 AOF 文件。AOF 持久化的优点是可以提供更高的数据安全性,可以根据不同的策略来平衡性能和持久化频率。缺点是文件体积大,恢复速度慢,可能会出现命令重复或者不完整的情况。

Redis 可以同时使用 RDB 和 AOF 两种持久化方式,这样可以兼顾数据安全性和恢复效率。当 Redis 重启时,如果存在 AOF 文件,那么 Redis 会优先使用 AOF 文件来恢复数据,否则会使用 RDB 文件。

Redis的缓存穿透、缓存击穿、缓存雪崩

   缓存穿透   是指查询一个没有的数据时,Redis无法缓存数据导致,频繁的访问数据库,可能导致数据库挂掉。我们可以将反回的空结果在Redis中使用缓存,但是过期时间设置短点。或者对请求进行过滤。或者使用布隆过滤器。

   缓存击穿  是指设置过期时间的热点key刚好过期,而且刚好有大量对这key请求,导致大量访问请求数据库,导致数据库挂掉。处理方法可以对热点信息进行永不过期设置或较长时间设置。还可以对热点信息进行提前更新。还可以使用互斥锁,当缓存失效就让一个线程去更新key其他等待。

   缓存雪崩  是指大量缓存过期或者Redis宕机,导致大量请求访问数据库,导致数据库挂掉。解决方法可以对不同数据采用不同的缓存时间,搭建Redis集群,启用哨兵模式。

Redis的数据淘汰策略

  当Redis的内存使用超过配置的最大值时,就需要采用一些策略来释放内存空间,这就是数据淘汰策略。

Redis提供了以下八种数据淘汰策略:

noeviction:这是默认的策略,表示不淘汰任何数据,当内存不足时,只能执行读取或删除操作,其他写入操作会返回错误。

allkeys-lru:这是一种基于最近最少使用(LRU)算法的策略,表示从所有的键中选择最久未被访问的键进行淘汰。

volatile-lru:这是一种基于最近最少使用(LRU)算法的策略,表示从设置了过期时间的键中选择最久未被访问的键进行淘汰。

allkeys-random:这是一种基于随机算法的策略,表示从所有的键中随机选择一个键进行淘汰。

volatile-random:这是一种基于随机算法的策略,表示从设置了过期时间的键中随机选择一个键进行淘汰。

volatile-ttl:这是一种基于过期时间(TTL)算法的策略,表示从设置了过期时间的键中选择即将过期的键进行淘汰。

volatile-lfu:这是一种基于最不经常使用(LFU)算法的策略,表示从设置了过期时间的键中选择使用频率最低的键进行淘汰。

allkeys-lfu:这是一种基于最不经常使用(LFU)算法的策略,表示从所有的键中选择使用频率最低的键进行淘汰。

想要修改Redis的淘汰策略只需要修改Redis的配置文件redis.conf,找到maxmemory-policy这一项,并将其值改为您想要使用的策略名称。

Redis的集群,主从复制、哨兵模式等机制

Redis的集群机制:

Redis集群是一种分布式的解决方案,使用数据分片,主从复制,故障检测和转移,客户端重定向来实现。

数据分片:Redis集群将所有的数据划分为16384个哈希槽,每个键都属于其中一个槽,不同的槽可以分配给不同的节点,这样就可以将数据分散到多个节点上,提高存储容量和吞吐量。

主从复制:Redis集群中的每个节点都可以有一个或多个复制品,其中一个为主节点,负责处理读写请求,其他为从节点,负责复制主节点的数据。当主节点发生故障时,集群会自动将一个从节点升级为新的主节点,保证服务的可用性。

故障检测和转移:(哨兵模式)Redis集群中的所有节点都会定期发送心跳包和信息包给其他节点,以检测彼此的状态和配置。当某个节点被判定为下线或不可达时,集群会触发故障转移的流程,重新分配其负责的哈希槽和复制关系。

客户端重定向:当客户端向某个节点发送请求时,如果该节点不负责请求中键所属的哈希槽,那么该节点会返回一个MOVED或ASK错误给客户端,告诉客户端应该向哪个节点重试请求。客户端收到错误后会根据错误信息找到正确的目标节点,并重新发送请求。

Redis的主从复制实现步骤:

从节点申请请求复制,然后主节点接受后生成RDB文件,并且使用缓存区缓存将此后的所有写命令,主将RDB文件发送给从,从接受后更新,然后主将缓存区的写命令发送给从,从也执行,主从复制就完成了,之后的主每次执行写命令就发送给从更新,保持主从数据一致性。

Redis的哨兵模式:

哨兵可以监控Redis主节点和从节点的运行状态,并在主节点发生故障时自动进行故障转移,选举出一个新的主节点,并通知客户端和其他从节点。

        Redis哨兵是一个运行在特殊模式下的Redis服务器,它可以监控多个Redis主节点及其从节点,通过发送PING命令来检测它们是否存活。

        Redis哨兵之间会通过发布订阅模式来相互通信,通过一个名为__sentinel__:hello的频道来交换信息,从而发现其他哨兵和主从节点。

        当一个哨兵发现某个主节点在一定时间内没有回复PING命令,它会将该主节点标记为主观下线(subjectively down),并向其他哨兵询问该主节点的状态。

        如果超过一定数量(可配置)的哨兵都认为某个主节点主观下线,那么该主节点就被标记为客观下线(objectively down),并开始进行故障转移。

故障转移的具体过程是这样的:

第一步,所有监控同一个主节点的哨兵会进行投票,选出一个领头哨兵(leader sentinel),负责执行故障转移。

第二步,领头哨兵会从原主节点的所有从节点中选出一个合适的从节点,作为新的主节点。选举的依据是从节点的优先级、复制偏移量、运行时间等因素。

第三步,领头哨兵会向被选中的从节点发送SLAVEOF NO ONE命令,让它升级为主节点,并向其他从节点发送SLAVEOF命令,让它们复制新的主节点。

第四步,领头哨兵会向所有客户端和哨兵广播新的主节点的地址,让它们更新自己的配置。

Redis 的 key 如何寻址的

取决于Redis是以单机模式还是集群模式运行。

如果是单机模式,那么Redis会使用以下步骤来寻址:

1,Redis会根据key计算出一个哈希值,并根据哈希值在当前数据库中找到对应的哈希表。

2,Redis会根据哈希值在哈希表中找到对应的索引值,并根据索引值在哈希表中取出一个链表。

3,Redis会遍历链表中的每个元素,并比较元素中存储的key是否与给定的key相等。如果相等,则返回该元素中存储的value;如果不相等,则继续遍历下一个元素;如果遍历完毕仍未找到匹配的key,则返回空。

如果是集群模式,那么Redis会使用以下步骤来寻址:

1,Redis会根据key计算出一个哈希值,并根据哈希值对16384取模,得到一个哈希槽的编号。

2,Redis会根据哈希槽的编号在集群中找到负责该哈希槽的节点,并向该节点发送请求。,

3,如果该节点是主节点,那么它会按照单机模式的步骤在本地数据库中寻址,并返回结果给客户端;如果该节点是从节点,那么它会将请求转发给其对应的主节点,并将主节点的结果返回给客户端。

4,如果该节点不负责该哈希槽,那么它会返回一个MOVED或ASK错误给客户端,告诉客户端应该向哪个节点重试请求。客户端收到错误后会根据错误信息找到正确的目标节点,并重新发送请求。

Redis在SpringBoot项目中如何使用?

1,在pom.xml文件中添加spring-boot-starter-data-redis依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 2, 写Redis的配置类
@EnableCaching//开启缓存
@Configuration
public class RedisConfig extends CachingConfigurerSupport {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> 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<String> 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;
    }
}
 3,在要使用Redis缓存的项目中的application.properties或application.yml文件中配置Redis的连接信息,如主机名,端口号,密码等
#redis配置
spring.redis.host=192.168.231.128
spring.redis.port=6379
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
4,使用Redis。在SpringBoot项目中使用Redis有几种方式:

1,使用RedisTemplate进行操作。这是一种比较底层的方式,需要自己配置RedisTemplate的Bean,设置序列化器,然后通过注入RedisTemplate来执行Redis的各种操作,如设置键值对、获取值等。

2,使用Repository的方式操作。这是一种类似于JPA的方式,需要创建一个继承了CrudRepository或PagingAndSortingRepository的接口,然后在实体类上使用@RedisHash注解,指定存储到Redis中的key。这样就可以通过Repository的方法来对Redis中的数据进行增删改查。

3,使用Spring Cache简化缓存操作。这是一种比较高级的方式,只需要添加Spring Cache的依赖,启用缓存支持,然后在服务类或方法上使用缓存注解,如@Cacheable、@CachePut、@CacheEvict等。这样就可以自动将数据缓存到Redis中,不需要手动操作Redis。我下面用的就是这种方式。

使用步骤

1,添加相关的依赖。你需要在你的项目中引入spring-boot-starter-cache和spring-boot-starter-data-redis两个依赖,但是spring-boot-starter-data-redis包含有spring-boot-starter-cache所以只需要引入spring-boot-starter-data-redis。上面已经引入了

2,配置Redis的连接信息。上面也已经配置

3,启用缓存支持。你需要在你的启动类上添加@EnableCaching注解,这样就可以开启Spring Cache的功能了。这个在上面的配置类中也已经完成

4,使用缓存注解。你需要在你想要缓存的服务类或方法上添加相应的缓存注解,比如@Cacheable、@CachePut、@CacheEvict等。这些注解可以让你指定缓存的名称、键值、条件等属性,以及执行前后的操作等。这样就可以实现数据的自动缓存和读取了。

常用的注解详情:

注:下面的@Cacheable等等的key属性是用于指定缓存的键值的一个Spring Expression Language (SpEL) 表达式。这个属性可以让你动态地根据方法的参数或其他变量来生成缓存的键值,而不是使用默认的方法参数作为键值。

@Cacheable:这个注解用于标记一个方法,表示该方法的返回值可以被缓存到Redis中。当下次使用相同的参数调用该方法时,就可以直接从缓存中获取结果,而不需要再执行该方法。你可以通过value属性来指定缓存的名称,通过key属性来指定缓存的键值,通过condition属性来指定缓存的条件等。

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    // 使用@Cacheable注解来缓存用户信息
    // value属性指定缓存名称为user
    // key属性指定缓存键为用户name
    @Cacheable(value = "user", key = "#name")
    @Override
    public User getUserById(Integer id, String name) {
        // 从数据库中查询用户信息
        User user = userMapper.selectById(id);
        return user;
    }
}

@CachePut:这个注解用于标记一个方法,表示该方法的返回值可以被更新到Redis中。当每次调用该方法时,都会将结果更新到缓存中,而不管缓存中是否已经存在该键值。你可以通过value属性来指定缓存的名称,通过key属性来指定缓存的键值,通过condition属性来指定缓存的条件等。

@CacheEvict:这个注解用于标记一个方法,表示该方法可以删除Redis中的缓存。当调用该方法时,会根据指定的键值或者所有键值来删除缓存中的数据。你可以通过value属性来指定缓存的名称,通过key属性来指定缓存的键值,通过allEntries属性来指定是否删除所有键值,通过beforeInvocation属性来指定是否在方法执行前删除缓存等。

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    // 使用@CacheEvict注解来删除缓存数据
    // value属性指定缓存名称为user
    // key属性指定缓存键为用户id
    // beforeInvocation属性指定在方法执行前删除缓存
    @CacheEvict(value = "user", key = "#user.id", beforeInvocation = true)
    @Override
    public void updateUser(User user) {
        // 从数据库中更新用户信息
        userMapper.updateById(user);
    }
}

@Caching:这个注解用于组合多个缓存注解,可以同时使用@Cacheable、@CachePut

/**
*保存用户信息时,将用户信息缓存到Redis中,键值为用户id。并且删除缓存中所有以name开头的键值。
*/
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    // 使用@Caching注解来组合缓存操作
    @Caching(
        // 使用put属性来指定缓存更新操作
        put = @CachePut(value = "user", key = "#user.id"),
        // 使用evict属性来指定缓存删除操作
        evict = @CacheEvict(value = "user", key = "'name'.concat(#user.name)", allEntries = true)
    )
    @Override
    public void saveUser(User user) {
        // 保存用户信息到数据库中
        userMapper.insert(user);
    }
}